Introduction: JSON parsing in C++ has evolved through a range of libraries over the years. Early on, developers relied on libraries like JSONCPP or Boost.PropertyTree (which had JSON support but with limitations) and later widely adopted RapidJSON for its speed and nlohmann/json (“JSON for Modern C++”) for its ease of use. More recently, specialized solutions like simdjson and Glaze have pushed JSON processing performance to new heights using SIMD and compile-time reflection techniques, respectively. In this context, Boost.JSON (introduced in Boost around 2020) emerged to provide a robust, standardized JSON library within the Boost framework – one that balances performance, memory management, and modern C++ API design. Below we explore Boost.JSON’s design goals and features in depth, and compare it to other major libraries (nlohmann/json, simdjson, Glaze) in terms of performance, API ergonomics, compile-time impact, and real-world usage.
C++ has no built-in JSON support, so third-party libraries have filled the gap. RapidJSON (by Tencent) was one of the early high-performance libraries, using C++98/11 with manual memory pools and SIMD optimizations. It offers a DOM and SAX interface but requires careful memory management from the user. nlohmann/json (first released mid-2010s) took a different approach, providing a single-header, STL-like JSON value that is easy and intuitive for developers to use. Its json type acts like a variant/containment of all JSON types and supports idiomatic C++ operations – at the cost of using dynamic allocations for each element and heavy C++ template instantiation.
By the late 2010s, performance demands led to simdjson, which applies novel algorithms with SIMD instructions to achieve parse speeds on the order of gigabytes per second. Simdjson’s authors report it parses JSON 4× faster than RapidJSON and ~25× faster than nlohmann/json on large inputs . Simdjson introduced a two-phase parsing (structural indices first, then actual materialization on access) and an “on-demand” API for lazy parsing, trading some API complexity for speed. Meanwhile, libraries like DAW JSON Link and later Glaze explored compile-time reflection and C++20/23 features to generate JSON parse/serialize code tailored to user-defined types, avoiding runtime overhead. Glaze, for example, directly reads/writes C++ structs from/to JSON without intermediate DOM structures, achieving top-tier speeds in scenarios with known schemas.
Within this landscape, Boost.JSON was created under the Boost umbrella to offer a modern, standardized JSON solution. It had the advantage of learning from earlier libraries: the goal was to combine high performance (comparable to RapidJSON) with a safe and flexible API (like nlohmann/json), robust memory management, and seamless integration with Boost and C++11 and above. Boost.JSON was developed with contributions from experienced Boost developers (Vinnie Falco, Krystian Stasiowski, etc.) and underwent thorough performance tuning and even a security audit  before official release. It focuses on the common use-case of in-memory DOM parsing/serialization (as opposed to code-generation or schema-specific parsing) and strives to be a “vocabulary type” JSON container suitable for public interfaces . In the following sections, we examine Boost.JSON’s design and then compare its characteristics to nlohmann/json, simdjson, and Glaze.
Boost.JSON provides a DOM-style API centered around the boost::json::value container (and related types like json::object and json::array). It is a portable library requiring only C++11, and can be used either as a header-only library or linked as a separately compiled component  . Its design objectives include high performance (with streaming support), robust memory/allocator customization, full JSON standard conformance with optional extensions, and an interface that feels at home for seasoned C++ developers  :
- Performance: The parser and serializer are optimized for speed, employing techniques like SSE2-accelerated string handling and reusable internal buffers. In fact, Boost.JSON’s documentation notes that its parser/serializer meet or exceed the throughput of other top C++ libraries . We will see examples of this in the performance comparison later.
- Streaming (Incremental) I/O: Boost.JSON supports parsing and writing JSON in a piecewise fashion, which is critical for large payloads or network scenarios. Its
stream_parsercan parse JSON in chunks as data becomes available, without needing the entire document in memory upfront . For example, one can feed partial strings tostream_parser::write()repeatedly and then callfinish()to complete parsing, obtaining ajson::valueat the end  . This design allows Boost.JSON to be used in event-driven systems (e.g. reading from sockets) where waiting for the full message isn’t feasible. Similarly, the library provides aserializerclass to incrementally output JSON – you can reset it with ajson::valueand repeatedly callserializer::read()to produce chunks of the serialized text . This streaming algorithm approach means Boost.JSON can handle very large JSON texts or continuous JSON streams with fixed memory usage and without blocking for one giant operation. By contrast, nlohmann/json has to parse the entire input at once (though it offers an input iterator interface, it still constructs the full DOM before returning). RapidJSON’s SAX interface can be used for streaming, but it requires the user to handle the callbacks and state machine manually. Glaze currently does not have a chunked streaming API (it expects complete inputs for direct parsing to structs), and the Glaze author has noted that pause/resume parsing is a feature on the roadmap . Simdjson’s DOM parser expects a complete buffer (with some padding) for its algorithms, so true streaming input is not its forte – although simdjson can efficiently parse very large files in situ, it typically requires the whole buffer to run its SIMD discovery stage. (Simdjson’s “NDJSON” mode can parse newline-delimited JSON records iteratively, and the On-Demand API can parse large documents with lower memory overhead, but feeding random partial chunks still requires manual assembly of complete JSON messages.) - Memory Management and Custom Allocators: A hallmark of Boost.JSON is its extensive support for custom allocation strategies. It uses the C++17 memory_resource model (with Boost.PMR polyfill for C++11/14) to let developers control where JSON DOM allocations come from. All JSON container types (json::object, json::array, and even json::string) can be constructed with a storage_ptr pointing to a user-supplied memory resource . For example, one can use a fixed-size static buffer resource to parse JSON without any dynamic allocation (all allocations will come from the user-provided buffer) . Or one can use a monotonic buffer resource optimized for fast allocation during parsing (and then release all memory in one go when done) . Crucially, Boost.JSON ensures that all child values of a container use the same memory resource as the parent, preventing accidental mixing of allocators . This means if you parse an entire document with a monotonic resource, every object, array, and string inside it also comes from that resource, enabling very cache-friendly memory layouts. The library even supports making the memory resource reference-counted (make_shared_resource) so that a json::value can own the allocator and be safely returned from a function without lifetime issues  . In effect, Boost.JSON’s storage_ptr behaves like a smart allocator handle for the whole JSON tree. For general use, if no custom resource is provided, Boost.JSON uses a default new/delete based allocator optimized for typical usage (allowing expansion and safe mutation). But performance-sensitive code can gain significant benefits by using a pooling allocator. In internal benchmarks, parsing JSON with a monotonic resource and reusing the parser for multiple inputs drastically improves throughput by reusing allocations and avoiding memory fragmentation  . By contrast, nlohmann/json only supports custom allocators in a more static way – you can define basic_json with a different allocator type, but all allocations for a given json instance will use the default or that allocator globally (there’s no built-in support for per-operation custom pools or arenas beyond what C++ allocators provide, and you cannot easily limit the scope of allocations to a user-provided buffer without a lot of boilerplate). RapidJSON offers a similar concept of a user-supplied MemoryPoolAllocator for its Document. In fact, Boost.JSON’s approach is reminiscent of RapidJSON’s in-situ and pool parsing options, but with a cleaner C++ interface. Simdjson currently does not allow custom allocators for its internal buffers – it manages its internal parsing buffers internally (on the heap or via std::aligned_alloc), expecting to parse in place in a provided padded_string. Glaze, interestingly, performs no internal allocations at all during parsing – it reads directly into user variables. This means memory usage is entirely in control of the user’s data structures (and the input source). Glaze can therefore integrate with custom memory regimes naturally (e.g. if your struct has std::vector with a custom allocator, Glaze will use that when resizing the vector). The downside for Glaze is that it doesn’t automatically handle scenarios where the JSON contains more data than your structures account for (unknown keys are skipped or cause errors by design). Overall, Boost.JSON provides a very flexible middle ground: for complex applications (like game engines or embedded systems with multiple memory pools), you can direct JSON allocations to specific regions – for example, allocate large transient JSONs in an “external RAM” pool vs. smaller configs in fast memory, as one Reddit user noted, something that was traditionally only feasible in C++ by using custom allocators with nlohmann/json or similar  . Boost.JSON makes this convenient with storage_ptr at runtime rather than requiring a redefinition of the JSON type.
- Value Container and Data Model: The core type boost::json::value is a variant-like container that can hold any JSON type: null, bool, number (stored as either 64-bit signed, 64-bit unsigned, or double), string, array, or object. The interface will feel familiar to users of nlohmann’s json or other DOM libraries. You can query the type with functions like is_int64(), is_object(), etc., and retrieve values with as_int64(), as_string(), etc., with type-checking. There’s also value::kind() to get an enum of the type. One nice aspect is that constructing or assigning a json::value from C++ types is straightforward – Boost.JSON has converting constructors and assignment from all the basic types, and even initializer-list constructors for building complex structures in code. For example, one can write:
json::value jv = {
{"happy", true},
{"name", "Boost"},
{"nothing", nullptr},
{"answer", { {"everything", 42} }},
{"list", {1, 0, 2}},
{"object", { {"currency", "USD"}, {"value", 42.99} } }
};which will construct a nested JSON object in a single expression . (Under the hood, Boost.JSON ensures that such initializer-list construction is efficient, doing a single allocation for the whole object when possible .) The json::array and json::object types are analogous to std::vector<json::value> and std::unordered_map<std::string, json::value> respectively, but with important optimizations. Objects in Boost.JSON maintain insertion order and offer O(1) average time key lookup  . This is implemented by storing key-value pairs in a contiguous array (for cache locality) and simultaneously maintaining a hash table for the keys internally . Iteration over an object is simply iteration over an array of key_value_pair structs, which can be done with normal pointers (Boost.JSON even provides std::tuple_size/tuple_element specializations so you can use structured bindings on these pairs ). Lookup by key (obj["key"]) computes a hash and finds the index in constant time on average. This design is a hybrid of the approaches taken by other libraries: nlohmann/json by default uses std::map for objects (giving O(log n) lookup and sorted keys), though it can be configured to use std::unordered_map (O(1) but no ordering). RapidJSON stores object members in an array and uses linear search for lookups (O(n)), unless the user manually sorts the keys and binary searches. Boost.JSON’s object therefore provides both fast lookups and stable insertion order – at the cost that inserting new keys can occasionally trigger rehashing and will invalidate pointers/iterators (unlike std::map which has stable references). In practice, this is a reasonable trade-off for JSON usage since one typically builds objects and then mostly queries them. The library clearly documents that inserting into an object invalidates references to values (because of potential reallocation) , so users must be mindful of that (just as they would with an std::vector of values). Arrays in Boost.JSON are simpler: they are essentially dynamic arrays of json::value with amortized constant-time push-back, O(1) index access, etc. They support .resize(), .insert(), and so forth, similar to std::vector semantics. One subtle invariant Boost.JSON maintains is that all values and sub-values created from a given parent share the same memory resource (as mentioned earlier) – so you cannot, for example, take a json::value from one allocator and insert it into an object that uses a different allocator without explicitly copying through a common interface. This prevents accidental use-after-free or mixing memory sources. Overall, Boost.JSON’s data model is designed to cover the full JSON type space with safe, strongly-typed accessors (no implicit unchecked conversions), while still providing conveniences like operator[] for objects and arrays (with unchecked operator[] for non-const access and a bounds-checked .at() as well  ). It is fully UTF-8 compliant; the json::string (which is a typedef for a PMR-enabled string) stores text as UTF-8, and the parser will validate UTF-8 encoding (it even rejects JSON texts with a BOM as non-standard ). Any JSON output produced is also guaranteed to be valid UTF-8.
In terms of JSON standard conformance, Boost.JSON by default only accepts strict JSON (RFC 8259) – meaning no comments, no trailing commas, etc. However, it does provide a parse_options struct to enable a couple of common extensions: you can allow C/C++ style comments and trailing commas if desired  . This is similar to RapidJSON which has flags for comments/trailing commas. Nlohmann/json is stricter by default (it doesn’t parse comments or trailing commas), though it has a relaxed UBJSON mode and other format supports (more on that later). Simdjson is also strictly standard-compliant in parsing. Glaze focuses on JSON proper (no comment support as far as I’m aware, though it can skip unknown struct fields if configured). Boost.JSON currently does not support serializing to or parsing from alternate JSON dialects like CBOR, BSON, MessagePack, etc., whereas nlohmann/json includes built-in support for those binary JSON formats (via additional functions). Glaze, on the other hand, supports not only JSON but also other text and binary formats through the same API – for example, it has BEVE (Binary Efficient Versatile Encoding) and CSV support built-in . Boost.JSON’s philosophy is narrower: focus on JSON text, but do it efficiently and correctly. (This narrower focus likely also helped in its security audit, ensuring a smaller attack surface.)
- Type Conversion and Custom tag_invoke Mechanism: A powerful feature of Boost.JSON is its system for converting between
json::valueand user-defined C++ types. Rather than requiring users to manually traverse JSON to populate structs, Boost.JSON offers two generic functions:value_from(T)andvalue_to<T>(). These perform conversion to JSON (producing ajson::value) and from JSON (reading ajson::value) for arbitrary types via customizable rules. Under the hood, Boost.JSON implements these conversions using a novel customization point design calledtag_invoke . This design was inspired by a proposal (P1895) and provides a form of extensible ADL-based overloading. In short, to make your type T convertible, you can define:
void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, const T& val);
T tag_invoke(const boost::json::value_to_tag<T>&, const boost::json::value& jv);in the associated namespace of T (or as friend functions). Boost.JSON will find these via argument-dependent lookup when you call boost::json::value_from(myT) or boost::json::value_to<MyType>(jv)  . For example, the library itself provides tag_invoke overloads for standard types like std::vector (treated as JSON arrays), std::map<std::string, U> (treated as JSON objects with string keys), std::tuple/std::pair (converted to JSON arrays), etc., by detecting template traits. Users can override or extend this. If no explicit tag_invoke is found, Boost.JSON falls back on a set of type traits to decide how to convert a type (e.g. if a type has std::tuple_size it will attempt tuple-like conversion unless overridden, etc.)  . This two-layer mechanism (traits and tag_invoke) is quite powerful. It allows conversion of even complex user types. For instance, imagine a custom ip_address class with an STL tuple interface and an iterator interface – the user can specialize a trait to ensure Boost.JSON treats it as a 4-tuple of bytes instead of a sequence if needed  , or just directly write a tag_invoke to serialize it to a dotted string if they prefer. The tag_invoke functions have access to a json::value& they should populate (for value_from) or a json::value const& they should read (for value_to), so the implementation can use the normal Boost.JSON API to build or parse the JSON. For example, to convert a std::complex<T> the library’s documentation shows writing a tag_invoke that stores it as a two-element JSON array (real, imag)  . Similarly, one can convert a user struct vec3{x,y,z} to a JSON object {"x": ..., "y": ..., "z": ...} by providing a tag_invoke that assigns those fields on a json::value (which will become an object)  . This approach is conceptually similar to nlohmann/json’s approach of defining to_json(json&, const T&) and from_json(const json&, T&) in the type’s namespace. The main difference is the use of a tag type to disambiguate conversions and possibly allow multiple conversions if desired (though in practice one rarely has more than one way to map a type). For an experienced C++ dev, tag_invoke may take a bit of learning (it’s essentially an idiom to create custom extension points without polluting the namespace with too-common names), but it results in very flexible and non-intrusive customization. You don’t need to modify or friend the Boost.JSON library – just provide free functions. Boost.JSON’s library-provided overloads have no special precedence over user ones, so you can override behavior for your own types completely . In terms of ergonomics, nlohmann/json’s free function approach is a bit simpler to grasp (no tag objects), but it’s functionally equivalent – you write to_json/from_json and the library finds them via ADL. One difference: nlohmann’s adl_serializer allows even third-party types to be customized by specializing a template struct if you can’t put functions in their namespace. Boost.JSON’s design might require putting the tag_invoke in the same namespace as the type or as a friend. In practice, either library allows strong typing instead of using intermediate maps or manually iterating JSON. Simdjson does not offer a built-in generic mapping mechanism; you typically parse with simdjson’s DOM and then manually extract fields to your structures, or use the On-Demand API to directly drive a custom parse (which is manual). This gives maximum control but requires more code. (There are third-party helpers or one could combine simdjson with a reflection library to achieve something similar to Glaze, but that’s not out-of-the-box). Glaze’s entire paradigm is direct mapping – you declare how your struct maps to JSON (through a glz::meta definition listing its members and possibly renaming or ignoring some fields), and then glz::read(json_string, myStruct) will populate it. Glaze uses constexpr and reflection tricks (like C++20 __REFLECT in the future, or currently templates and macros) to generate code that parses the JSON into your struct’s fields, effectively hard-coding the keys and types. This yields extremely fast, zero-allocation conversion for that type. It’s great for “direct parsing”, as one Boost.JSON maintainer noted: if you know the structure, Glaze’s approach is more customizable and faster than first building a generic DOM then converting  . However, the trade-off is that Boost.JSON’s conversion approach works indirectly via the DOM; you always pay the cost to construct a json::value (with all its sub-values) and then convert, whereas Glaze parses straight into your variables without intermediate. In scenarios where you need a general JSON container (for example, you accept arbitrary JSON from users and need to store or forward it without predefined schema), Boost.JSON (or nlohmann, RapidJSON DOM, etc.) is necessary. In scenarios where you have a fixed schema and performance is critical, direct mapping libraries like Glaze or DAW shine. Boost.JSON tries to cover the middle ground by making the DOM <-> struct conversion reasonably convenient so that the “two-step” approach isn’t too painful for developers.
Integration and Deployment: Boost.JSON is part of Boost, which means it inherits Boost’s broad compiler support and stable release cadence. It works on C++11 and later, tested across many compilers (g++5+, clang 3.5+, MSVC 2015+ etc. per Boost’s support matrix) . Notably, Boost.JSON can be used in header-only mode by including the implementation file once (<boost/json/src.hpp>) , which allows one to avoid linking against a separate library. This is similar to how Boost.Asio or Boost.Beast allow an amalgamated header-only usage. If you prefer dynamic or static linking (to potentially reduce compile times or binary size by not inlining everything), you can build Boost.JSON as part of Boost and link to it. The library is relatively lightweight in external dependencies: it uses Boost.Container (for pmr::polymorphic_allocator if std::pmr is not available in C++11 mode) , and Boost.Core/Assert for some internals, plus Boost.ThrowException for optional exception support . If using C++17, you can define BOOST_JSON_STANDALONE to make Boost.JSON use std::string_view and std::pmr::memory_resource directly, dropping the Boost.Container dependency . In any case, the dependency on Boost is not heavy – essentially the library can operate standalone with C++17. This makes it plausible to integrate Boost.JSON in projects that don’t otherwise use Boost, if desired. The Boost Software License (BSL-1.0) is very permissive (similar to MIT), so there are no licensing obstacles. In terms of deployment strategies, Boost.JSON is well-suited for applications already using Boost (it fits naturally with Boost.Beast for networking, Boost.Asio, etc., and uses boost::system::error_code for non-throwing parse errors to align with Boost conventions  ). For example, a network server using Boost.Beast could receive a JSON HTTP body and parse it incrementally with Boost.JSON’s stream_parser to avoid unnecessary buffering. Because Boost.JSON supports exceptions disabled builds (honoring -fno-exceptions via Boost.ThrowException) , it’s also suitable for low-level or performance-critical environments where exceptions are turned off. This isn’t the case with nlohmann/json (which would require modifications to use in a no-exception context). On the other hand, nlohmann’s single-header deployment is extremely convenient for small projects – you drop in one header and you’re done – whereas Boost.JSON’s inclusion of multiple headers or linking might be slightly more involved (though still straightforward with package managers or the Boost distribution). Simdjson is available as a small library (two files for basic usage, as their docs highlight ), and Glaze as header-only via Git or package manager. So in 2025, all these libraries are relatively easy to get and integrate, with Boost.JSON and nlohmann being most established in terms of API stability and community support.
In summary, Boost.JSON’s design is that of a high-performance DOM library with strong memory management and extension hooks. It carefully avoids the pitfalls of earlier solutions by providing streaming, using a cache-friendly object layout, and enabling custom allocators. It also ensures fast compilation times were considered: much of the heavy lifting (parsing, etc.) is implemented in .cpp source that you can choose to link, rather than via massive templates. The library explicitly lists “fast compilation” as a feature . Users coming from nlohmann/json often cite compile-time overhead as an issue – including that header can significantly slow builds when used in many files. Boost.JSON ameliorates this by separating interface and implementation (you include lightweight headers and only one source). Many developers have found that as projects scale, switching from a header-only template JSON (like nlohmann’s) to something like Boost.JSON or simdjson improves build times. One Hacker News commenter noted that using nlohmann/json increased their build times by 50-100% in a large codebase, leading them to prefer alternatives for iterative development . Boost.JSON provides an attractive solution in such cases: it’s more efficient at runtime than nlohmann/json and does not incur as much template instantiation cost on every use.
Performance is often the make-or-break factor for choosing a JSON library, especially as data sizes grow. We will compare how Boost.JSON stacks up against nlohmann/json, simdjson, and Glaze in terms of parsing and serialization speed, as well as memory usage patterns.
Boost.JSON vs. RapidJSON vs. nlohmann/json parsing performance (higher is better) in an internal Boost benchmark parsing a large JSON of unescaped strings. “boost (pool)” uses a monotonic resource and reused parser, “boost” uses default allocator.  
The embedded chart above (from Boost.JSON’s documentation) illustrates one of Boost.JSON’s strengths. In a benchmark parsing a 667 KB JSON consisting largely of strings (strings.json), Boost.JSON with pooling achieved 6.0 GB/s on Clang x64 (SSE2) – roughly 13× faster than RapidJSON and ~25× faster than nlohmann/json in that test. Even without the monotonic resource, Boost.JSON hit ~3.0 GB/s, still an order of magnitude faster than the others for this workload. How is this possible? The key is optimized string handling and reduced overhead. This input has lots of string data without escape characters, effectively making parsing a test of how fast the library can copy text into JSON values. Boost.JSON uses an SSE2 routine (developed with Peter Dimov) to accelerate copying of unescaped sequences . RapidJSON and nlohmann likely use more conventional loops for this, hence the disparity. Essentially, Boost.JSON approaches the memory bandwidth limits in this scenario.
It’s important to note that this is a favorable case for Boost.JSON; not all inputs will see such dramatic speedups. For more mixed JSON (numbers, nested objects, etc.), Boost.JSON and RapidJSON are closer, though Boost.JSON still tends to have an edge due to its parser optimizations and maybe slightly more efficient handling of DOM construction. The Boost team’s benchmark results showed Boost.JSON outperforming or matching RapidJSON in most cases (particularly when using the monotonic_resource + parser reuse technique)  . Nlohmann/json consistently trails far behind in raw parse speed – it’s designed with convenience in mind, not maximum throughput. In fact, simdjson’s authors used nlohmann/json as a baseline of “slow” performance, showing simdjson could be 25 times faster on large files . This aligns with general observations: nlohmann/json may parse on the order of a few tens of MB/s, whereas RapidJSON might parse a few hundred MB/s, Boost.JSON can parse several hundred MB/s to low GB/s, and simdjson can parse multiple GB/s on modern CPUs. Simdjson, in particular, uses parallel algorithms on 128-byte chunks of input and heavy use of SIMD (up to AVX-512 where available) to detect JSON structure. On typical data, simdjson achieves 2–4 GB/s parse rates, which is roughly 2–5× faster than Boost.JSON on the same input (exact ratios vary on data pattern and CPU). For example, simdjson’s README reports ~4× faster than RapidJSON ; since Boost.JSON in some tests is ~1.5×–2× RapidJSON’s speed, simdjson would still be ~2× above Boost.JSON for large documents. The difference may be less if the JSON is very simple (where Boost’s simpler loop can keep up with SIMD) or if the input is so small that parsing isn’t the bottleneck.
One area where simdjson excels is large arrays of numbers or objects. Its stage 1 quickly identifies tokens and stage 2 can use CPU branch prediction efficiently. Boost.JSON (and RapidJSON) use a more traditional state-machine parse which can be harder to optimize at the instruction level. However, Boost.JSON mitigates this by reusing internal buffers: when parsing multiple JSON documents in succession (like in a server handling many JSON messages), you can reuse a single parser instance so that its internal scratch space (for number parsing, etc.) doesn’t get reallocated each time . This improved performance in Boost’s tests since allocation costs for parse were amortized. Nlohmann/json and RapidJSON don’t have a comparable parser object reuse (RapidJSON’s Document cannot easily be reused without freeing and reallocating its internal stack; nlohmann has no persistent parse state at all) .
Memory usage is another critical aspect. All DOM-style libraries (Boost.JSON, RapidJSON DOM, nlohmann/json) will use more memory than the raw JSON text, because they allocate separate objects for the parsed representation. Nlohmann/json is relatively heavy: every value is a std::variant holding either a pointer to a std::map (object), std::vector (array), std::string, etc. The tree of allocations includes one std::string allocation for every JSON string, one node for every object key/value (map node), etc. It also stores numeric values as types like long long or long double by default, which are bigger than the minimal needed in some cases. In contrast, RapidJSON and Boost.JSON can use a single contiguous memory pool for an entire document. RapidJSON’s Document with a MemoryPoolAllocator will allocate one big block and carve it up for values and strings, which is very efficient (minimal overhead per value, just pointer arithmetic). Boost.JSON with a monotonic_resource does essentially the same – all values and strings end up in a contiguous block (or set of blocks) with constant-time allocation. This not only saves memory overhead from allocator bookkeeping, it also improves cache locality. If you parse a JSON and then iterate through all values, they are likely packed tightly in memory when using a monotonic resource. With nlohmann/json, the values may be all over the heap, causing more cache misses.
Simdjson’s DOM takes a unique approach: it does not allocate one object per JSON value. Instead, during parsing it produces a tape (array) of 64-bit tokens representing the JSON structure (with a specific encoding to indicate object/array boundaries and scalar values). Strings and numbers are stored within this tape (numbers are stored as 64-bit or double in place; strings are copied into an adjacent contiguous string buffer and referenced by pointer/length). The result is that simdjson’s memory overhead is extremely low – roughly the size of the JSON text plus a small percentage for the indexes. The catch is that simdjson’s DOM element references point into this tape and the original string buffer. Thus, the original JSON text (or at least the string data) must remain in memory if you want to access string values. In practice, simdjson often operates on an input that it copies into a simdjson::padded_string (which includes some padding bytes required by SIMD). That copy is one allocation (size = input size + padding). Then it allocates the tape (size = 2 × number_of_tokens, which is about 16 bytes per value on average). This is still very compact compared to the numerous small allocations a naive DOM might do. Boost.JSON’s memory model (with pooling) will be a bit less compact – it needs to allocate full json::value objects and store data like key strings separately. But it’s much better than, say, nlohmann’s default, because of pooling and because its object is just an array of pairs rather than a tree of red-black tree nodes.
For very large JSON documents, simdjson is typically the go-to choice because of both speed and memory. Boost.JSON will require roughly 1.3× to 2× the memory of the text (depending on how many numbers vs. strings etc.), and it will take multiple passes of allocation if using default new/delete for each string. Simdjson will be closer to 1× text size and uses fewer allocations. That said, Boost.JSON can handle fairly large data and has been used in systems where its speed is sufficient. If you have extremely deep nesting or need to parse without recursion, note that Boost.JSON’s parser is iterative (uses its own stack buffers for managing nesting up to a default depth, I believe) – it won’t blow the C++ call stack, which is good for malicious or very nested inputs.
Glaze’s performance is exceptional when mapping to structs. Benchmarks by Glaze’s author and others show that for parsing directly into C++ structs, Glaze can outperform RapidJSON, and even simdjson in some scenarios (especially when the JSON has a complex object structure). The reason is that Glaze generates very efficient code tailored to the expected schema: it doesn’t need to store any metadata or perform any general lookup – it knows that, for example, it should expect an object with keys “x”,“y”,“z” and directly map those to struct fields. This eliminates the intermediate representation and the cost of storing all keys/values in a general container. A Reddit discussion noted that “for reading/writing directly from C++ structs, Glaze should be faster… If you want lazy partial parsing then simdjson is the way to go, but if you’re populating C++ data structures, simdjson has to do extra work that plays against its SIMD benefits.”  . In other words, simdjson shines when you only need parts of the JSON or when you want to iterate through a DOM; but if you ultimately need every field in a C++ struct, its extra stage (and conversions from its tape to actual C++ types) can make it less efficient than directly writing into those fields as Glaze does. Indeed, Boost.JSON’s maintainer acknowledged that Glaze outperforms Boost.JSON in direct parsing and serialization to user types  – unsurprising, as Boost.JSON goes through the DOM. However, the DOM approach is more flexible (you can examine or modify the JSON structure arbitrarily, whereas Glaze stops at your struct’s definition).
In terms of serialization (writing JSON), the patterns are similar. Nlohmann/json is convenient but not the fastest – it constructs an std::ostringstream or similar under the hood and formats each element, which incurs some locale-dependent formatting for numbers unless you tweak it. RapidJSON and Boost.JSON use efficient integer-to-string and float-to-string conversions (Boost.JSON likely uses to_chars or a custom routine). Boost.JSON’s serializer can write to a user-provided buffer in chunks, which can reduce memory overhead when generating large JSON (you don’t have to allocate one giant string). Simdjson’s focus is parsing, but it does provide a minify() function to write out JSON (essentially the inverse of parse, printing without whitespace). Simdjson’s performance in writing is very high for minification (they claim 6 GB/s for converting JSON DOM to text without extra spaces) , partly because their internal representation is easy to traverse sequentially. However, simdjson does not support “pretty-printing” or customized formatting – it’s mainly a fast dumper. Boost.JSON can pretty-print via streaming to std::ostream (since it overloads operator<< for json::value to produce valid JSON text ), although pretty printing (with indentation) you would handle manually. Glaze again leverages compile-time knowledge to serialize very quickly: it formats known types field by field, which often approaches the performance of std::to_chars calls in tight loops, with minimal runtime overhead.
To summarize performance: Boost.JSON offers top-tier performance among DOM parsers, often beating RapidJSON in both parsing and writing  , and utterly outperforming nlohmann/json (which is ~10–20× slower on large inputs ). It is not as fast as simdjson for raw parsing throughput on large data – simdjson remains the champion for maximal parse speed, with reported figures like 2–3 GB/s or more on realistic JSON (boost’s own highest numbers, ~6 GB/s, were in a best-case scenario for their algorithm; simdjson can hit ~3–4 GB/s on more varied JSON, and up to 13 GB/s for validating UTF-8 only ). Glaze can surpass Boost.JSON when working with user types because it sidesteps the DOM completely, but it targets a different use-case (structured data binding). If your use-case involves manipulating JSON dynamically (e.g., you’re writing a JSON editor, or a REST API forwarding arbitrary JSON fields), Boost.JSON provides a very balanced solution – fast and memory-efficient enough, while still giving you full random access to the JSON tree.
From a developer’s perspective, each library offers a distinct style of working with JSON data. Ease-of-use can be as important as raw speed, especially when JSON is used in configurations, I/O, or one-off tasks in a larger program. Here we compare the API design and usability of Boost.JSON with nlohmann/json, simdjson, and Glaze, highlighting the “feel” of each in real-world scenarios.
Boost.JSON API: Users of Boost.JSON interact primarily with the json::value and its contained types (json::object, json::array, json::string, etc.). The API is type-safe and fairly rich. For example, to access an object property you might do:
json::value jv = parse(someText);
if(jv.is_object()) {
json::object& obj = jv.as_object();
if(obj.contains("key")) {
int x = obj["key"].as_int64();
...
}
}This is a bit more verbose than nlohmann/json, where one might write int x = jv["key"].get<int>(); directly. In Boost.JSON, calling operator[] on a json::object returns a json::value& (inserting a null if the key wasn’t present for non-const objects) , so you need to then convert that to a concrete type with .as_int64() or .to_number<T>(). The design forces an explicit check or conversion step, which can prevent mistakes (e.g., it won’t automatically cast a number to a string or vice versa – you have to call the appropriate function). Seasoned developers might appreciate that explicitness. There are convenience features: you can iterate objects with range-based for (for (auto& [key, val] : obj) thanks to the structured binding support ), and iterate arrays as (for (json::value& elem : arr)). There’s also support for JSON Pointer queries: json::value* pv = json::pointer("/foo/0/bar").get(jv) will navigate through a JSON structure (Boost.JSON implements the standard JSON Pointer syntax) . This is similar to nlohmann/json’s at(pointer) support.
Writing JSON is straightforward: call boost::json::serialize(jv) to get a std::string of the minified JSON text . Or use the operator<< overload to stream it. You can control some formatting (like whether to use a certain float precision or to omit null members) through manual code or by manipulating the json::value before serialization (Glaze offers more built-in options here as we’ll see). Overall, Boost.JSON’s API will feel familiar to anyone used to STL containers and variant types – it doesn’t hide the fact that JSON is a tree with different types at nodes, but it provides tools to traverse and manipulate that tree safely.
One notable convenience is Boost.JSON’s handling of error reporting. The parse() function by default will throw a boost::json::system_error on parse failure (with a message and location). Alternatively, there’s an overload that takes a boost::system::error_code reference and will fill it instead of throwing . This dual approach (throwing vs error_code) aligns with common Boost patterns and lets the user choose what’s more appropriate. Nlohmann/json’s parse() will throw exceptions (e.g. parse_error) on invalid JSON; if you want a no-throw version, you have to use exceptions disabled at compile-time or catch exceptions. Simdjson doesn’t throw by default – its functions return an error code simdjson::error_code that you must check. This makes simdjson very safe for use in code where exceptions are off or not desired, but it puts more onus on the developer to handle errors on every step (though C++17’s [[nodiscard]] helps remind you). Glaze in C++23 uses std::expected for parse results (as noted by a user: it requires C++23 largely for std::expected and perhaps for the fancy type traits) . This means a call like glz::read(json, myStruct) returns an expected<void, glaze_error> that you check. That’s a very clean design in modern C++, but it does require C++23. In earlier C++20, I believe Glaze used a custom expected-like or error handling mechanism. In any case, all these libraries provide some way to handle errors; Boost and simdjson favor the explicit error-code path (with Boost also offering exceptions optionally), while nlohmann by default uses exceptions (with descriptive what() messages), and Glaze leans on expected/optional.
Nlohmann/json API: The nlohmann/json library became popular largely due to its extremely ergonomic interface. The json class is essentially variant-like but overloaded to behave like what you “expect.” For example, it overrides operator[] for different scenarios: if you do j["foo"] on a JSON object j, it returns a reference to the element (inserting a null if missing). If you call the const version j.at("foo"), it will throw if missing. It also overloads operator[] for array indices. You can even do things like j["newKey"] = 42; to set a value (which will implicitly convert 42 to a JSON number). There are implicit conversions to basic types in some cases (e.g. int x = j; if j is a number), although using the get<T>() or get_to(T&) methods is safer and recommended. Nlohmann’s design prioritizes convenience and natural syntax – many C++ developers liken it to scripting languages usage of JSON. It also has nice additions like json::dump(indent) for pretty-printing with indentation, and json::value("key", default_value) to fetch a value or return a default if not present (avoiding exceptions). Its integration with the STL is also polished: it provides begin()/end() iterators for arrays and objects (object iterators yield std::pair<const std::string, json> like a map). It even supports algorithms directly on JSON arrays as if they were STL containers.
For custom types, as discussed, nlohmann/json uses adl_serializer. In practice, you just write a to_json(json& j, const T& val) and from_json(const json& j, T& val) and the library will find them (via ADL, since it calls to_json in the same namespace as T). This is straightforward and many developers find it easy to implement. The library comes with built-in support for some STL types (like std::chrono types, Eigen matrices via an extension, etc.), and you can extend it. One potential pitfall is that if you call get() on a json and it doesn’t know how to convert to T (no specialization or conversion), you get a compile-time error. That’s generally fine – it forces you to define the conversion if needed.
simdjson API: Simdjson offers two APIs: the DOM API (simdjson::dom) and the On-Demand API (simdjson::ondemand). The DOM API is more comparable to Boost/nlohmann in that you get a generic tree (though simdjson’s “tree” is not exactly a tree of objects, but a view on the internal tape). For example, using the DOM API, you might do:
dom::parser parser;
dom::element doc = parser.parse(json_text); // parse entire JSON
std::string_view name = doc["person"]["name"]; // access fields with operator[]
uint64_t age = doc["person"]["age"]; // implicit conversion via cast to uint64_t
for (dom::element friend : doc["person"]["friends"]) {
std::string_view fname = friend["fname"];
...
}This is fairly high-level. Simdjson overloads operator[] on dom::object to return a dom::element for the given key (this actually does a search – internally likely linear search through object fields). It also overloads conversion operators: you can assign a dom::element to a uint64_t or std::string_view directly, which will throw an exception if the type doesn’t match or if something goes wrong  . Alternatively, you can call methods like .get<T>(T&) which return an error code instead of throwing. This design gives flexibility: you can choose an exception style or an error-code style. It is somewhat reminiscent of how JSON is handled in dynamically-typed languages – you try to fetch a field and cast to the expected type. However, it lacks some of the niceties of nlohmann or Boost; for instance, as noted in their docs, array[0] is not allowed (because it would suggest O(1) random access, which simdjson doesn’t want to promise if it hasn’t materialized the entire array) . Instead, you iterate or use .at(index) which actually just iterates under the hood. This is a minor difference, but it highlights that simdjson’s DOM is optimized for sequential access.
The On-Demand API is a different beast: it’s used like a streaming parser. You iterate through tokens as they appear, without storing everything. For example, you create an ondemand::parser, then do auto doc = parser.iterate(json_text). If the JSON is an object, you might iterate fields with for (auto field : doc.get_object()), and for each field you can get the key and value. Once you move past a field, you cannot go back (the name “on-demand” is a bit misleading; it’s more like a forward-only streaming iterator that parses as you access). This API is very efficient (no unnecessary work is done for parts of the JSON you don’t touch), but it requires a careful, rule-based approach. You also cannot easily random-access deep into the structure without manually code to skip through.
Simdjson’s APIs are a bit lower-level than Boost or nlohmann – they assume you have performance in mind and are willing to handle errors at each step or design your code flow to match the JSON structure. For an experienced developer dealing with huge data, this is acceptable. But for a beginner or a quick script-like usage, simdjson is more cumbersome. That’s one reason many might prototype with nlohmann/json and only switch to simdjson if needed.
Glaze API: Glaze’s API is quite different because it is centered on compile-time reflection of C++ structs. To use Glaze, you define or include a meta-definition for your types. For example:
struct Person { std::string name; int age; };
template<> struct glz::meta<Person> {
static constexpr auto value() {
return object("name", &Person::name, "age", &Person::age);
}
};This tells Glaze that Person should be read/written as a JSON object with “name” and “age” keys mapping to those members. You can even transform or rename fields (e.g., use [](){ return ... } lambdas or custom getter/setter functions in place of direct member pointers), which is very powerful for customization  . After this setup, using Glaze is simple:
Person p;
auto err = glz::read(json_string, p);This will parse the JSON and fill p. If JSON is missing fields or has extra fields, behavior depends on options (you can configure whether to error on unknown keys or missing keys, etc., with Glaze’s flags) . Writing is similarly: std::string out = glz::write(p).
The “developer experience” of Glaze is great if you treat JSON as a serialization format for your C++ data structures. There’s no need to manually traverse a DOM or worry about types at runtime – everything is checked at compile time. If a JSON field is wrong (type mismatch), Glaze will report an error during parsing. If an expected field is missing and you set it to error out on that, you get an error result you handle.
However, Glaze is not convenient for scenarios where the JSON schema is not known in advance or not fixed. For instance, if you need to accept an arbitrary JSON from a user and then maybe forward it or partially process it, Glaze doesn’t provide an easy DOM or variant type to hold it (it does have some generic constructs like you could reflect a std::map<std::string, glz::raw_json> or something, but that defeats the purpose). For those cases, Boost.JSON or nlohmann or simdjson are necessary.
Glaze also requires modern C++ (the latest version requiring C++23). This means you need a fairly up-to-date compiler. In return, you get modern features: the use of std::expected for error handling, constexpr for meta definitions, and concepts to constrain what is serializable. One user noted “we are stuck at C++14 at work, so unfortunately can’t use Glaze yet”  – that will be a common barrier in some industry projects, whereas Boost.JSON and nlohmann work on C++11.
- Boost.JSON: Good balance of safety and convenience. A bit verbose when extracting values (explicit type queries), but straightforward. Integrates with Boost’s style (error_code or exceptions). Requires managing memory resources only if you want to optimize – otherwise, you can ignore that part and use the default. The learning curve is moderate for anyone familiar with JSON and C++ containers.
- nlohmann/json: Easiest to quickly get something done. Very high-level syntax (almost like using a Python dictionary). Ideal for small tasks, configs, or prototyping. The downside is that you might accidentally write inefficient code (e.g., repeatedly doing
j["foo"]in a loop, which in nlohmann performs a lookup each time in a map; Boost.JSON’soperator[]would do a hash lookup each time too – better to extract once outside a loop in both cases). But for many, the convenience outweighs these concerns until performance or memory becomes an issue. - simdjson: Provides a DOM interface but one that feels a bit less idiomatic (with those casts for types). The On-Demand interface requires careful sequential logic but yields great performance. It’s more verbose to use safely – you’ll be checking error codes often or wrapping in try/catch if using exceptions. It’s targeted at experienced developers who don’t mind doing that work to get speed.
- Glaze: In terms of code you have to write at runtime, it’s minimal – just one function call to serialize/deserialize. But you must set up the compile-time meta mapping. That’s an upfront cost, though tools could generate it or reflect it (if C++ gets static reflection in the future, Glaze could potentially auto-map struct fields without even needing macros). Once set up, it’s arguably the cleanest usage for typed data: you simply deal with your C++ structs directly. In that sense, it avoids a whole class of runtime errors (no forgetting to check a JSON field’s existence – if it’s not in the JSON, you’ll know from the error or default, etc.). The developer experience is excellent for schemaful JSON, and not applicable for schema-less JSON.
As mentioned, compile-time impact and binary size can be significant factors when choosing a library, especially in large codebases or resource-constrained builds.
- Boost.JSON was explicitly designed to be modular and friendly to compile times. Because you can include
<boost/json.hpp>in many files (which brings in declarations and small inline functions) and then include<boost/json/src.hpp>in one implementation file, you minimize repetitive parsing of heavy implementation code by the compiler . The parsing and serialization algorithms are implemented in normal (non-templated) C++ functions inside the Boost.JSON library. This means your compile time doesn’t blow up with template instantiations for each different JSON type or input – it’s one set of compiled functions. In practice, developers have found Boost.JSON to compile much faster in large projects compared to nlohmann/json. One anecdote on Hacker News cited avoiding nlohmann due to compile time, switching to RapidJSON or cJSON for that reason . Boost.JSON would similarly avoid those issues. The downside of Boost’s approach is that if you do use it header-only (by includingsrc.hppeverywhere or in multiple modules), you could accidentally get ODR violations or multiple definitions – but the docs clearly instruct to include it in exactly one source file. Most package managers (vcpkg, Conan, etc.) for Boost will build Boost.JSON as a static/dynamic lib, so you’d just link it. - nlohmann/json being header-only means it’s very easy to drop in, but the compiler will parse that header in every file that includes it. Heavy use (especially if you include it in a precompiled header, it can slow down IntelliSense or compile). Template instantiations of things like
json::parseorbasic_json<...>member functions might get repeated (though modern compilers can deduplicate via COMDAT folding, so the binary bloat isn’t as bad as it used to be, but compile time is still impacted). Nlohmann’s library uses a lot of C++ templates internally (for implementing iterators, implicit conversions, etc.), and that adds to build time. If your project is small, you won’t notice; if it’s huge, you will. Some users mitigate this by only including nlohmann/json in cpp files rather than headers, to limit exposure. - Simdjson is fairly light on templates. It uses some templates to dispatch between SIMD implementations (e.g., a template or ifdefs for SSE4.2 vs AVX2 vs Neon), but those are mostly confined to its implementation. The public API is not template-heavy (except that DOM elements are templated on value type when you call
.get<T>()). Simdjson’s distribution includes a single-header mode (one.hand one.cpp)  which you compile once – similar to Boost’s approach, this means you don’t have to include all the code in every file. So compile-time for simdjson usage is generally very good. In fact, people often comment that simdjson is not only fast at runtime, but also adds little compile overhead (especially compared to template DOM libraries). In terms of binary size, simdjson’s code is fairly compact, but it does include some large lookup tables and such for UTF-8 and number parsing. Still, its focus on performance does not blow up code size unnecessarily; it’s used even in Node.js where binary size matters. - Glaze is header-only and template-heavy by nature. It uses C++20 concepts and
constexprlogic to generate parsers/formatters for your types. This inevitably means long compile times for complex types or if you reflect a lot of types. Each type’s serialization code is generated in each translation unit that includes the meta definition (unless you isolate them). However, one can mitigate that by explicitly instantiating or using modules. If you have, say, a dozen large structs to serialize, you might implement a.cppthat includes theirglz::metadefinitions and then explicitly instantiateglz::read/writefor those types, so that the heavy work is done once. Glaze is a newer library, so compile-time optimizations may still be evolving. But as with many template libraries, the cost is paid at compile time rather than runtime. This is a trade-off some are willing to make (especially if final executable speed is paramount and build time is acceptable). Projects using Glaze should use recent compiler versions for best compile times (concepts andconstexprimprovements in GCC/Clang have made things faster in C++20 vs C++17 days).
In terms of deployability: all four libraries are permissively licensed. Nlohmann is MIT, Boost.JSON is BSL-1.0, simdjson is MIT/Apache2 dual, Glaze is MIT. These are all business-friendly. Boost.JSON, being part of Boost, might have an easier path in organizations that already allow Boost (some companies have Boost pre-approved, etc.). Conversely, some companies hesitate to add Boost if they aren’t using it, due to Boost’s large footprint. But adding just one Boost library isn’t too onerous – you can link only Boost.JSON and its needed Boost.System/Container, you don’t need the entire Boost suite. Simdjson and Glaze being standalone libraries can be added via package managers readily.
For embedded or constrained environments, Boost.JSON has a specific note that it works well on embedded devices, using smaller default internal buffers on non-Intel CPUs (256 bytes vs 4KB on x86) to save stack space, adjustable via a macro . It also uses Boost.Endian to handle endianness issues gracefully . Nlohmann/json can be used on embedded (people have used it on ARM microcontrollers even), but one has to be mindful of memory and exceptions. Simdjson requires a 64-bit system; it doesn’t support 32-bit (due to its reliance on 64-bit SIMD and pointers). That means you can’t use simdjson on, say, a 32-bit MCU or older system. Glaze could potentially be used on embedded systems if the compiler supports C++20/23 (which is a big if – many embedded toolchains lag behind, making Glaze less viable there in the near term).
Interoperability: If your application might need to work with other data formats later, nlohmann’s support for CBOR/MsgPack might be a plus (you can reuse your JSON serialization code for those formats by just calling json::to_cbor(j) etc.). Glaze also aims to support multiple formats with the same mapping (JSON, binary, etc.)  . Boost.JSON is solely JSON (if you needed MessagePack, you’d use a different lib or convert Boost.JSON value to something else). Simdjson similarly focuses only on JSON text input.
Finally, community and maintenance: Boost.JSON is supported by the Boost community and C++ Alliance, meaning it has multiple eyes on it and a formal review process behind it. It’s relatively young, but being in Boost implies long-term stability and versioning. Nlohmann/json is primarily maintained by Niels Lohmann with community contributions; it’s very stable (version 3.x is widely used) and well-tested, but one person’s project (though with 300+ contributors on GitHub over time). Simdjson is actively maintained by academics and industry folks (Daniel Lemire and team), and it’s used in big projects (for example, it’s used in Node.js as an experimental JSON backend, and in databases like ClickHouse ). Glaze is quite new (as of 2023/2024) and largely driven by its author (who is active on forums and improving it). It’s rapidly evolving, which is exciting but could mean potential changes or less maturity in some edge cases. An experienced developer might choose Boost.JSON or simdjson for critical production use today if maximum stability is needed, whereas Glaze might be chosen for newer projects where C++23 is available and performance requirements justify it.
Having “been there, done that” with these libraries, one realizes that each library excels in different scenarios:
- Boost.JSON in Production: Boost.JSON is a great choice when you need a reliable JSON DOM with top performance and you’re already in a C++11 (or newer) environment. For instance, if you’re building a high-throughput network service (maybe using Boost.Beast for HTTP/WebSockets) that processes JSON messages, Boost.JSON lets you parse incrementally from the network buffer, use a custom allocator tied to each connection or request for efficient cleanup, and produce responses without a lot of overhead. Its integration with Boost.Asio/Beast (using error_code etc.) means you can avoid exceptions in latency-sensitive code. In such a scenario, nlohmann/json would likely be too slow and too memory-heavy (and its lack of streaming would force buffering entire messages), while simdjson could be an alternative for just parsing speed – but simdjson wouldn’t give you a modifiable DOM to easily adjust values or reserialize (simdjson’s DOM is more read-only). Boost.JSON allows mutation: you can build a JSON value, add/remove keys, change values, then serialize it. That’s useful for servers that might need to inject or update fields. Its constant-time object lookup means even reasonably large JSON objects (tens or hundreds of keys) can be queried quickly without linear search. Another example: an embedded system (say running on an ARM Cortex-A with Linux) where memory is limited and you need to parse JSON configuration or messages. Boost.JSON’s ability to use a static buffer means you can parse without invoking the global new/delete at all (important in environments with custom memory management). A user on Hacker News pointed out using nlohmann/json on ESP32 (which is quite constrained) specifically because it could use a custom allocator to target external RAM . Boost.JSON would let you do the same, likely with less fuss and better performance. It’s also compiled with exceptions off if needed, which is sometimes required in embedded builds.
- nlohmann/json in Real World: Despite its slowness, nlohmann’s library is extremely popular in open-source. It’s found in many projects as the go-to JSON for its simplicity. In a desktop application or tool where JSON parsing is not the bottleneck (perhaps you parse a config once on startup, or occasionally read small JSON files), nlohmann/json is perfectly fine. Its API will let developers implement features quickly and with less code. For example, a game developer might use nlohmann/json to parse a configuration file or level data because it’s one header drop-in and they can immediately treat the JSON like a
std::map. If performance issues arise (e.g., loading times too slow), they might consider switching to something like Boost.JSON or RapidJSON later. There’s also the factor of familiarity – many C++ devs know nlohmann/json’s API by heart (the README is basically a cheat sheet for JSON manipulation). That lowers maintenance cost for some teams. But one should be aware of its limitations: I have seen cases where using nlohmann/json in a tight loop (thousands of small JSON parses) caused surprising slowdowns and memory spikes, which led to either optimizing usage or migrating to a faster library. - Simdjson in Practice: Simdjson really shines when dealing with huge volumes of JSON data – for example, log processing, data ingestion pipelines, or big data systems. If you need to parse gigabytes of JSON per second (log processing at Facebook, etc.), simdjson is the proven solution. It’s also great if you need to parse a large JSON and query only a small part of it. For instance, if you have a 100 MB JSON and you just need a few specific fields out of it, simdjson’s On-Demand API can extract those in a single pass without building a full DOM, making it extremely efficient. This would be significantly faster than Boost.JSON which would build the entire 100 MB into a DOM (taking memory and time) before you pick out what you need. A concrete scenario: imagine a service that receives JSON records (maybe from Kafka) that are 1 MB each, and you need to filter them by some field and do minimal processing – simdjson could parse and let you check the field in one pass with minimal overhead. Trying the same with a DOM library would allocate lots of objects and strain memory bandwidth more. That said, simdjson’s complexity means it might not always be the first choice unless you know you need it. Many production systems start with an easier library and only consider simdjson if profiles show JSON parse dominating CPU. But its adoption is growing (it’s being used in Node.js core, in various database projects, etc., which speaks to its reliability as well).
- Glaze in Practice: Glaze is newer, but it represents a trend towards compile-time optimized serialization. It is ideal for scenarios where you control both ends of the JSON format, and you care about performance and type safety. For example, for an RPC system or microservice communication where both client and server are C++ and share data models, using Glaze could eliminate overhead. You’d essentially be serializing directly from C++ objects on one side to JSON text, then directly into C++ objects on the other side. This is almost like having a binary protocol in terms of efficiency, but human-readable JSON on the wire (which can help with debugging). Another use-case is configuration or save files in applications: if you have well-defined structs for your config, Glaze can load them extremely fast from JSON, faster than generic DOM parsing. And since it can skip unknown fields or provide defaults for missing ones (with the right options), it’s robust to version changes too  . However, Glaze requires modern compilers and may not be as battle-tested for all edge cases (e.g., extreme nesting depths, very large numbers, etc.) as the older libraries. A seasoned developer would weigh that. In a scenario where maximum performance is needed and environment is controlled (say, high-frequency trading system exchanging JSON messages internally, compiled with latest C++), Glaze could be a secret weapon, giving near binary performance with JSON. In more heterogeneous environments or where JSON schema can vary, it’s not the tool.
Interplay between libraries: It’s not unheard of to use multiple libraries in one project for different purposes. For example, one might use Boost.JSON for general DOM handling of dynamic JSON, but use Glaze for a specific module that serializes known structures (like using Glaze to rapidly parse a large JSON into a struct, then maybe hand it to Boost.JSON if it needs to be manipulated further dynamically). Or use simdjson to pre-filter or stream parse large inputs, then construct a Boost.JSON value for the portion that needs to be modified or output. These combinations can work but add complexity – often it’s simpler to stick to one good library unless you have very distinct needs in different parts of the system.
In conclusion, Boost.JSON hits a sweet spot for many C++ use cases: it’s fast, memory-efficient, and offers a modern C++ interface with the extensibility and safety features that experienced developers appreciate. Compared to nlohmann/json, Boost.JSON requires a bit more explicit handling (no implicit magic conversions), but in return you get much better performance and the ability to tune memory usage. Compared to simdjson, Boost.JSON is easier to use for general scenarios and allows full modification of JSON data; it sacrifices the last word in performance, but is still extremely fast and often more than sufficient (and it uniquely offers true streaming parse of indefinite inputs, which simdjson doesn’t in the same way ). And compared to Glaze, Boost.JSON is usable in older C++ standards and for arbitrary JSON structures, whereas Glaze targets a different niche of known-structure, ultra-high-performance serialization (with Boost.JSON’s tag_invoke conversions helping to bridge some of that gap by making struct<->JSON transformation easier  , albeit with the intermediate DOM cost).
- Boost.JSON official documentation and performance notes     
- Discussion on JSON library performance and usage (Reddit, Hacker News)   
- Simdjson project README and documentation  
- Glaze library announcement and discussions (Reddit)  
- Benchmarks from Boost.JSON vs RapidJSON vs nlohmann