Merge branch 'develop' into claude/json-view-docs-split-48c8e0
Port the json_view.md change of #6017 (reading a file into a string) into json_view/differences.md, and point the links to json_view.md that develop added in files.md, performance.md and parsing/index.md at json_view/index.md. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
41 files changed
+611
-260
No files matched your search
@@ -15,7 +15,9 @@ the patch. This function applies a JSON patch in place and returns void.
|
||||
|
||||
## Exception safety
|
||||
|
||||
No guarantees, value may be corrupted by an unsuccessful patch operation.
|
||||
No guarantees: if an operation fails, the operations before it stay applied, and a failing `move` operation may already
|
||||
have removed the value at its "from" location. Use [`patch`](patch.md) if the value must be unchanged after a failed
|
||||
patch.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -62,8 +64,19 @@ the JSON value, the complexity can usually be neglected.
|
||||
## Notes
|
||||
|
||||
Unlike [`patch`](patch.md), `patch_inplace` applies the operation "in place" and no copy of the JSON value is created.
|
||||
That makes it faster for large documents by avoiding the copy. However, the JSON value might be corrupted if the
|
||||
function throws an exception.
|
||||
That makes it faster for large documents by avoiding the copy.
|
||||
|
||||
!!! warning "Not atomic"
|
||||
|
||||
`patch_inplace` applies the operations one after the other and does not undo them when one fails. After an
|
||||
exception, the value holds the result of the operations before the failing one, and a failing `move` operation
|
||||
(a "remove" followed by an "add") may already have removed the value at its "from" location; see the second example
|
||||
below. [RFC 6902](https://tools.ietf.org/html/rfc6902#section-5) requires that a patch with a failing operation is
|
||||
not deemed successful, but leaves open what happens to the document.
|
||||
|
||||
If the value must stay unchanged when the patch fails, use [`patch`](patch.md), which applies the patch to a copy:
|
||||
`#!cpp j = j.patch(json_patch);`. Validating the patch beforehand is not enough, as "test" operations and missing
|
||||
paths are only detected while the patch is applied.
|
||||
|
||||
Duplicate member names inside an operation object are resolved by the parser before the patch is applied (the last one
|
||||
is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# Reading and writing files
|
||||
|
||||
The library has no functions that take a file name. It reads from and writes to streams, so files are handled with
|
||||
`#!cpp std::ifstream` and `#!cpp std::ofstream` (or a `#!cpp FILE*` when reading). This page collects the recipes for
|
||||
the common cases and the mistakes that show up again and again.
|
||||
|
||||
## Reading a JSON file
|
||||
|
||||
Pass the stream to [`parse`](../api/basic_json/parse.md) directly, and check that the file was opened:
|
||||
|
||||
```cpp
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
json read_json_file(const std::string& path)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open())
|
||||
{
|
||||
throw std::runtime_error("cannot open " + path);
|
||||
}
|
||||
return json::parse(f);
|
||||
}
|
||||
```
|
||||
|
||||
- **Check that the file is open.** A stream that failed to open looks like an empty input, so `parse` reports
|
||||
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) ("unexpected end of input") at line 1,
|
||||
column 1 instead of saying that the file is missing. A relative path is resolved against the working directory of the
|
||||
running program, which is often not the directory of the source file (see the
|
||||
[FAQ](../home/faq.md#parsing-a-file-fails-with-unexpected-end-of-input)).
|
||||
- **Don't read the file into a string first.** Parsing from a `#!cpp std::ifstream` is only about 10 to 20 % slower
|
||||
than reading the whole file into a string with one `read()` and parsing that, and it needs no memory for the copy (see
|
||||
[input sources and performance](parsing/index.md#input-sources-and-performance)). The common way to read a file with
|
||||
`#!cpp std::istreambuf_iterator<char>` is so slow that it takes about twice as long as parsing from the stream. Read
|
||||
the file into a string only if you need the text itself, for example for an error message. This advice is for
|
||||
`#!cpp json::parse`. [`json_document`](json_view/index.md) reads a stream into a buffer that it owns anyway, so there is no
|
||||
copy to save: reading the file into a `#!cpp std::string` yourself and passing that as an lvalue, which the document
|
||||
borrows, was about 6 to 12 % faster than parsing from the stream, with the same single buffer.
|
||||
- `parse` requires the file to hold exactly one JSON value. Files with one value per line are read with
|
||||
[JSON Lines](parsing/json_lines.md); files too large for memory are covered in
|
||||
[Large, streamed, and partial input](parsing/large_and_partial_input.md).
|
||||
|
||||
## Writing a JSON file
|
||||
|
||||
[`dump`](../api/basic_json/dump.md) the value into a string first, then write the string:
|
||||
|
||||
```cpp
|
||||
void write_json_file(const std::string& path, const json& j)
|
||||
{
|
||||
const std::string text = j.dump(4) + '\n'; // may throw: nothing is written yet
|
||||
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
if (!f.is_open())
|
||||
{
|
||||
throw std::runtime_error("cannot open " + path);
|
||||
}
|
||||
f << text;
|
||||
f.close();
|
||||
if (f.fail())
|
||||
{
|
||||
throw std::runtime_error("cannot write " + path);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Serialize before opening the file.** Opening a `#!cpp std::ofstream` truncates the file. `dump` and
|
||||
[`operator<<`](../api/operator_ltlt.md) throw [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316)
|
||||
when a string is not valid UTF-8 (see [handling invalid UTF-8](serialization.md#handling-invalid-utf-8)), and
|
||||
`#!cpp f << std::setw(4) << j` may already have written part of the value when it throws. Serializing into a string
|
||||
first leaves the old file untouched in that case.
|
||||
- **Check the stream after closing it.** A full disk or a missing permission sets the stream's error state; it does not
|
||||
throw unless you enabled exceptions on the stream with `exceptions()`.
|
||||
- `std::ios::binary` writes the text as it is. Without it, Windows writes `\r\n` for every newline of a pretty-printed
|
||||
value.
|
||||
|
||||
## Replacing a file without corrupting it
|
||||
|
||||
The library never writes a file itself, so this section is not about the library. It is general C++ advice for a
|
||||
problem users of the library keep running into.
|
||||
|
||||
A program that crashes or is killed while it writes leaves a truncated file behind. To make sure readers see either the
|
||||
old or the new contents, write to a temporary file in the same directory and rename it over the original
|
||||
(C++17, `#!cpp <filesystem>`):
|
||||
|
||||
```cpp
|
||||
#include <filesystem>
|
||||
|
||||
void replace_json_file(const std::filesystem::path& path, const json& j)
|
||||
{
|
||||
std::filesystem::path tmp = path;
|
||||
tmp += ".tmp"; // use a unique name if several processes may write the file
|
||||
|
||||
write_json_file(tmp.string(), j);
|
||||
std::filesystem::rename(tmp, path); // replaces path
|
||||
}
|
||||
```
|
||||
|
||||
The temporary file must be in the same directory (more precisely, on the same file system) as the target: a rename
|
||||
across file systems does not fall back to a copy, it fails and throws `#!cpp std::filesystem::filesystem_error`. On
|
||||
POSIX systems, the rename atomically replaces the file. Standard C++ has no way to flush a file to the disk (`fsync`),
|
||||
so this protects against a crashed program, but not necessarily against a power loss.
|
||||
|
||||
## Binary formats
|
||||
|
||||
The [binary formats](binary_formats/index.md) work the same way, with the streams opened in binary mode:
|
||||
|
||||
```cpp
|
||||
// read
|
||||
std::ifstream in("data.cbor", std::ios::binary);
|
||||
json j = json::from_cbor(in);
|
||||
|
||||
// write
|
||||
const std::vector<std::uint8_t> bytes = json::to_cbor(j);
|
||||
std::ofstream out("data.cbor", std::ios::binary);
|
||||
out.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
|
||||
```
|
||||
|
||||
Without `std::ios::binary`, Windows turns the bytes `0x0D 0x0A` into `0x0A` and may stop at a byte `0x1A` while reading,
|
||||
and turns `0x0A` into `0x0D 0x0A` while writing, which corrupts the data.
|
||||
|
||||
## See also
|
||||
|
||||
- [Parsing](parsing/index.md) - the inputs `parse` accepts
|
||||
- [Serialization](serialization.md) - the options of `dump`
|
||||
- [`parse`](../api/basic_json/parse.md), [`dump`](../api/basic_json/dump.md)
|
||||
- [`operator>>`](../api/operator_gtgt.md), [`operator<<`](../api/operator_ltlt.md)
|
||||
@@ -46,6 +46,8 @@ C++ types, and finally serialize it again.
|
||||
[BJData](binary_formats/bjdata.md), [BON8](binary_formats/bon8.md), [BSON](binary_formats/bson.md),
|
||||
[CBOR](binary_formats/cbor.md), [MessagePack](binary_formats/messagepack.md), or [UBJSON](binary_formats/ubjson.md).
|
||||
- [Binary values](binary_values.md) — store and exchange raw byte sequences.
|
||||
- [Reading and writing files](files.md) — read and write files safely, including replacing a file without
|
||||
corrupting it.
|
||||
|
||||
## How values are stored and configured
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ other three continue on its result.
|
||||
|
||||
[`patch`](../api/basic_json/patch.md) leaves the original value unchanged and returns the patched result as a copy.
|
||||
If the document is large and the original value is no longer needed,
|
||||
[`patch_inplace`](../api/basic_json/patch_inplace.md) applies the same operations in place instead.
|
||||
[`patch_inplace`](../api/basic_json/patch_inplace.md) applies the same operations in place instead. Unlike
|
||||
`patch`, it is not atomic: if an operation fails, the operations before it stay applied.
|
||||
|
||||
!!! note "Duplicate member names"
|
||||
|
||||
|
||||
@@ -56,7 +56,9 @@ does not: what a document parses differently, what a view's functions do differe
|
||||
wide. The limit is 16 bytes below 2^32, because the parser reads in 16-byte steps, and a position one step past the
|
||||
end must still fit.
|
||||
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
|
||||
is read to its end into a buffer the document owns, and parsing starts after that.
|
||||
is read to its end into a buffer the document owns, and parsing starts after that. For a file, reading it into a
|
||||
`#!cpp std::string` yourself and parsing that (an lvalue is borrowed) is therefore not more expensive in memory, and
|
||||
was about 6 to 12 % faster than parsing from an `#!cpp std::ifstream`.
|
||||
- **No parser callbacks.** [`parse()`](../../api/basic_json_document/parse.md) takes no
|
||||
[`parser_callback_t`](../../api/basic_json/parser_callback_t.md): a document always indexes the whole text. Skip
|
||||
the values you do not need when reading the view instead.
|
||||
|
||||
@@ -35,6 +35,8 @@ std::ifstream f("example.json");
|
||||
json data = json::parse(f);
|
||||
```
|
||||
|
||||
See [Reading and writing files](../files.md) for checking that the file was opened and for writing files safely.
|
||||
|
||||
The input must be encoded in UTF-8; other encodings are not supported. The exception is wide-character input, which
|
||||
is read as UTF-16 or UTF-32 — depending on the character type — and converted to UTF-8; see the
|
||||
[`parse`](../../api/basic_json/parse.md) page for an example. A single input may contain only one JSON value.
|
||||
@@ -84,6 +86,9 @@ ahead: after parsing, it is positioned exactly as if every character had been re
|
||||
[`operator>>`](../../api/operator_gtgt.md) can still read several values from one stream (see also
|
||||
[JSON Lines](json_lines.md)).
|
||||
|
||||
This does not hold for [`json_document`](../json_view/index.md): it reads a stream into a buffer it owns, so the stream saves
|
||||
no memory, and reading the file into a `#!cpp std::string` yourself and borrowing it is about 6 to 12 % faster.
|
||||
|
||||
Two macros switch these optimizations off without changing any result:
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not
|
||||
follow the usual `std::streambuf` contract, and
|
||||
|
||||
@@ -39,7 +39,9 @@ iterator pair over non-contiguous but random-access storage (e.g. `#!cpp std::de
|
||||
optimization but not the second, since the byte-scanning fast path requires contiguous storage.
|
||||
|
||||
Practically: a file can be passed directly to `parse`, which also avoids the memory for a copy of the file. Reading it
|
||||
into a string first is no longer necessary for speed:
|
||||
into a string first is no longer necessary for speed (this is for `#!cpp json::parse`; a
|
||||
[`json_document`](json_view/index.md) reads a stream into an owned buffer anyway, so for it reading the file into a string
|
||||
and borrowing that is the faster way):
|
||||
|
||||
```cpp
|
||||
// fast: the stream buffer's get area is scanned in bulk
|
||||
|
||||
@@ -17,6 +17,9 @@ To write a value directly to a stream (for example, a file or `#!cpp std::cout`)
|
||||
std::cout << j << std::endl;
|
||||
```
|
||||
|
||||
To write a file without leaving a truncated file behind when serialization fails, see
|
||||
[Reading and writing files](files.md#writing-a-json-file).
|
||||
|
||||
!!! note "String, not raw value"
|
||||
|
||||
`dump` always returns a **JSON text**. Serializing a JSON string therefore includes the surrounding quotes and
|
||||
|
||||
@@ -110,7 +110,7 @@ in the source files that need it, instantiate `basic_json` once, and consider pr
|
||||
The parser did not see any character: the file was most likely not opened. A relative path is resolved against the
|
||||
working directory of the running program, which is often the build directory of your IDE, not the source directory.
|
||||
Check `is_open()` and try an absolute path. Other causes are an empty file and a stream that was already read. See
|
||||
[`parse_error.101`](exceptions.md#jsonexceptionparse_error101).
|
||||
[`parse_error.101`](exceptions.md#jsonexceptionparse_error101) and [Reading and writing files](../features/files.md).
|
||||
|
||||
### Input that arrives in chunks
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<svg class="nj-fig nj-charts nj-charts--compile" viewBox="0 0 470 516" role="img" aria-labelledby="nj-charts-ct nj-charts-ct-d" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="nj-fig nj-charts nj-charts--compile" viewBox="0 0 470 620" role="img" aria-labelledby="nj-charts-ct nj-charts-ct-d" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="nj-charts-ct">Change in compile time against the default configuration at -O0, Apple clang and G​CC 16</title>
|
||||
<desc id="nj-charts-ct-d">Grouped horizontal bars, negative is faster. json_fwd.hpp instead of json.hpp on a header-only translation unit: Apple clang -62 %, G​CC 16 -82 %; extern template on a parsing translation unit: Apple clang -7 %, G​CC 16 -21 %; JSON_NO_AUTOMATIC_UDLS on a model translation unit: Apple clang -11 %, G​CC 16 -11 %; JSON_NO_AUTOMATIC_UDLS on a parsing translation unit: Apple clang -1 %, G​CC 16 -2 %; JSON_HAS_FILESYSTEM=0 + JSON_NO_IO on a model translation unit: Apple clang -8 %, G​CC 16 -11 %; JSON_HAS_FILESYSTEM=0 on a model translation unit: Apple clang -5 %, G​CC 16 -9 %; JSON_NO_IO on a model translation unit: Apple clang -2 %, G​CC 16 -2 %; JSON_USE_GLOBAL_UDLS=0 on a model translation unit: Apple clang 0 %, G​CC 16 -1 %.</desc>
|
||||
<desc id="nj-charts-ct-d">Grouped horizontal bars, negative is faster. json_fwd.hpp instead of json.hpp on a header-only translation unit: Apple clang -62 %, G​CC 16 -82 %; extern template on a parsing translation unit: Apple clang -7 %, G​CC 16 -21 %; JSON_NO_AUTOMATIC_UDLS on a model translation unit: Apple clang -11 %, G​CC 16 -11 %; JSON_NO_AUTOMATIC_UDLS on a parsing translation unit: Apple clang -1 %, G​CC 16 -2 %; JSON_HAS_FILESYSTEM=0 + JSON_NO_IO on a model translation unit: Apple clang -8 %, G​CC 16 -11 %; JSON_HAS_FILESYSTEM=0 on a model translation unit: Apple clang -5 %, G​CC 16 -9 %; JSON_HAS_STD_FORMAT=0 on a model translation unit: Apple clang -1 %, G​CC 16 -4 %; JSON_HAS_RANGES=0 on a model translation unit: Apple clang -4 %, G​CC 16 -1 %; JSON_NO_IO on a model translation unit: Apple clang -2 %, G​CC 16 -2 %; JSON_USE_GLOBAL_UDLS=0 on a model translation unit: Apple clang 0 %, G​CC 16 -1 %.</desc>
|
||||
<rect class="nj-charts-clang" x="8" y="10" width="12" height="12" rx="3"/>
|
||||
<text class="nj-charts-leg" x="26" y="20.5">Apple clang</text>
|
||||
<rect class="nj-charts-gcc" x="126" y="10" width="12" height="12" rx="3"/>
|
||||
<text class="nj-charts-leg" x="144" y="20.5">G​CC 16</text>
|
||||
<line class="nj-charts-grid" x1="47.4" y1="58" x2="47.4" y2="472"/>
|
||||
<text class="nj-charts-tick" x="47.4" y="488">−80 %</text>
|
||||
<line class="nj-charts-grid" x1="126.2" y1="58" x2="126.2" y2="472"/>
|
||||
<text class="nj-charts-tick" x="126.2" y="488">−60 %</text>
|
||||
<line class="nj-charts-grid" x1="205" y1="58" x2="205" y2="472"/>
|
||||
<text class="nj-charts-tick" x="205" y="488">−40 %</text>
|
||||
<line class="nj-charts-grid" x1="283.8" y1="58" x2="283.8" y2="472"/>
|
||||
<text class="nj-charts-tick" x="283.8" y="488">−20 %</text>
|
||||
<text class="nj-charts-tick" x="362.6" y="488">0 %</text>
|
||||
<text class="nj-charts-tick nj-charts-tick--l" x="8" y="506">← faster</text>
|
||||
<text class="nj-charts-tick nj-charts-tick--r" x="462" y="506">slower →</text>
|
||||
<line class="nj-charts-grid" x1="47.4" y1="58" x2="47.4" y2="576"/>
|
||||
<text class="nj-charts-tick" x="47.4" y="592">−80 %</text>
|
||||
<line class="nj-charts-grid" x1="126.2" y1="58" x2="126.2" y2="576"/>
|
||||
<text class="nj-charts-tick" x="126.2" y="592">−60 %</text>
|
||||
<line class="nj-charts-grid" x1="205" y1="58" x2="205" y2="576"/>
|
||||
<text class="nj-charts-tick" x="205" y="592">−40 %</text>
|
||||
<line class="nj-charts-grid" x1="283.8" y1="58" x2="283.8" y2="576"/>
|
||||
<text class="nj-charts-tick" x="283.8" y="592">−20 %</text>
|
||||
<text class="nj-charts-tick" x="362.6" y="592">0 %</text>
|
||||
<text class="nj-charts-tick nj-charts-tick--l" x="8" y="610">← faster</text>
|
||||
<text class="nj-charts-tick nj-charts-tick--r" x="462" y="610">slower →</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="62"><tspan class="nj-charts-code">json_fwd.hpp instead of json.hpp</tspan><tspan class="nj-charts-sub"> · header-only TU</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 70 H122.32 Q118.32 70 118.32 74 V78 Q118.32 82 122.32 82 H362.6 Z"><title>json_fwd.hpp instead of json.hpp, header-only TU (Apple clang): −62 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="80.5">−62 %</text>
|
||||
@@ -46,15 +46,25 @@
|
||||
<text class="nj-charts-val" x="367.6" y="340.5">−5 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 344 H331.14 Q327.14 344 327.14 348 V352 Q327.14 356 331.14 356 H362.6 Z"><title>JSON_HAS_FILESYSTEM=0, model TU (G​CC 16): −9 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="354.5">−9 %</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="374"><tspan class="nj-charts-code">JSON_NO_IO</tspan><tspan class="nj-charts-sub"> · model TU*</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 382 H358.72 Q354.72 382 354.72 386 V390 Q354.72 394 358.72 394 H362.6 Z"><title>JSON_NO_IO, model TU (Apple clang): −2 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="392.5">−2 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 396 H358.72 Q354.72 396 354.72 400 V404 Q354.72 408 358.72 408 H362.6 Z"><title>JSON_NO_IO, model TU (G​CC 16): −2 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="406.5">−2 %</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="426"><tspan class="nj-charts-code">JSON_USE_GLOBAL_UDLS=0</tspan><tspan class="nj-charts-sub"> · model TU*</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 434 h0.5 v12 h-0.5 Z"><title>JSON_USE_GLOBAL_UDLS=0, model TU (Apple clang): 0 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="444.5">0 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 448 H362.6 Q358.66 448 358.66 451.94 V456.06 Q358.66 460 362.6 460 H362.6 Z"><title>JSON_USE_GLOBAL_UDLS=0, model TU (G​CC 16): −1 %</title></path>
|
||||
<text class="nj-charts-rowlabel" x="8" y="374"><tspan class="nj-charts-code">JSON_HAS_STD_FORMAT=0</tspan><tspan class="nj-charts-sub"> · model TU</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 382 H362.6 Q358.66 382 358.66 385.94 V390.06 Q358.66 394 362.6 394 H362.6 Z"><title>JSON_HAS_STD_FORMAT=0, model TU (Apple clang): −1 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="392.5">−1 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 396 H350.84 Q346.84 396 346.84 400 V404 Q346.84 408 350.84 408 H362.6 Z"><title>JSON_HAS_STD_FORMAT=0, model TU (G​CC 16): −4 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="406.5">−4 %</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="426"><tspan class="nj-charts-code">JSON_HAS_RANGES=0</tspan><tspan class="nj-charts-sub"> · model TU</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 434 H350.84 Q346.84 434 346.84 438 V442 Q346.84 446 350.84 446 H362.6 Z"><title>JSON_HAS_RANGES=0, model TU (Apple clang): −4 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="444.5">−4 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 448 H362.6 Q358.66 448 358.66 451.94 V456.06 Q358.66 460 362.6 460 H362.6 Z"><title>JSON_HAS_RANGES=0, model TU (G​CC 16): −1 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="458.5">−1 %</text>
|
||||
<line class="nj-charts-zero" x1="362.6" y1="58" x2="362.6" y2="472"/>
|
||||
<text class="nj-charts-rowlabel" x="8" y="478"><tspan class="nj-charts-code">JSON_NO_IO</tspan><tspan class="nj-charts-sub"> · model TU*</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 486 H358.72 Q354.72 486 354.72 490 V494 Q354.72 498 358.72 498 H362.6 Z"><title>JSON_NO_IO, model TU (Apple clang): −2 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="496.5">−2 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 500 H358.72 Q354.72 500 354.72 504 V508 Q354.72 512 358.72 512 H362.6 Z"><title>JSON_NO_IO, model TU (G​CC 16): −2 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="510.5">−2 %</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="530"><tspan class="nj-charts-code">JSON_USE_GLOBAL_UDLS=0</tspan><tspan class="nj-charts-sub"> · model TU*</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 538 h0.5 v12 h-0.5 Z"><title>JSON_USE_GLOBAL_UDLS=0, model TU (Apple clang): 0 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="548.5">0 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 552 H362.6 Q358.66 552 358.66 555.94 V560.06 Q358.66 564 362.6 564 H362.6 Z"><title>JSON_USE_GLOBAL_UDLS=0, model TU (G​CC 16): −1 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="562.5">−1 %</text>
|
||||
<line class="nj-charts-zero" x1="362.6" y1="58" x2="362.6" y2="576"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 9.0 KiB |
@@ -11,6 +11,11 @@ that cost, ordered by how much they typically save.
|
||||
16.2.0 on macOS (Apple M1 Max). All configurations were run interleaved, in alternating order. They show the order
|
||||
of magnitude to expect; measure your own code before and after a change.
|
||||
|
||||
The two sections on `std::format` and ranges apply to C++20 only and were measured with `-std=c++20`, so their
|
||||
baselines are slower than the others. Their differences are mostly within the run-to-run noise of a few percent:
|
||||
two complete runs of the same configuration differed by up to 10% in the median. The tables show the later run (one
|
||||
outlier cell was repeated).
|
||||
|
||||
The translation units are listed below. They replace the ones used for earlier versions of this page, whose
|
||||
sources were not published, so the numbers differ from earlier versions.
|
||||
|
||||
@@ -66,7 +71,8 @@ that cost, ordered by how much they typically save.
|
||||
--8<-- "images/compile_times.svg"
|
||||
</div>
|
||||
<p class="nj-charts-caption" markdown>Change in compile time at `-O0` against the default configuration of the same
|
||||
translation unit, taken from the tables below (medians of nine runs, see above). * computed from the milliseconds in
|
||||
translation unit, taken from the tables below (medians of nine runs, see above; the `std::format` and ranges rows are
|
||||
measured with `-std=c++20`). * computed from the milliseconds in
|
||||
[Options without effect on compile times](#options-without-effect-on-compile-times).</p>
|
||||
|
||||
## Include `json_fwd.hpp` in headers
|
||||
@@ -214,6 +220,88 @@ from and to `std::string`; whether that fallback exists depends on the platform'
|
||||
(`std::wstring` on Windows), so check code that converts paths. `JSON_NO_IO` additionally removes all stream
|
||||
functions (see [Options without effect](#options-without-effect-on-compile-times)).
|
||||
|
||||
## Switch off `std::format` support
|
||||
|
||||
With C++20 and a standard library that provides `<format>`, the library includes it to provide a
|
||||
[`std::formatter<basic_json>`](../api/basic_json/std_formatter.md) specialization (see
|
||||
[`JSON_HAS_STD_FORMAT`](../api/macros/json_has_std_format.md)). If you do not format JSON values with `std::format`,
|
||||
switch the detection off for the whole project:
|
||||
|
||||
```cmake
|
||||
target_compile_definitions(my_target PRIVATE JSON_HAS_STD_FORMAT=0)
|
||||
```
|
||||
|
||||
| Compiler | Translation unit | Default (`-O0` / `-O2`) | `std::format` off (`-O0` / `-O2`) | Change |
|
||||
|-------------|------------------|------------------------:|----------------------------------:|-----------:|
|
||||
| Apple clang | model | 1283 ms / 1544 ms | 1269 ms / 1539 ms | −1% / 0% |
|
||||
| GCC 16 | model | 2026 ms / 2482 ms | 1944 ms / 2416 ms | −4% / −3% |
|
||||
| Apple clang | parsing | 1350 ms / 2278 ms | 1347 ms / 2300 ms | 0% / +1% |
|
||||
| GCC 16 | parsing | 2715 ms / 4351 ms | 2644 ms / 4287 ms | −3% / −1% |
|
||||
|
||||
<p class="nj-table-caption" markdown>Compile time with `JSON_HAS_STD_FORMAT=0` against the default configuration, both with `-std=c++20`, at `-O0` and `-O2`.</p>
|
||||
|
||||
The saving is small, and only GCC shows it: libstdc++'s `<format>` is an additional header that nothing else in the
|
||||
translation unit includes, so the macro removes it (and the headers it pulls in). With libc++ (Apple clang), other
|
||||
standard headers include `<format>` anyway, so the macro only removes the library's own formatter, and the difference is
|
||||
within the noise.
|
||||
|
||||
The trade-off: the `std::formatter<basic_json>` specialization is removed, so `#!cpp std::format("{}", j)`,
|
||||
and `#!cpp std::format_to` with a JSON value no longer compile (in a test with both compilers on
|
||||
macOS, the error was that `std::formatter` must be specialized for the type). Use [`dump()`](../api/basic_json/dump.md),
|
||||
or the stream operators, instead.
|
||||
|
||||
## Switch off ranges support
|
||||
|
||||
With C++20 and a standard library that provides `<ranges>`, the library includes it to declare the
|
||||
[`items()`](../api/basic_json/items.md) proxy a *borrowed range*, and uses the `std::ranges` concepts and algorithms for
|
||||
construction from range views and for iterator input (see [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md)). If you
|
||||
do not need these, switch the detection off for the whole project:
|
||||
|
||||
```cmake
|
||||
target_compile_definitions(my_target PRIVATE JSON_HAS_RANGES=0)
|
||||
```
|
||||
|
||||
| Compiler | Translation unit | Default (`-O0` / `-O2`) | Ranges off (`-O0` / `-O2`) | Change |
|
||||
|-------------|------------------|------------------------:|---------------------------:|-----------:|
|
||||
| Apple clang | model | 1283 ms / 1544 ms | 1237 ms / 1495 ms | −4% / −3% |
|
||||
| GCC 16 | model | 2026 ms / 2482 ms | 2004 ms / 2464 ms | −1% / −1% |
|
||||
| Apple clang | parsing | 1350 ms / 2278 ms | 1311 ms / 2284 ms | −3% / 0% |
|
||||
| GCC 16 | parsing | 2715 ms / 4351 ms | 2705 ms / 4427 ms | 0% / +2% |
|
||||
|
||||
<p class="nj-table-caption" markdown>Compile time with `JSON_HAS_RANGES=0` against the default configuration, both with `-std=c++20`, at `-O0` and `-O2`.</p>
|
||||
|
||||
The saving is small as well, and with GCC it is not distinguishable from the noise: its `<format>` includes `<span>`,
|
||||
so with `std::format` support left on, part of the ranges machinery is still parsed.
|
||||
Both macros together save more than either alone:
|
||||
|
||||
```cmake
|
||||
target_compile_definitions(my_target PRIVATE JSON_HAS_STD_FORMAT=0 JSON_HAS_RANGES=0)
|
||||
```
|
||||
|
||||
| Compiler | Translation unit | Default (`-O0` / `-O2`) | Both off (`-O0` / `-O2`) | Change |
|
||||
|-------------|------------------|------------------------:|-------------------------:|-----------:|
|
||||
| Apple clang | model | 1283 ms / 1544 ms | 1223 ms / 1504 ms | −5% / −3% |
|
||||
| GCC 16 | model | 2026 ms / 2482 ms | 1914 ms / 2378 ms | −6% / −4% |
|
||||
| Apple clang | parsing | 1350 ms / 2278 ms | 1311 ms / 2271 ms | −3% / 0% |
|
||||
| GCC 16 | parsing | 2715 ms / 4351 ms | 2635 ms / 4260 ms | −3% / −2% |
|
||||
|
||||
<p class="nj-table-caption" markdown>Compile time with `JSON_HAS_STD_FORMAT=0` and `JSON_HAS_RANGES=0` against the default configuration, both with `-std=c++20`, at `-O0` and `-O2`.</p>
|
||||
|
||||
The trade-offs of `JSON_HAS_RANGES=0` are larger than those of the other options on this page:
|
||||
|
||||
- `items()` is no longer a borrowed range. On a temporary, `#!cpp std::ranges::begin(make_json().items())` no longer
|
||||
compiles, and `std::ranges` algorithms such as `#!cpp std::ranges::find_if(make_json().items(), pred)` return
|
||||
`std::ranges::dangling` instead of an iterator, so code that uses the result no longer compiles. Calling them on
|
||||
`#!cpp j.items()` for a named `j` is not affected.
|
||||
- [`JSON_HAS_RANGE_VIEW_CONVERSION`](../api/macros/json_has_range_view_conversion.md) becomes `0`, so a JSON array can no
|
||||
longer be constructed from a `std::ranges::view` such as the result of `std::views::filter`. The assignment
|
||||
`#!cpp json j = v | std::views::filter(pred);` no longer compiles.
|
||||
- Parsing from an iterator pair loses the C++20 fast path for contiguous iterators that are not pointers (for
|
||||
example those of `std::vector`) and falls back to byte-at-a-time scanning. The result is the same; parsing may be
|
||||
slower (not measured here).
|
||||
|
||||
The first two were checked with both compilers on macOS; the third is read from the library's source.
|
||||
|
||||
## Use C++20 modules
|
||||
|
||||
With a toolchain that supports named modules, `import nlohmann.json;` compiles the library once into a module and
|
||||
@@ -343,6 +431,8 @@ and −2% / 0% (GCC). To get a saving, combine it with
|
||||
|
||||
- [`JSON_HAS_FILESYSTEM`](../api/macros/json_has_filesystem.md) - control the conversions from and to
|
||||
`std::filesystem::path`
|
||||
- [`JSON_HAS_STD_FORMAT`](../api/macros/json_has_std_format.md) - control the `std::formatter` specialization
|
||||
- [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md) - control the use of `std::ranges`
|
||||
- [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md) - do not include the user-defined string
|
||||
literals automatically
|
||||
- [Modules](../features/modules.md) - C++20 module support
|
||||
|
||||
@@ -130,6 +130,7 @@ nav:
|
||||
- features/element_access/unchecked_access.md
|
||||
- features/element_access/checked_access.md
|
||||
- features/element_access/default_value.md
|
||||
- features/files.md
|
||||
- features/iterators.md
|
||||
- features/merge_patch.md
|
||||
- features/json_patch.md
|
||||
|
||||
@@ -73,6 +73,8 @@ COMPILE = [
|
||||
("JSON_NO_AUTOMATIC_UDLS", "parsing TU", -1, -2, False),
|
||||
("JSON_HAS_FILESYSTEM=0 + JSON_NO_IO", "model TU", -8, -11, False),
|
||||
("JSON_HAS_FILESYSTEM=0", "model TU", -5, -9, False),
|
||||
("JSON_HAS_STD_FORMAT=0", "model TU", -1, -4, False),
|
||||
("JSON_HAS_RANGES=0", "model TU", -4, -1, False),
|
||||
# computed from the ms in "Options without effect on compile times": 1034/1051, 1756/1801, 1055/1051, 1783/1801
|
||||
("JSON_NO_IO", "model TU", round((1034 / 1051 - 1) * 100), round((1756 / 1801 - 1) * 100), True),
|
||||
("JSON_USE_GLOBAL_UDLS=0", "model TU", round((1055 / 1051 - 1) * 100), round((1783 / 1801 - 1) * 100), True),
|
||||
|
||||
Reference in new issue
Block a user