C++ JSON Parsing: nlohmann/json, RapidJSON, Custom Types,
The Problem with Naive JSON Parsing
C++ has no built-in JSON support, and rolling your own parser is an invitation to bugs. The common pitfalls when consuming JSON from REST APIs or config files:
- Type mismatch: the API sends
"age": "30"(string) but your code calls.get<int>() - Missing keys:
j["optional_field"]silently inserts a null into the object - Parse errors: malformed JSON crashes the process if exceptions aren’t caught
- Memory bloat: loading a 50MB JSON file into memory all at once
Two libraries dominate C++ JSON parsing: nlohmann/json (ergonomic, STL-like) and RapidJSON (fast, low memory).
Library Comparison
| nlohmann/json | RapidJSON | |
|---|---|---|
| API style | STL-like, intuitive | Verbose, explicit |
| Parse speed | Good | Excellent (~2-5x faster) |
| Memory | Higher | Lower; SAX mode available |
| Custom types | to_json / from_json | Manual mapping |
| Error handling | Exceptions | HasParseError() check |
| Header-only | Yes | Yes |
| Best for | Most projects | High-throughput, large files |
1. nlohmann/json
Install via CMake’s FetchContent or copy the single header:
#include <nlohmann/json.hpp>
using json = nlohmann::json;
Parsing a String or File
#include <nlohmann/json.hpp>
#include <fstream>
#include <stdexcept>
using json = nlohmann::json;
// Parse from string
json parseFromString(const std::string& raw) {
try {
return json::parse(raw);
} catch (const json::parse_error& e) {
throw std::runtime_error(
"JSON parse error at byte " + std::to_string(e.byte) + ": " + e.what()
);
}
}
// Parse from file
json parseFromFile(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
throw std::runtime_error("Cannot open file: " + path);
}
try {
return json::parse(file);
} catch (const json::parse_error& e) {
throw std::runtime_error("Parse error in " + path + ": " + e.what());
}
}
Safe Field Access
The most common mistake is using j["key"] for optional fields — it silently inserts null. Use these patterns instead:
void processApiResponse(const json& j) {
// Required field — throws json::out_of_range if missing
std::string id = j.at("id").get<std::string>();
// Optional field with default
std::string name = j.value("name", "Unknown");
// Check before accessing
if (j.contains("email")) {
std::string email = j["email"].get<std::string>();
}
// Type checking before conversion
if (j.contains("age") && j["age"].is_number_integer()) {
int age = j["age"].get<int>();
}
// Nested object — check each level
if (j.contains("address") && j["address"].is_object()) {
const auto& addr = j["address"];
std::string city = addr.value("city", "");
}
// Array iteration
if (j.contains("tags") && j["tags"].is_array()) {
for (const auto& tag : j["tags"]) {
if (tag.is_string()) {
std::cout << tag.get<std::string>() << '\n';
}
}
}
}
Error Taxonomy
try {
auto j = json::parse(raw_input);
// type_error: wrong type assumed
int x = j["count"].get<int>(); // throws if "count" is a string
} catch (const json::parse_error& e) {
// Malformed JSON — log e.byte for the failure location
log_error("Parse failed at byte {}: {}", e.byte, e.what());
} catch (const json::type_error& e) {
// Type mismatch during .get<T>() or operator usage
log_error("Type error: {}", e.what());
} catch (const json::out_of_range& e) {
// .at("key") when key doesn't exist
log_error("Missing field: {}", e.what());
}
2. Custom Type Serialization
For structs, define to_json and from_json as free functions:
struct User {
std::string id;
std::string email;
int age{0};
std::optional<std::string> phone; // optional field
};
void to_json(json& j, const User& u) {
j = json{
{"id", u.id},
{"email", u.email},
{"age", u.age},
};
if (u.phone.has_value()) {
j["phone"] = u.phone.value();
}
}
void from_json(const json& j, User& u) {
u.id = j.at("id").get<std::string>();
u.email = j.at("email").get<std::string>();
u.age = j.value("age", 0);
if (j.contains("phone") && j["phone"].is_string()) {
u.phone = j["phone"].get<std::string>();
}
}
// Usage — automatic conversion
User user = json::parse(raw).get<User>();
json j = user; // serialize back to JSON
std::cout << j.dump(2) << '\n'; // pretty-print with 2-space indent
Macro Shortcut for Simple Structs
struct Config {
std::string host;
int port{8080};
bool debug{false};
};
// Generates to_json and from_json automatically
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Config, host, port, debug)
// Now: Config cfg = json::parse(raw).get<Config>();
3. RapidJSON
RapidJSON is faster and uses less memory, at the cost of a more verbose API:
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/filereadstream.h>
using namespace rapidjson;
void parseWithRapidJson(const std::string& raw) {
Document doc;
doc.Parse(raw.c_str());
// Always check for parse errors
if (doc.HasParseError()) {
fprintf(stderr, "Parse error at offset %zu: %s\n",
doc.GetErrorOffset(),
GetParseError_En(doc.GetParseError()));
return;
}
// Safe field access
if (doc.HasMember("name") && doc["name"].IsString()) {
std::string name = doc["name"].GetString();
}
if (doc.HasMember("count") && doc["count"].IsInt()) {
int count = doc["count"].GetInt();
}
// Array
if (doc.HasMember("items") && doc["items"].IsArray()) {
const auto& items = doc["items"];
for (SizeType i = 0; i < items.Size(); ++i) {
if (items[i].IsString()) {
printf("%s\n", items[i].GetString());
}
}
}
}
File Streaming (Low Memory)
For large files, stream directly rather than loading into a std::string:
#include <rapidjson/filereadstream.h>
#include <cstdio>
void parseLargeFile(const char* path) {
FILE* fp = fopen(path, "rb");
if (!fp) return;
char readBuffer[65536];
FileReadStream is(fp, readBuffer, sizeof(readBuffer));
Document doc;
doc.ParseStream(is);
fclose(fp);
if (doc.HasParseError()) {
fprintf(stderr, "Error: %s\n", GetParseError_En(doc.GetParseError()));
}
}
Peak memory is roughly the size of the largest single JSON value, not the entire file.
4. Performance Comparison
Rough numbers parsing a 1 MB JSON document (environment-dependent):
| Library | Parse time | Peak memory |
|---|---|---|
| nlohmann/json | ~60-100ms | ~2× file size |
| RapidJSON DOM | ~15-30ms | ~1× file size |
| RapidJSON SAX | ~10-20ms | ~0.1× file size |
Rule: use nlohmann/json unless profiling shows JSON parsing as a bottleneck. If you process megabyte-scale JSON frequently, switch to RapidJSON SAX.
Production Checklist
- Always catch
parse_errorwhen parsing untrusted input (API responses, config files, user uploads) - Never use
j["key"]for optional fields — it silently inserts null and corrupts your object - Cap input size before parsing — reject payloads over your maximum (e.g., 1MB for API requests)
- Log the byte offset from
parse_error.byteso you can diagnose truncated or malformed messages - Thread safety: nlohmann/json objects are not thread-safe for concurrent mutation — copy or parse per thread
- Validate required fields with
.at()so missing required data throws immediately rather than silently defaulting - Encode UTF-8 — both libraries expect UTF-8; validate incoming byte sequences from untrusted sources
Key Takeaways
- nlohmann/json is the right default — readable, STL-compatible, and supports automatic custom type serialization via
to_json/from_json - RapidJSON wins on raw performance and memory; use its SAX API for multi-MB files
- Use
value("key", default)orcontains()for optional fields — never nakedj["key"] - The error hierarchy matters:
parse_error(bad JSON),type_error(wrong type),out_of_range(missing required key) - Define
to_json/from_jsonor useNLOHMANN_DEFINE_TYPE_NON_INTRUSIVEto keep struct mapping maintainable
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Parse REST APIs and config files in C++ safely: nlohmann/json vs RapidJSON, contains/value/at, to_json/from_json.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ JSON 처리 | nlohmann/json으로 파싱과 생성하기 [#27-2]
- C++ REST API 클라이언트 완벽 가이드 | CRUD·인증·에러 처리·프로덕션 패턴 [#21-3]
- C++ REST API 서버 완벽 가이드 | Beast 라우팅·JSON·미들웨어 [#31-2]
Keywords Covered in This Article (Related Search Terms)
This article covers C++, JSON, Parsing, nlohmann, RapidJSON, Serialization, REST API.