Jesse A. Green / notebook

JSON for Modern C++ (nlohmann/json) — User Guide

Generated June 2026 from nlohmann/json at d10879b

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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 is nlohmann::json (conventionally aliased to json). The full online API reference lives at json.nlohmann.me.


Table of Contents

  1. Quick Start
  2. Installation
  3. Creating JSON Values
  4. Parsing and Serializing
  5. Accessing Elements
  6. Iterating
  7. Modifying Values
  8. Converting To and From Your Own Types
  9. Type Inspection and Conversion
  10. JSON Pointer, JSON Patch, and Merge Patch
  11. Binary Formats
  12. Parsing Extras: Comments, Trailing Commas, JSON Lines
  13. Compile-Time Options (Macros)
  14. Common Tasks (Recipes)
  15. Troubleshooting
  16. FAQ
  17. 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 a json to an std::ostream. Without it, the output is the compact single-line form. For programmatic control, use j.dump(4) instead (see §4).

Note

Object keys are stored sorted by default (note answer appears before happy above). If you need to preserve insertion order, use nlohmann::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 (for find_package) the version compatibility check. The CMake package name is nlohmann_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 CONFIG to find_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, use json::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 with using 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_handler controls what happens if the value contains a string that is not valid UTF-8. error_handler_t::strict (default) throws a type_error; replace substitutes U+FFFD (�); ignore drops 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). Use at() or contains() 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 an int default, the value is narrowed. Pass a default of the right type (or use the explicit value<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 an invalid_iterator (#207). Reverse iteration is available via rbegin()/rend() and the const variants cbegin()/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's dict.update). By default it merges only the top level; pass true as 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 a type_error/out_of_range if a listed key is missing from the JSON during deserialization. If your inputs may omit fields, use the _WITH_DEFAULT family. There is also an _ONLY_SERIALIZE family for types that should be written but never read back, and _WITH_NAMES variants 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 from target — cleaner than target = j.get<T>() inside from_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 defining JSON_USE_IMPLICIT_CONVERSIONS=0, which forces the explicit get<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 with is_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 a type_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 a parse_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_DIAGNOSTICS changes the library's ABI. If you set it, set it consistently across every translation unit and every library that exchanges json objects, 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=1 while 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()