* Review and extend the documentation, and check it in CI A review of all documentation pages found factual errors, dead links, missing cross-references, and gaps in examples. This fixes them and adds checks so the same problems are caught automatically. Fixes: - wrong signatures and version histories (operator!= C++20 member, binary() subtype type, get<PointerType>(), JSON_NO_THREAD_LOCAL, ...) - stale descriptions (number parsing since #5283, UBJSON table, SAX example that no longer compiled, tsl::ordered_map advice) - dead internal and external links; repology.org badges (the domain is suspended) replaced by badges that query the registries directly - deprecation notes link the migration guide; the guide itself fixed Additions: - "See also" sections, cross-references, 25 runnable examples, 12 Mermaid diagrams, new API pages for json_pointer::operator<=> and byte_container_with_subtype::operator==/!= - landing page, guides for untrusted input and performance - "unreleased" badge after versions newer than the latest release Checks: - strict documentation build (broken links/anchors fail it); CI and the publish workflow fetch the full history the build needs - weekly external link check, Mermaid syntax check in CI - check_structure.py: example titles, heading levels, alt texts, header links, docset index coverage; its unused-example check works again - all examples produce the same output on every platform Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Keep the customer links that could not be fixed A dead link on the customers page is still the evidence of where the use of the library was documented. Keep the original URLs of the entries without a working replacement (Marne, Cisco Webex Desk Camera, Philips Hue, CyberArk) and exclude exactly these URLs from the link check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Correct the duplicate-key recipe's claim about SAX positions The SAX interface's key() receives no position either; only parse_error() does. Also note that the recipe does not report the path to the repeated key (see discussion #5085). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Say the library is available as a single header and mention json_fwd.hpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Correct documentation errors found while hunting for bugs - patch/patch_inplace: list the JSON pointer errors parse_error.106-109 and out_of_range.402/404, and quote the actual parse_error.105 message. - unflatten: list parse_error.106/107/108 and out_of_range.404. - to_bson: list out_of_range.415 (binary subtype above 255) and note that 412 and 415 are new in 3.13.0. - to_string: state that string_t must be convertible to std::string, also in the StringType requirements table. - JSON Lines: a `while (input >> j)` loop also throws after the last value for concatenated JSON values; show a loop that works for both. - BON8: a string gets 0xFF only if nothing follows it in the message; a string at the end of an array or object is ended by 0xFE. - custom_string_type.hpp: add operator+=(char), which the "Always required" list asks for (json_pointer::to_string, flatten, unflatten, and diff did not compile), and an ADL int_to_string for diff and items. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Cache the release headers with functools.lru_cache Codacy (Pylint) flagged the mutable default argument that header() used as its cache. functools.lru_cache keeps the same memoization without it. The script's output is unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
14 KiB
Frequently Asked Questions (FAQ)
Known bugs
Brace initialization yields arrays
!!! question
Why does
```cpp
json j{true};
```
and
```cpp
json j(true);
```
yield different results (`#!json [true]` vs. `#!json true`)?
This is a known issue, and -- even worse -- the behavior differs between GCC and Clang. The "culprit" for this is the library's constructor overloads for initializer lists to allow syntax like
json array = {1, 2, 3, 4};
for arrays and
json object = {{"one", 1}, {"two", 2}};
for objects.
!!! tip
To avoid any confusion and ensure portable code, **do not** use brace initialization with the types `basic_json`, `json`, or `ordered_json` unless you want to create an object or array as shown in the examples above.
To explicitly create a single-element array, use `json::array({value})`:
```cpp
json j = json::array({true}); // [true]
```
Opt-in copy semantics (since version 3.13.0)
If you define JSON_BRACE_INIT_COPY_SEMANTICS to 1 before including the library, single-element brace initialization is treated as copy/move instead of creating a single-element array:
#define JSON_BRACE_INIT_COPY_SEMANTICS 1
#include <nlohmann/json.hpp>
json obj = {{"key", "value"}};
json j{obj}; // -> {"key":"value"} (copy, not array)
Without the macro (default behavior), json j{obj} creates [{"key":"value"}]. This opt-in macro fixes issue #5074 while preserving backwards compatibility for existing code.
Limitations
Relaxed parsing
!!! question
Can you add an option to ignore trailing commas?
This library does not support any feature that would jeopardize interoperability.
Parse errors reading non-ASCII characters
!!! question "Questions"
- Why is the parser complaining about a Chinese character?
- Does the library support Unicode?
- I get an exception `[json.exception.parse_error.101] parse error at line 1, column 53: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '"Testé$')"`
The library supports Unicode input as follows:
- Only UTF-8 encoded input is supported, which is the default encoding for JSON, according to RFC 8259.
std::u16stringandstd::u32stringcan be parsed, assuming UTF-16 and UTF-32 encoding, respectively. These encodings are not supported when reading from files or other input containers.- Other encodings such as Latin-1 or ISO 8859-1 are not supported and will yield parse or serialization errors.
- The library will not replace Unicode noncharacters.
- Invalid surrogates (e.g., incomplete pairs such as
\uDEAD) will yield parse errors. - The strings stored in the library are UTF-8 encoded. When using the default string type (
std::string), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs. - When you store strings with different encodings in the library, calling
dump()may throw an exception unlessjson::error_handler_t::replaceorjson::error_handler_t::ignoreare used as error handlers.
In most cases, the parser is right to complain, because the input is not UTF-8 encoded. This is especially true for Microsoft Windows, where Latin-1 or ISO 8859-1 is often the standard encoding.
NUL bytes in the input
!!! question "Questions"
- Why does [`json::parse()`](../api/basic_json/parse.md) silently ignore part of my input?
- Why does a `std::string`/buffer with extra data after the JSON text parse without error, while a similar-looking string with extra text does not?
A '\0' (NUL) byte anywhere in the input is treated the same as the real end of the input, rather than as an ordinary (and, outside of a string, invalid) byte. Everything from that byte onward is silently ignored, without a parse error — including further, otherwise well-formed JSON:
json::parse(std::string("123") + '\0'); // == 123, no error
json::parse(std::string("123") + '\0' + "true"); // == 123, the "true" is silently ignored too
This is different from any other unexpected trailing byte, which does raise parse_error.101:
json::parse("123x"); // throws parse_error.101: unexpected additional data
This falls out of the same convention used when no explicit input length is given at all: json::parse(const char*) already stops at the first NUL byte via strlen(), since a bare pointer has no length of its own. The library applies that same NUL-terminated-C-string convention uniformly, rather than only when a length is genuinely unavailable — so a std::string, iterator range, or container whose content happens to include a NUL byte is affected the same way a raw const char* would be.
If your input may contain a trailing or embedded NUL that is not meant to signal the end of the JSON text — for instance, a fixed-size, zero-padded buffer — trim it yourself before calling parse(), since the library will otherwise silently stop there instead of raising an error:
s.resize(s.find('\0')); // drop everything from the first NUL onward, if any
json::parse(s);
Opt-in strict handling (since version 3.13.0)
Manually trimming every input is easy to forget. If you define JSON_STRICT_NUL_HANDLING to 1 before including the library, a '\0' byte is instead rejected like any other unexpected byte and raises parse_error.101, instead of being treated as end of input:
#define JSON_STRICT_NUL_HANDLING 1
#include <nlohmann/json.hpp>
json::parse(std::string("123") + '\0'); // throws parse_error.101 instead of silently returning 123
This macro defaults to 0 (disabled, preserving the behavior described above) to avoid breaking existing code that may depend on it, even unknowingly; it is planned to become the default in version 4.0.0. See its documentation for details, including how it also affects char arrays such as string literals.
Note that this is unrelated to an unescaped NUL byte occurring inside a quoted JSON string, which is a different, already-invalid case and is correctly rejected either way:
json::parse(std::string("\"") + '\0' + "\""); // throws parse_error.101: control character U+0000 (NUL) must be escaped to \u0000
Wide string handling
!!! question
Why are wide strings (e.g., `std::wstring`) dumped as arrays of numbers?
As described above, the library assumes UTF-8 as encoding. To store a wide string, you need to change the encoding.
!!! example
```cpp
#include <codecvt> // codecvt_utf8
#include <locale> // wstring_convert
// encoding function
std::string to_utf8(std::wstring& wide_string)
{
static std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
return utf8_conv.to_bytes(wide_string);
}
json j;
std::wstring ws = L"車B1234 こんにちは";
j["original"] = ws;
j["encoded"] = to_utf8(ws);
std::cout << j << std::endl;
```
The result is:
```json
{
"encoded": "車B1234 こんにちは",
"original": [36554, 66, 49, 50, 51, 52, 32, 12371, 12435, 12395, 12385, 12399]
}
```
Usage
Thread safety
!!! question
Is `basic_json` thread-safe?
No. basic_json provides no built-in synchronization, the same as std::map or std::vector. Concurrent reads of
the same value from multiple threads are safe, as are concurrent (non-overlapping) accesses to independent json
objects. However, any concurrent write to a json object -- or a concurrent read while another thread writes to the
same object -- is a data race and requires external synchronization (e.g., a std::mutex) by the caller.
Schema validation
!!! question
Does this library support JSON Schema validation?
Not directly, but the companion project json-schema-validator builds JSON Schema (draft 7; draft 4 in its older, now-superseded 1.x releases) validation on top of this library and is a common recommendation for this use case.
Exceptions
Parsing without exceptions
!!! question
Is it possible to indicate a parse error without throwing an exception?
Yes, see Parsing and exceptions.
Key name in exceptions
!!! question
Can I get the key of the object item that caused an exception?
Yes, you can. Please define the symbol JSON_DIAGNOSTICS to get extended diagnostics messages.
Serialization issues
Number precision
!!! question
- It seems that precision is lost when serializing a double.
- Can I change the precision for floating-point serialization?
The library uses std::numeric_limits<number_float_t>::digits10 (15 for IEEE doubles) digits for serialization. This value is sufficient to guarantee roundtripping. If one uses more than this number of digits of precision, then string -> value -> string is not guaranteed to round-trip.
!!! quote "cppreference.com"
The value of `std::numeric_limits<T>::digits10` is the number of base-10 digits that can be represented by the type T without change, that is, any number with this many significant decimal digits can be converted to a value of type T and back to decimal form, without change due to rounding or overflow.
!!! tip
The website https://float.exposed gives a good insight into the internal storage of floating-point numbers.
See this section on the library's number handling for more information.
Serializing untrusted or invalid UTF-8
!!! question "Questions"
- Why does `dump()` throw when I serialize data that came from the network?
- Is CVE-2024-34363 a vulnerability in this library?
Crashes reported against this library that stem from an uncaught
type_error.316 while serializing unvalidated input (e.g.,
CVE-2024-34363) are a usage issue, not a library vulnerability:
dump() throws in its default strict mode because
RFC 8259 requires JSON text to be valid UTF-8.
The recommended pattern is to pass a non-strict error_handler or to handle the
exception:
// replace invalid sequences with U+FFFD instead of throwing
const auto s = j.dump(-1, ' ', false, json::error_handler_t::replace);
Using JSON values with std::format or fmt
!!! question
- Can I use `std::format("{}", j)` on a JSON value?
- Can I use `fmt::format("{}", j)` or `fmt::print("{}", j)` (the [{fmt}](https://github.com/fmtlib/fmt) library) on a JSON value?
std::format works out of the box since version 3.13.0, as long as the standard library provides
<format> (see JSON_HAS_STD_FORMAT); see
std::formatter<basic_json> for details, including the #!cpp "{:#}"
pretty-print spec, indent widths (#!cpp "{:2}"), and custom indent characters (#!cpp "{:.>#}").
For fmt, the library ships format_as, a small customization point
fmt looks for via argument-dependent lookup. It only has an effect on fmt 10.0.0 through 11.0.2 — from
fmt 11.1.0 onwards, fmt no longer picks up a format_as overload that returns a std::string. On such
versions (or any version, if you also want the same #!cpp "{:#}"/width/fill-and-align spec support that
std::formatter<basic_json> has), define your own fmt::formatter specialization; see
format_as for a recipe that mirrors it.
If you get ambiguous-overload errors when passing a JSON value to fmt::format/fmt::print without any
fmt::formatter<json> specialization in scope, that's fmt picking up basic_json's implicit
operator ValueType() conversion operator (see #964 and
#958); disabling it via
JSON_USE_IMPLICIT_CONVERSIONS 0 avoids the ambiguity.
Compilation issues
Android SDK
!!! question
Why does the code not compile with Android SDK?
Since NDK r18 (2018), GCC and the gnustl/stlport C++
libraries have been removed from the Android NDK; Clang and libc++ are now the only compiler and C++ library, and
they support C++11 and later out of the box. With a current NDK, no special configuration is needed to use this
library.
Only very old NDKs (before r18), which defaulted to GCC and gnustl, lacked C++11 library features such as
std::to_string. If you run into this, update to a current NDK.
Missing STL function
!!! question "Questions"
- Why do I get a compilation error `'to_string' is not a member of 'std'` (or similarly, for `strtod` or `strtof`)?
- Why does the code not compile with MinGW or Android SDK?
This is not an issue with the code, but rather with the compiler itself. On Android, use a current NDK (see above). For MinGW, please refer to this site and this discussion for information on how to fix this bug.