JSON for Modern C++ is a header-only C++ library that brings JSON into your code as a first-class data type. Its design goal is intuitive syntax: a JSON value behaves like an STL container, and converting between JSON and your own C++ types is a one-liner. If you can write std::vector or std::map, you already know most of this library.
#include <nlohmann/json.hpp>
using json = nlohmann::json;
json j;
j["pi"] = 3.141;
j["happy"] = true;
j["list"] = {1, 0, 2};
This guide shows you how to add the library to your project and use every part of its public API — building values, parsing and serializing, accessing elements, converting your own types, working with JSON Pointer / JSON Patch, and reading and writing binary formats (CBOR, MessagePack, UBJSON, BSON, BJData). Architecture, the full macro reference, and build-system internals live in the companion Admin Guide.
Note
This guide documents library version 3.12.0. The single header you include is
nlohmann/json.hpp, and the JSON value type isnlohmann::json(conventionally aliased tojson). The full online API reference lives at json.nlohmann.me.
Table of Contents
- Quick Start
- Installation
- Creating JSON Values
- Parsing and Serializing
- Accessing Elements
- Iterating
- Modifying Values
- Converting To and From Your Own Types
- Type Inspection and Conversion
- JSON Pointer, JSON Patch, and Merge Patch
- Binary Formats
- Parsing Extras: Comments, Trailing Commas, JSON Lines
- Compile-Time Options (Macros)
- Common Tasks (Recipes)
- Troubleshooting
- FAQ
- Quick Reference
1. Quick Start
Here is a complete program that builds a JSON object, modifies it, and pretty-prints it. Every line below uses only the public API.
#include <iostream>
#include <iomanip>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// create a JSON object
json j =
{
{"pi", 3.141},
{"happy", true},
{"name", "Niels"},
{"nothing", nullptr},
{"answer", {{"everything", 42}}},
{"list", {1, 0, 2}},
{"object", {{"currency", "USD"}, {"value", 42.99}}}
};
// add new values
j["new"]["key"]["value"] = {"another", "list"};
// count elements
auto s = j.size();
j["size"] = s;
// pretty print with indent of 4 spaces
std::cout << std::setw(4) << j << '\n';
}
{
"answer": {
"everything": 42
},
"happy": true,
"list": [
1,
0,
2
],
"name": "Niels",
"new": {
"key": {
"value": [
"another",
"list"
]
}
},
"nothing": null,
"object": {
"currency": "USD",
"value": 42.99
},
"size": 8
}
Tip
The
std::setw(4)stream manipulator sets the indentation when you print ajsonto anstd::ostream. Without it, the output is the compact single-line form. For programmatic control, usej.dump(4)instead (see §4).
Note
Object keys are stored sorted by default (note
answerappears beforehappyabove). If you need to preserve insertion order, usenlohmann::ordered_json— see §16, "How do I keep object keys in insertion order?".
2. Installation
The library is header-only: at minimum you only need to make single_include/nlohmann/json.hpp reachable on your include path and compile with C++11 or newer. There is nothing to build or link.
Requirements
| Requirement | Detail |
|---|---|
| Language standard | C++11 minimum; C++14/17/20/23 unlock extra features (see §13) |
| Compiler | Any reasonably modern GCC, Clang, MSVC, Intel, or compatible compiler |
| Dependencies | None for using the library (test/build tooling is separate) |
| Header | #include <nlohmann/json.hpp> |
Choosing an integration method
CMake — FetchContent (recommended)
No manual download. CMake fetches the library at configure time:
cmake_minimum_required(VERSION 3.15)
project(json_example)
include(FetchContent)
FetchContent_Declare(
nlohmann_json
URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
)
FetchContent_MakeAvailable(nlohmann_json)
add_executable(json_example example.cpp)
target_link_libraries(json_example PRIVATE nlohmann_json::nlohmann_json)
CMake — find_package (system / package manager)
If the library is already installed (via vcpkg, Conan, Homebrew, your distro, etc.):
cmake_minimum_required(VERSION 3.15)
project(json_example)
find_package(nlohmann_json 3.12.0 REQUIRED)
add_executable(json_example example.cpp)
target_link_libraries(json_example PRIVATE nlohmann_json::nlohmann_json)
CPM.cmake
cmake_minimum_required(VERSION 3.15)
project(json_example)
include(${CMAKE_SOURCE_DIR}/cmake/CPM.cmake)
CPMAddPackage("gh:nlohmann/json@3.12.0")
add_executable(json_example example.cpp)
target_link_libraries(json_example PRIVATE nlohmann_json::nlohmann_json)
Single header (no build system)
Download json.hpp from the release page and drop it into your project:
# compile directly, pointing the compiler at the header's directory
g++ -std=c++11 -I path/to/single_include example.cpp -o example
Important
Always link against the imported target
nlohmann_json::nlohmann_json, not a raw include path. The target carries the correct include directories and (forfind_package) the version compatibility check. The CMake package name isnlohmann_json(with the underscore).
Package managers
The library is widely packaged. Pick whichever matches your toolchain, then use the find_package snippet above.
| Manager | Install command |
|---|---|
| vcpkg | vcpkg install nlohmann-json |
| Conan | add nlohmann_json/3.12.0 to your conanfile |
| Homebrew | brew install nlohmann-json |
| Meson | dependency('nlohmann_json') |
| Spack | spack install nlohmann-json |
| Swift Package Manager | depend on the package; product is json (C++11) |
Tip
Several install snippets in the project's own documentation pass
CONFIGtofind_package(e.g.find_package(nlohmann_json CONFIG REQUIRED)). This forces CMake to use the package's config file rather than a find-module, which is the correct behavior for Homebrew, vcpkg, and macports installs.
3. Creating JSON Values
A json value can hold any JSON type: object, array, string, number, boolean, binary, or null. You build values with ordinary C++ syntax.
From C++ literals
json j_null; // null (default-constructed)
json j_bool = true; // boolean
json j_int = 42; // number (integer)
json j_float = 3.141; // number (floating-point)
json j_string = "Hello, world!"; // string
json j_array = {1, 2, 3}; // array
json j_object = {{"key", "value"}};// object
Nested objects and arrays with initializer lists
Braced initializer lists build nested structures. A list of {key, value} pairs becomes an object; any other list becomes an array.
json j =
{
{"name", "Niels"},
{"age", 30},
{"hobbies", {"coding", "reading"}}, // array
{"address", { // nested object
{"city", "Munich"},
{"zip", "80331"}
}}
};
Warning
An empty
{}is ambiguous — the library treats it as an empty object, not an empty array. To force an empty array, usejson::array(). To be explicit either way, use the named constructors below.
Forcing array vs. object
When initializer-list deduction is ambiguous (for example, a list that looks like key/value pairs but should stay an array), use the explicit factory functions:
// always an array, even though it could be read as an object
json arr = json::array({{"one", 1}, {"two", 2}});
// always an object
json obj = json::object({{"one", 1}, {"two", 2}});
json empty_array = json::array(); // []
json empty_object = json::object(); // {}
From a string literal with _json
The _json user-defined literal parses a string into a json at the point of use:
using namespace nlohmann::literals;
json j = R"({"happy": true, "pi": 3.141})"_json;
Tip
Prefer raw string literals (
R"( ... )") for embedded JSON so you don't have to escape every". Bring the literal into scope withusing namespace nlohmann::literals;.
4. Parsing and Serializing
Parsing (text → json)
json::parse() accepts a string, a char range, an std::istream, an iterator pair, or any contiguous container of bytes.
// from a string
json j = json::parse(R"({"happy": true, "pi": 3.141})");
// from a stream (e.g. a file)
std::ifstream f("data.json");
json j2 = json::parse(f);
// from an iterator pair
std::vector<std::uint8_t> bytes = {'{', '}'};
json j3 = json::parse(bytes.begin(), bytes.end());
If the input is malformed, parse() throws a json::parse_error (see Troubleshooting). To check validity without building a value or throwing, use accept():
bool ok = json::accept(R"({"valid": true})"); // true
Serializing (json → text)
dump() returns a std::string. With no argument it produces compact output; pass an indentation width for pretty-printing.
json j = {{"pi", 3.141}, {"happy", true}};
std::string compact = j.dump(); // {"happy":true,"pi":3.141}
std::string pretty = j.dump(4); // indented with 4 spaces
dump() has four parameters, all optional:
| Parameter | Type | Default | Purpose |
|---|---|---|---|
indent |
int |
-1 |
Spaces per level; -1 means compact (no newlines) |
indent_char |
char |
' ' |
Character used for indentation (e.g. '\t') |
ensure_ascii |
bool |
false |
If true, escape non-ASCII as \uXXXX |
error_handler |
error_handler_t |
strict |
How to handle invalid UTF-8 (see below) |
// tab-indented, ASCII-safe output
std::string s = j.dump(1, '\t', true);
Stream operators
You can read and write JSON directly with << and >>:
std::cout << j; // compact
std::cout << std::setw(4) << j; // pretty (indent = 4)
json j2;
std::cin >> j2; // parse from stdin
Note
When dumping, the
error_handlercontrols what happens if the value contains a string that is not valid UTF-8.error_handler_t::strict(default) throws atype_error;replacesubstitutes U+FFFD (�);ignoredrops the offending bytes.
5. Accessing Elements
The library offers four access styles. Choosing the right one is mostly about whether a missing key is a bug (throw) or expected (return a default).
| Style | Syntax | On missing key/index | Use when |
|---|---|---|---|
| Unchecked | j["key"] / j[0] |
Inserts a null (objects) / UB (arrays) | You know it exists, or you are assigning |
| Checked | j.at("key") / j.at(0) |
Throws out_of_range |
A missing key is an error |
| Default | j.value("key", fallback) |
Returns fallback |
A missing key is acceptable |
| Pointer | j.get_ptr<T*>() |
Returns nullptr |
You want to test without throwing |
operator[] — unchecked, may insert
json j;
j["name"] = "Niels"; // creates the key
auto n = j["name"]; // reads it
Caution
Reading a non-existent key with
operator[]on a non-const object silently inserts a null value at that key. This is a frequent source of bugs (objects that grow unexpectedly). Useat()orcontains()when you only mean to read.
at() — checked, throws on miss
json j = {{"name", "Niels"}};
std::string n = j.at("name"); // ok
// j.at("missing"); // throws json::out_of_range (#403)
// j.at(10) on an array; // throws json::out_of_range (#401)
value() — read with a fallback
json j = json::parse(R"({"uint64": 18446744073709551615})");
std::cout << j.value("uint64", 0) << '\n'; // narrows to int
std::cout << j.value("uint64", std::uint64_t(0)) << '\n'; // keeps full width
std::cout << j.value<std::uint64_t>("uint64", 0) << '\n'; // explicit return type
Tip
The return type of
value()is deduced from the default argument. If you read a large integer with anintdefault, the value is narrowed. Pass a default of the right type (or use the explicitvalue<T>(...)form) to avoid surprises.
contains(), find(), count()
json j = {{"name", "Niels"}, {"age", 30}};
if (j.contains("name")) { /* ... */ } // true, no insertion
auto it = j.find("age");
if (it != j.end()) { int age = *it; }
std::size_t n = j.count("name"); // 0 or 1 for objects
6. Iterating
A json array or object exposes STL-style iterators, so range-based for works directly.
json j = {1, 2, 3, 4};
for (auto& element : j) {
std::cout << element << '\n';
}
For objects, range-based for over the value alone gives you only the values. To get keys and values, use items():
json j = {{"one", 1}, {"two", 2}};
for (auto& [key, value] : j.items()) { // C++17 structured bindings
std::cout << key << " : " << value << '\n';
}
// pre-C++17
for (auto it = j.begin(); it != j.end(); ++it) {
std::cout << it.key() << " : " << it.value() << '\n';
}
Note
it.key()is only valid when iterating an object. Calling it while iterating an array throws aninvalid_iterator(#207). Reverse iteration is available viarbegin()/rend()and the const variantscbegin()/cend()/crbegin()/crend().
7. Modifying Values
Arrays
json j = {1, 2, 3};
j.push_back(4); // [1,2,3,4]
j += 5; // [1,2,3,4,5] (operator+= alias)
j.emplace_back(6); // construct in place
j.erase(0); // remove by index → [2,3,4,5,6]
Objects
json j = {{"a", 1}};
j["b"] = 2; // insert/overwrite
j.emplace("c", 3); // insert if absent
j.erase("a"); // remove by key
// merge another object's keys in
json extra = {{"d", 4}, {"e", 5}};
j.update(extra); // adds d and e
Tip
update()is the idiomatic "merge these keys into me" operation for objects (similar to Python'sdict.update). By default it merges only the top level; passtrueas the second argument to merge recursively.
insert(), clear(), swap()
json arr = {1, 2, 3};
arr.insert(arr.begin() + 1, 99); // [1,99,2,3]
json a = {1, 2}, b = {3, 4};
a.swap(b); // a == [3,4], b == [1,2]
arr.clear(); // becomes [] (type preserved, emptied)
8. Converting To and From Your Own Types
This is the library's headline feature: any type can be made JSON-serializable, and conversion is implicit.
Standard library types just work
std::vector, std::map, std::array, std::set, std::optional (C++17), and friends convert automatically:
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
json j = m; // {"a":1,"b":2}
auto m2 = j.get<std::map<std::string, int>>();
Your own structs — the macros
For your own types, the simplest path is one of the NLOHMANN_DEFINE_TYPE_* macros. Place the macro inside (intrusive) or next to (non-intrusive) your type, listing the members to serialize.
Non-intrusive (use when you cannot change the type, or want to keep it a plain aggregate):
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using namespace nlohmann::literals;
namespace ns {
struct person {
std::string name;
std::string address;
int age;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age)
}
int main()
{
ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
json j = p; // serialize
std::cout << j << '\n';
// {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}
json j2 = R"({"name":"Homer","address":"742 Evergreen Terrace","age":40})"_json;
auto p2 = j2.get<ns::person>(); // deserialize
}
Intrusive with defaults — when JSON may be missing fields, use the _WITH_DEFAULT variant so absent keys fall back to the member's default-constructed value instead of throwing:
namespace ns {
class person {
std::string name = "John Doe";
std::string address = "123 Fake St";
int age = -1;
public:
person() = default;
person(std::string n, std::string a, int g)
: name(std::move(n)), address(std::move(a)), age(g) {}
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(person, name, address, age)
};
}
// JSON missing "age" → age stays -1 instead of throwing
auto p = R"({"address":"742 Evergreen Terrace","name":"Maggie Simpson"})"_json
.get<ns::person>();
Important
The plain (non-
WITH_DEFAULT) macros throw atype_error/out_of_rangeif a listed key is missing from the JSON during deserialization. If your inputs may omit fields, use the_WITH_DEFAULTfamily. There is also an_ONLY_SERIALIZEfamily for types that should be written but never read back, and_WITH_NAMESvariants that let the JSON key differ from the C++ member name. The full macro matrix is in the Admin Guide.
Enums
Map enum values to JSON (usually strings) with NLOHMANN_JSON_SERIALIZE_ENUM:
namespace ns {
enum class Color { red, green, blue, unknown };
NLOHMANN_JSON_SERIALIZE_ENUM(Color, {
{ Color::unknown, "unknown" },
{ Color::red, "red" },
{ Color::green, "green" },
{ Color::blue, "blue" },
})
}
json j = ns::Color::red; // "red"
auto c = j.get<ns::Color>(); // Color::red
Warning
The first pair listed is the default: deserializing a JSON value that doesn't match any mapping yields that first enum value (here
Color::unknown). This surprised enough users (issue #3992) that a stricter macro,NLOHMANN_JSON_SERIALIZE_ENUM_STRICT, now exists — it throws on an unmatched value instead of silently defaulting.
Manual to_json / from_json
For full control, define free functions to_json and from_json in your type's namespace (ADL finds them):
namespace ns {
void to_json(json& j, const person& p) {
j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
}
void from_json(const json& j, person& p) {
j.at("name").get_to(p.name);
j.at("address").get_to(p.address);
j.at("age").get_to(p.age);
}
}
Tip
get_to(target)reads a value into an existing variable and deduces the type fromtarget— cleaner thantarget = j.get<T>()insidefrom_json.
9. Type Inspection and Conversion
Asking what a value is
json j = 42;
j.is_number(); // true
j.is_number_integer(); // true
j.is_string(); // false
j.type(); // json::value_t::number_integer
j.type_name(); // "number"
The full predicate set: is_null, is_boolean, is_number, is_number_integer, is_number_unsigned, is_number_float, is_object, is_array, is_string, is_binary, is_primitive, is_structured, is_discarded.
Extracting a C++ value
json j = json::parse(R"({"name":"Niels","age":30})");
std::string name = j["name"].get<std::string>();
int age = j.at("age").get<int>();
// get_to writes into an existing variable
std::string n2;
j["name"].get_to(n2);
// implicit conversion (enabled by default)
std::string n3 = j["name"];
Note
Implicit conversions (
std::string n = j["name"];) are convenient but can be ambiguous in generic code. You can disable them project-wide by definingJSON_USE_IMPLICIT_CONVERSIONS=0, which forces the explicitget<T>()/get_to()forms everywhere (see §13).
get_ref / get_ptr — zero-copy access
When you need a reference to the underlying storage without a copy:
json j = "hello";
auto& s = j.get_ref<std::string&>(); // reference, no copy
auto* p = j.get_ptr<std::string*>(); // pointer, or nullptr if wrong type
10. JSON Pointer, JSON Patch, and Merge Patch
JSON Pointer (RFC 6901) — addressing into a document
A json::json_pointer is a path like /foo/bar/0 that drills into a nested document.
json j = R"({"foo": {"bar": [10, 20, 30]}})"_json;
json::json_pointer ptr("/foo/bar/1");
std::cout << j[ptr] << '\n'; // 20
std::cout << j.at(ptr) << '\n'; // 20 (checked)
Pointer rules: every reference token starts with /; ~1 means a literal / inside a key, and ~0 means a literal ~; the token - refers to the (nonexistent) element past the end of an array, used for appends.
json::json_pointer p1(""); // whole document
json::json_pointer p2("/foo/bar"); // nested
json::json_pointer p3("/foo/-"); // "append" position for arrays
// json::json_pointer bad("foo"); // throws parse_error: must start with '/'
JSON Patch (RFC 6902) — a list of edits
A JSON Patch is itself a JSON array of operations (add, remove, replace, move, copy, test). Apply it with patch():
json doc = R"({"baz": "qux", "foo": "bar"})"_json;
json patch = R"([
{ "op": "replace", "path": "/baz", "value": "boo" },
{ "op": "add", "path": "/hello", "value": ["world"] },
{ "op": "remove", "path": "/foo" }
])"_json;
json result = doc.patch(patch);
// {"baz":"boo","hello":["world"]}
You can also compute the patch that turns one document into another with json::diff():
json patch = json::diff(source, target);
json roundtrip = source.patch(patch); // == target
JSON Merge Patch (RFC 7386) — a simpler merge
Merge Patch describes changes as a partial document. null values mean "delete this key"; everything else is overlaid.
json document = R"({
"title": "Goodbye!",
"author": {"givenName": "John", "familyName": "Doe"},
"tags": ["example", "sample"]
})"_json;
json patch = R"({
"title": "Hello!",
"phoneNumber": "+01-123-456-7890",
"author": {"familyName": null},
"tags": ["example"]
})"_json;
document.merge_patch(patch);
// title updated, phoneNumber added, author.familyName removed, tags replaced
Tip
Use JSON Patch when you need precise, ordered, testable operations (e.g. an audit trail of edits). Use Merge Patch when you just want to overlay a partial update and don't care about per-operation control.
Flatten / unflatten
flatten() collapses a nested document into a flat object keyed by JSON Pointers; unflatten() reverses it.
json j = R"({"a": {"b": 1}, "c": [2, 3]})"_json;
json flat = j.flatten();
// {"/a/b":1, "/c/0":2, "/c/1":3}
json back = flat.unflatten(); // == j
11. Binary Formats
The library reads and writes five binary JSON-like formats. Each has a to_* (serialize) and from_* (parse) pair.
| Format | Serialize | Parse | Notes |
|---|---|---|---|
| CBOR | json::to_cbor(j) |
json::from_cbor(bytes) |
RFC 8949 |
| MessagePack | json::to_msgpack(j) |
json::from_msgpack(bytes) |
|
| UBJSON | json::to_ubjson(j) |
json::from_ubjson(bytes) |
|
| BSON | json::to_bson(j) |
json::from_bson(bytes) |
Top-level value must be an object |
| BJData | json::to_bjdata(j) |
json::from_bjdata(bytes) |
UBJSON derivative |
json j = R"({"compact": true, "schema": 0})"_json;
// JSON → CBOR (a std::vector<std::uint8_t>)
std::vector<std::uint8_t> v = json::to_cbor(j);
// CBOR → JSON
json j2 = json::from_cbor(v); // round-trips back to j
Note
Binary formats carry a true binary type that plain JSON lacks. Build one with
json::binary(bytes)(optionally with a subtype), and inspect it withis_binary()/get_binary(). CBOR and BSON preserve subtypes; the others approximate.
Caution
to_bson()requires the top-level value to be an object — BSON has no notion of a bare array or scalar document. Passing an array throws atype_error.
12. Parsing Extras: Comments, Trailing Commas, JSON Lines
Standard JSON forbids comments and trailing commas, but the parser can be told to tolerate them.
Comments
std::string s = R"({
// line comment
"planets": ["Mercury", "Venus", "Earth" /*, "Pluto" */]
})";
// 4th argument enables comment skipping
json j = json::parse(s,
/* callback */ nullptr,
/* allow_exceptions*/ true,
/* ignore_comments */ true);
Warning
Without
ignore_comments = true, the comment above causes aparse_error. Comment support is an extension; do not rely on it when interoperating with strict JSON consumers.
Trailing commas
Trailing-comma tolerance is controlled the same way (via the parse options). A document like {"a": 1,} parses cleanly when enabled and throws otherwise.
JSON Lines
To process a stream of one-JSON-value-per-line (JSON Lines / NDJSON), read and parse line by line:
std::ifstream f("data.jsonl");
std::string line;
while (std::getline(f, line)) {
if (line.empty()) continue;
json j = json::parse(line);
// handle j
}
Parser callbacks — filtering during parse
A parser_callback_t lets you accept, reject, or transform elements as they are parsed — useful for dropping large subtrees you don't need.
json::parser_callback_t cb =
[](int depth, json::parse_event_t event, json& parsed)
{
// skip any object member whose key is "Thumbnail"
if (event == json::parse_event_t::key && parsed == json("Thumbnail"))
return false; // discard
return true; // keep
};
json filtered = json::parse(text, cb);
For very large inputs where you don't want to build a DOM at all, use the SAX interface (json::sax_parse) — see the Admin Guide.
13. Compile-Time Options (Macros)
Because this is a header-only library with no runtime configuration, its "settings" are preprocessor macros you define before including the header (or via your build system, e.g. target_compile_definitions). These are the ones you are most likely to touch as a user; the complete macro reference is in the Admin Guide.
| Macro | Default | Effect |
|---|---|---|
JSON_USE_IMPLICIT_CONVERSIONS |
1 |
Set to 0 to disable implicit json → T conversions and require get<T>()/get_to() |
JSON_DIAGNOSTICS |
0 |
Set to 1 to include the JSON Pointer path of the offending value in exception messages |
JSON_NOEXCEPTION |
off | Disable all exceptions (calls abort on error instead); for -fno-exceptions builds |
JSON_NO_IO |
off | Drop all <iostream> dependencies (no stream operators, no parse(istream)) — for freestanding/embedded |
JSON_USE_GLOBAL_UDLS |
1 |
Set to 0 to keep the _json / _json_pointer literals out of the global namespace |
# enable richer exception messages for everything linking this target
target_compile_definitions(my_app PRIVATE JSON_DIAGNOSTICS=1)
Important
JSON_DIAGNOSTICSchanges the library's ABI. If you set it, set it consistently across every translation unit and every library that exchangesjsonobjects, or you will hit subtle linker/ODR issues (the cause of issue #3106). The recommended way with CMake is to define it on the imported target so it propagates everywhere.
14. Common Tasks (Recipes)
Read a JSON file into a value
std::ifstream f("config.json");
json config = json::parse(f);
Write a value to a file (pretty)
std::ofstream out("config.json");
out << std::setw(4) << config << '\n';
Safely read an optional setting with a default
int timeout = config.value("timeout_ms", 5000); // 5000 if absent
Round-trip a struct through a file
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Settings, host, port, retries)
Settings s = json::parse(std::ifstream("settings.json")).get<Settings>();
std::ofstream("settings.json") << std::setw(2) << json(s);
Convert JSON to MessagePack for the wire
std::vector<std::uint8_t> wire = json::to_msgpack(payload);
// ... send wire ...
json received = json::from_msgpack(wire);
Validate input without throwing
if (!json::accept(untrusted_text)) {
// reject early, no exception, no DOM built
}
Pull library/build metadata at runtime
json info = json::meta();
std::cout << info["version"]["string"] << '\n'; // "3.12.0"
std::cout << info["platform"] << ' ' << info["compiler"]["family"] << '\n';
15. Troubleshooting
The library reports errors as typed exceptions derived from json::exception. Each carries a stable numeric id and a human-readable message. Catch the base json::exception to handle any of them, or a specific subtype.
| Exception type | ID range | Meaning |
|---|---|---|
json::parse_error |
1xx | Input text/bytes were not valid JSON |
json::invalid_iterator |
2xx | Misused an iterator |
json::type_error |
3xx | Operation not valid for the value's type |
json::out_of_range |
4xx | Index or key does not exist |
json::other_error |
5xx | Everything else (e.g. a failed test patch op) |
Error: [json.exception.parse_error.101] parse error ...
Cause. The input passed to json::parse() is not well-formed JSON (a stray character, unterminated string, or — commonly — a comment or trailing comma in strict mode).
Solution. Fix the input, or enable the relevant tolerance: pass ignore_comments = true to parse() for comments. To check first without throwing, use json::accept().
Prevention. Validate untrusted input with accept() before parsing, and prefer raw string literals to avoid escaping mistakes.
Error: [json.exception.type_error.302] type must be string, but is ...
Cause. You called get<T>() or get_to() with a T that doesn't match the value's actual JSON type (e.g. reading a number as std::string).
Solution. Check j.type() / is_string() first, or read into the correct type. Use value("key", default) when the field may be absent or of the wrong type.
Prevention. Inspect types defensively when consuming external JSON; lean on the NLOHMANN_DEFINE_TYPE_*_WITH_DEFAULT macros so missing fields don't surprise you.
Error: [json.exception.out_of_range.403] key 'X' not found
Cause. at("X") was called for a key that does not exist.
Solution. Use contains("X") to test first, value("X", fallback) to supply a default, or find("X") to get an iterator you can compare against end().
Prevention. Reserve at() for cases where a missing key really is a programming error; use value() for optional fields.
Error: [json.exception.out_of_range.401] array index N is out of range
Cause. at(N) indexed past the end of an array.
Solution. Check j.size() before indexing, or iterate instead of indexing.
Problem: an object grew keys I never set
Cause. Reading a missing key with operator[] on a non-const json inserts a null at that key.
Solution. Use at() or contains() for read-only access. Mark the value const where possible — operator[] on a const object does not insert (it asserts/UB on a missing key, surfacing the bug at the access site).
Prevention. Treat operator[] as "create-or-assign"; treat at()/value() as "read".
Problem: deserializing my struct throws on valid-looking JSON
Cause. A plain NLOHMANN_DEFINE_TYPE_* macro requires every listed member to be present in the JSON.
Solution. Switch to the _WITH_DEFAULT variant so absent fields fall back to defaults, or supply the missing keys.
Problem: std::optional members aren't being picked up
Cause. std::optional support requires C++17, and it is not auto-detected in every configuration (issue #4864).
Solution. Compile with -std=c++17 (or later) and ensure the C++17 feature path is active. If you rely on optional members, verify with a small round-trip test.
Problem: ODR / linker errors when mixing diagnostic settings
Cause. JSON_DIAGNOSTICS (and other ABI-affecting macros) were defined differently across translation units or libraries (issue #3106).
Solution. Define ABI-affecting macros uniformly for the whole build — ideally on the imported CMake target so they propagate to every consumer.
Tip
Set
JSON_DIAGNOSTICS=1while debugging: exception messages then include the JSON Pointer to the exact element that triggered the error (e.g.(/author/familyName)), which turns "type error somewhere" into a precise location.
16. FAQ
Q: Is this library really header-only? Do I need to build or link anything?
No build, no link. Make nlohmann/json.hpp reachable and compile with C++11+. The CMake target nlohmann_json::nlohmann_json only carries include directories and compile options.
Q: What C++ standard do I need?
C++11 is the minimum. C++14, 17, 20, and 23 progressively enable extra conveniences (transparent comparators, std::optional and std::string_view support, the spaceship operator<=>, C++20 modules, and ranges).
Q: How do I keep object keys in insertion order instead of sorted?
Use nlohmann::ordered_json (a drop-in alias that stores objects in an insertion-ordered map) instead of nlohmann::json. The API is identical.
Q: Why does reading a key sometimes add it to my object?
operator[] inserts on a missing key for non-const objects. Use at(), value(), or contains() for pure reads. See §5.
Q: How do I serialize my own class with minimal code?
Add NLOHMANN_DEFINE_TYPE_INTRUSIVE(YourType, member1, member2, ...) inside the class (it can see private members) or NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(YourType, ...) in the same namespace. Use the _WITH_DEFAULT variants if fields may be missing in the JSON.
Q: What's the difference between dump() and the stream operator?
dump(indent) returns a std::string and lets you control indentation, indent character, ASCII escaping, and error handling. std::cout << j writes to a stream; combine with std::setw(n) to pretty-print.
Q: Can I parse JSON with comments or trailing commas?
Yes, but only by opting in per-parse (ignore_comments = true, and the matching trailing-comma option). They are extensions to standard JSON — don't emit them for strict consumers.
Q: How do I read or write CBOR / MessagePack / BSON?
Use the to_<format> / from_<format> static functions (§11). They convert between a json value and a std::vector<std::uint8_t>.
Q: How do I produce richer error messages?
Compile with JSON_DIAGNOSTICS=1 so exceptions include the JSON Pointer path to the offending value — but set it uniformly across your whole build.
Q: Is the library thread-safe?
Reading a json value from multiple threads is safe; concurrent reads and writes of the same value are not (standard C++ container rules apply). Give each thread its own values, or synchronize externally.
Q: How do I check the version at runtime?
json::meta()["version"]["string"] returns e.g. "3.12.0", along with platform and compiler details.
17. Quick Reference
Construction
| Goal | Code |
|---|---|
| Null | json j; |
| From literal text | auto j = R"({"a":1})"_json; |
| Empty array / object | json::array() / json::object() |
| Parse string/stream | json::parse(s) |
Access
| Goal | Code |
|---|---|
| Unchecked (may insert) | j["k"], j[0] |
| Checked (throws) | j.at("k"), j.at(0) |
| With default | j.value("k", fallback) |
| Test existence | j.contains("k"), j.find("k") |
| By JSON Pointer | j[json::json_pointer("/a/b")] |
Serialize
| Goal | Code |
|---|---|
| Compact string | j.dump() |
| Pretty (4 spaces) | j.dump(4) |
| To stream (pretty) | out << std::setw(4) << j; |
| To CBOR / MessagePack | json::to_cbor(j) / json::to_msgpack(j) |
Convert your types
| Goal | Code |
|---|---|
| Struct (non-intrusive) | NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(T, a, b) |
| Struct, tolerate missing | NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(T, a, b) |
| Enum ↔ string | NLOHMANN_JSON_SERIALIZE_ENUM(E, {{E::x,"x"}, ...}) |
| Extract value | j.get<T>(), j.get_to(var) |
Patch & pointer
| Goal | Code |
|---|---|
| Apply JSON Patch | doc.patch(patchArray) |
| Compute diff | json::diff(src, dst) |
| Merge Patch | doc.merge_patch(partial) |
| Flatten / unflatten | j.flatten() / flat.unflatten() |