mirror of
https://github.com/nlohmann/json.git
synced 2026-11-09 09:15:38 +00:00
docs: new design for the documentation site (#5934)
* docs: new design for the documentation site Apply the brand to the Material for MkDocs site: Inter and JetBrains Mono served by the site (upright and real italic), brand colours, blue header band with the syntax pattern, wordmark in the header, new home page (hero, cards, animated feature tour), 404 page, polish for tabs, search, footer, focus rings, tables and code blocks, and theme features (code copy, previous/next footer, TOC follow, search suggestions). Draw the number railroad diagram, the parser callback events and the iterator illustrations as inline SVGs that follow the light and dark scheme. The generator scripts are in docs/mkdocs/scripts/figures; the feature slideshow (json-light.svg, json-dark.svg, replacing json.gif on the home page) is built by docs/mkdocs/scripts/slideshow. Decision nodes in five flowcharts become hexagons, which need far less room. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: add figures, table captions and a macro chooser - Figures with numbered captions: SAX stepper, json_view layout and edit stepper, exception map, JSON pointer and patch walkers, QA pipeline, scaling and binary size charts, roadmap lanes, release timeline, integration decision tree and namespace builder - Table captions (nj-table-caption) on 9 pages - Compile times: modules and PCH charts, filesystem section, re-measured Figure 1, links between features/modules.md and compile_times.md - Interactive macro chooser for features/arbitrary_types.md - Link check: exclude URLs that answer the plugin with 400/401/419 or are dead (customers.md) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: macro chooser generates adl_serializer for non-default-constructible types Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: cross-link concept pages and API pages in See also sections Add See also links in both directions between the concept pages (features, home, integration) and the API pages, write API links in code font, give every entry a comment after a dash, and show the callback event figure on the parser callbacks page. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: address review comments on the new design - Home: add a "Latest release" button - Sponsors: update the list and group it by tier - Features: list all feature pages on the index page - Releases: add highlights per release, link the 4.0 roadmap, add the first commit to the timeline - Exceptions: prefix all example messages with the exception name - API: no code font for format names in the See also links of the to_/from_ pages - Merge Patch: show the document before, the patch, and the result - Replace "--" with typographic dashes, mark external links with an icon, center Mermaid diagrams Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -6,7 +6,7 @@ first. If a hard real-time budget or the last percent of throughput matters more
|
||||
[faster, more specialized library](https://github.com/miloyip/nativejson-benchmark#parsing-time) may be a better fit.
|
||||
|
||||
That said, how you use this library still makes a measurable difference. This page collects practical, code-verified
|
||||
techniques for reducing time, memory, and compile-time cost -- without repeating the detailed pages it links to.
|
||||
techniques for reducing time, memory, and compile-time cost — without repeating the detailed pages it links to.
|
||||
|
||||
## Parsing input
|
||||
|
||||
@@ -14,8 +14,8 @@ techniques for reducing time, memory, and compile-time cost -- without repeating
|
||||
`#!cpp FILE*` (see [Parsing](parsing/index.md#input)). Internally, every input is wrapped in an
|
||||
[input adapter](../home/architecture.md#input-adapters), and not all adapters are equally fast.
|
||||
|
||||
For inputs backed by contiguous, single-byte memory -- a `#!cpp std::string`, a `#!cpp std::vector<char>`, a string
|
||||
literal, or a pointer range -- the library uses `iterator_input_adapter`, wrapped in a raw pointer so the fast paths
|
||||
For inputs backed by contiguous, single-byte memory — a `#!cpp std::string`, a `#!cpp std::vector<char>`, a string
|
||||
literal, or a pointer range — the library uses `iterator_input_adapter`, wrapped in a raw pointer so the fast paths
|
||||
below apply on every supported standard. This adapter exposes two optimizations the lexer detects at compile time:
|
||||
|
||||
- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is
|
||||
@@ -69,7 +69,7 @@ a DOM, three alternatives avoid building it:
|
||||
`basic_json` values.
|
||||
- Pass a [parser callback](parsing/parser_callbacks.md) to `parse`. This still builds a DOM, but the callback can
|
||||
discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the
|
||||
unparsed remainder of the input) instead of the whole document -- see the [recipe for streaming a large homogeneous
|
||||
unparsed remainder of the input) instead of the whole document — see the [recipe for streaming a large homogeneous
|
||||
array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array).
|
||||
- Parse into a [`json_document`](json_view.md) (`#!cpp <nlohmann/json_view.hpp>`) instead of a `basic_json`. It keeps
|
||||
the input text and builds a flat index of 16 bytes per value; strings and floating-point numbers are not copied, but
|
||||
@@ -85,35 +85,53 @@ at a time, and a malformed line does not invalidate lines already processed.
|
||||
## Binary formats
|
||||
|
||||
JSON text is not a compact format. If the data is only exchanged between programs (not read by humans), the
|
||||
[binary formats](binary_formats/index.md) -- BJData, BON8, BSON, CBOR, MessagePack, and UBJSON -- encode the same
|
||||
[binary formats](binary_formats/index.md) — BJData, BON8, BSON, CBOR, MessagePack, and UBJSON — encode the same
|
||||
values more compactly, which reduces both the bytes transferred and, for most of them, the work needed to parse them
|
||||
back. The [size comparison](binary_formats/index.md#sizes) on that page, measured against minified JSON for four
|
||||
reference documents, shows the effect varies a lot by document shape: CBOR and MessagePack come out at 50.5% of the
|
||||
minified JSON size for the numeric-array-heavy `canada.json`, but only around 87-88% for the string-heavy
|
||||
`jeopardy.json`, where there is less numeric data to encode more compactly. BON8 is the most compact option in that
|
||||
comparison for text-heavy documents (63.5%-87.5%), at the cost of an
|
||||
[incomplete serializer](binary_formats/index.md#completeness) (no unsigned integers above int64). Which format -- and
|
||||
whether it is worth the loss of human readability at all -- depends on the actual data; see the
|
||||
[incomplete serializer](binary_formats/index.md#completeness) (no unsigned integers above int64). Which format — and
|
||||
whether it is worth the loss of human readability at all — depends on the actual data; see the
|
||||
[comparison tables](binary_formats/index.md#comparison) before choosing one.
|
||||
|
||||
<div class="nj-charts-grid-wrap" markdown>
|
||||
<div class="nj-fig-wrap" markdown>
|
||||
--8<-- "images/binary_sizes_canada.svg"
|
||||
</div>
|
||||
<div class="nj-fig-wrap" markdown>
|
||||
--8<-- "images/binary_sizes_jeopardy.svg"
|
||||
</div>
|
||||
</div>
|
||||
<p class="nj-charts-caption" markdown>Two of the four panels from the [size comparison](binary_formats/index.md#sizes);
|
||||
the smallest format per file is drawn in a stronger colour than the others.</p>
|
||||
|
||||
## Object type: `json` vs. `ordered_json`
|
||||
|
||||
The default [`json`](../api/json.md) type stores object keys in a `#!cpp std::map`, giving logarithmic-time lookup,
|
||||
insertion, and erasure, at the cost of sorting keys alphabetically rather than preserving insertion order (see
|
||||
[Object Order](object_order.md)). [`ordered_json`](../api/ordered_json.md) uses
|
||||
[`nlohmann::ordered_map`](../api/ordered_map.md) instead, a `#!cpp std::vector`-backed container with no lookup index:
|
||||
every key-based operation is a **linear scan**, so building an object of `n` distinct keys costs **O(n²)** in total --
|
||||
every key-based operation is a **linear scan**, so building an object of `n` distinct keys costs **O(n²)** in total —
|
||||
this applies equally to inserting keys one by one and to parsing an object, since the parser inserts each key as it is
|
||||
read. The [measurements on the `ordered_map` page](../api/ordered_map.md#complexity) show this is
|
||||
negligible at typical object sizes (2000 keys: 0.7 ms for `json` vs. 3.6 ms for `ordered_json`, a 5x factor) but grows
|
||||
steeply for large, machine-generated objects (16 000 keys: 3.3 ms vs. 181.6 ms, a 54x factor).
|
||||
|
||||
<div class="nj-fig-wrap" markdown>
|
||||
--8<-- "images/ordered_json_scaling.svg"
|
||||
</div>
|
||||
<p class="nj-charts-caption" markdown>Parsing a flat object of `n` keys, measured with `-O2 -DNDEBUG`; all
|
||||
four sizes from the [table on the `ordered_map` page](../api/ordered_map.md#complexity), both
|
||||
axes logarithmic.</p>
|
||||
|
||||
If insertion order matters *and* an object routinely has many thousands of keys, `ordered_json`'s quadratic build cost
|
||||
may not be acceptable. The library's [`ObjectType` template parameter](types/template_parameters.md#objecttype) can be
|
||||
set to a different container instead: `#!cpp nlohmann::fifo_map` keeps insertion order with a real lookup index
|
||||
(avoiding the quadratic cost), while `#!cpp std::unordered_map`, `#!cpp boost::unordered_flat_map`,
|
||||
`#!cpp absl::flat_hash_map`, and similar hash maps trade insertion order for average-case constant-time lookup (through
|
||||
an adapter, since their template argument order does not match what `basic_json` expects) -- see
|
||||
an adapter, since their template argument order does not match what `basic_json` expects) — see
|
||||
[Object Order](object_order.md#alternative-behavior-preserve-insertion-order) for the full list.
|
||||
|
||||
## Avoiding copies
|
||||
@@ -128,13 +146,13 @@ an adapter, since their template argument order does not match what `basic_json`
|
||||
- **Access without copying.** [`get<T>()`](../api/basic_json/get.md) returns a copy of the stored value converted to
|
||||
`T`. When a reference or pointer to the value already stored inside the `basic_json` is enough,
|
||||
[`get_ref()`](../api/basic_json/get_ref.md) and [`get_ptr()`](../api/basic_json/get_ptr.md) access it directly:
|
||||
both pages state, word for word, "No copies are made." -- at the cost of that reference or pointer becoming invalid
|
||||
both pages state, word for word, "No copies are made." — at the cost of that reference or pointer becoming invalid
|
||||
once the underlying value changes.
|
||||
- **Iterate by reference.** `#!cpp basic_json::iterator::operator*()` returns a `reference` (an alias for
|
||||
`#!cpp basic_json&`), but a range-based for loop with a by-value loop variable (`#!cpp for (auto el : j)`) still
|
||||
copies each element, because plain `#!cpp auto` drops the reference. Write `#!cpp for (const auto& el : j)` (or
|
||||
`#!cpp auto&` for a mutable loop), and use [`items()`](../api/basic_json/items.md) the same way when the key is
|
||||
needed too -- its own examples use `#!cpp for (auto& el : j.items())`.
|
||||
needed too — its own examples use `#!cpp for (auto& el : j.items())`.
|
||||
- **Construct in place.** [`emplace_back()`](../api/basic_json/emplace_back.md) (arrays, amortized constant time) and
|
||||
[`emplace()`](../api/basic_json/emplace.md) (objects, logarithmic in the size of the container for `json`) forward
|
||||
their arguments directly to a `basic_json` constructor, rather than requiring a temporary value to be
|
||||
@@ -143,13 +161,13 @@ an adapter, since their template argument order does not match what `basic_json`
|
||||
in instead of copying it.
|
||||
- **Skip the bounds check when it is redundant.** [`at()`](../api/basic_json/at.md) and
|
||||
[`operator[]`](../api/basic_json/operator%5B%5D.md) have the same complexity (constant for a valid array index,
|
||||
logarithmic for an object key in `json`) -- the difference is that `at()` additionally checks the key or index and
|
||||
logarithmic for an object key in `json`) — the difference is that `at()` additionally checks the key or index and
|
||||
throws if it is invalid, while `operator[]` does not (see [unchecked access](element_access/unchecked_access.md) and
|
||||
[checked access](element_access/checked_access.md)). Prefer `operator[]` when the surrounding code has already
|
||||
established that the access is valid.
|
||||
- **Reserve array capacity.** `basic_json` has no public `reserve()`, but when building a large array
|
||||
incrementally with a known final size, [`get_ref()`](../api/basic_json/get_ref.md) exposes the underlying
|
||||
`#!cpp array_t` so it can be reserved directly -- see
|
||||
`#!cpp array_t` so it can be reserved directly — see
|
||||
["reserving array capacity"](element_access/unchecked_access.md#performance-reserving-array-capacity) for the
|
||||
one-line recipe.
|
||||
|
||||
@@ -182,7 +200,7 @@ an adapter, since their template argument order does not match what `basic_json`
|
||||
(word for word from the page); any non-negative `indent` pretty-prints instead, which is more readable but produces
|
||||
more bytes and more work. `dump()` builds and returns a complete `#!cpp string_t` containing the whole serialization.
|
||||
[`operator<<`](../api/operator_ltlt.md) writes directly to a `#!cpp std::ostream` instead, through the same
|
||||
serializer, but without ever materializing that intermediate string -- so if the destination is a stream (a file, or
|
||||
serializer, but without ever materializing that intermediate string — so if the destination is a stream (a file, or
|
||||
`#!cpp std::cout`), `#!cpp os << j;` avoids the allocation and copy that `#!cpp os << j.dump();` would incur for large
|
||||
values.
|
||||
|
||||
@@ -193,7 +211,7 @@ while it is in use:
|
||||
|
||||
- [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) adds a JSON Pointer to exception messages, pointing at the
|
||||
value that triggered the exception. Quoting the page directly: "enabling this macro increases the size of every
|
||||
JSON value by one pointer and adds some runtime overhead" -- every value gains a parent pointer that has to be kept
|
||||
JSON value by one pointer and adds some runtime overhead" — every value gains a parent pointer that has to be kept
|
||||
up to date as the document is built and modified.
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) adds
|
||||
[`start_pos()`](../api/basic_json/start_pos.md) and [`end_pos()`](../api/basic_json/end_pos.md), the byte offsets a
|
||||
@@ -210,7 +228,7 @@ untrusted input, or in a debug build), and keep them off in a release build that
|
||||
[`basic_json`](../api/basic_json/index.md), [`json`](../api/json.md), [`ordered_json`](../api/ordered_json.md),
|
||||
[`json_pointer`](../api/json_pointer/index.md), and [`adl_serializer`](../api/adl_serializer/index.md), pulling in only
|
||||
a handful of lightweight standard headers instead of the full `json.hpp`. A header that only needs to *name*
|
||||
`nlohmann::json` -- in a function signature or a class member declaration, for instance -- can include `json_fwd.hpp`
|
||||
`nlohmann::json` — in a function signature or a class member declaration, for instance — can include `json_fwd.hpp`
|
||||
and leave `#!cpp #include <nlohmann/json.hpp>` to the source files that actually parse, build, or serialize values,
|
||||
the same way a project would forward-declare any other heavy class to keep it out of widely-included headers:
|
||||
|
||||
@@ -231,8 +249,8 @@ nlohmann::json my_type::config() const { /* ... */ }
|
||||
```
|
||||
|
||||
One caveat: ABI-affecting macros such as `JSON_DIAGNOSTICS` and `JSON_DIAGNOSTIC_POSITIONS` are encoded into the
|
||||
library's [inline namespace name](namespace.md#limitations). Every translation unit -- whether it includes
|
||||
`json_fwd.hpp` or the full header -- must define them the same way, or linking fails with undefined references
|
||||
library's [inline namespace name](namespace.md#limitations). Every translation unit — whether it includes
|
||||
`json_fwd.hpp` or the full header — must define them the same way, or linking fails with undefined references
|
||||
instead of a compile error.
|
||||
|
||||
If I/O support is not needed at all, [`JSON_NO_IO`](../api/macros/json_no_io.md) excludes `<cstdio>`, `<ios>`,
|
||||
@@ -252,3 +270,9 @@ those headers are then never processed by the compiler at all.
|
||||
- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices
|
||||
- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types
|
||||
- [Supported macros](macros.md) - overview of all configuration macros, including the diagnostics ones above
|
||||
- [`JSON_NO_SIMD`](../api/macros/json_no_simd.md) - disable SIMD acceleration
|
||||
- [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) - use simdutf for UTF-8 validation
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) - disable the stream bulk scan
|
||||
- [`parse`](../api/basic_json/parse.md) - deserialize from a compatible input
|
||||
- [`sax_parse`](../api/basic_json/sax_parse.md) - parse with a SAX interface
|
||||
- [`json_view`](../api/json_view.md) - a non-owning view of a value in a document
|
||||
|
||||
Reference in New Issue
Block a user