Compare commits

..
Author SHA1 Message Date
Niels Lohmann ff8ba3cb74 Keep the serializer's buffers out of -Weffc++ and cpplint warnings
GCC's -Weffc++ asks for the three buffers in the member initialization
list, which would zero them again. They are now of a type with an empty
user-provided constructor (as the json_view builder's frame stack), so
they are listed there and stay uninitialized.

The 8-to-31-byte branch of the short-string writer no longer spans an
#if with a dangling else, which cpplint read as an else body without
braces.

dump() instructions (Apple clang, twitter/citm/canada, compact and
indented) are unchanged within noise (-0.3 % to +0.6 %).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 10:51:40 +02:00
Niels Lohmann 0c3e83325a Test strings of 8 to 15 characters as one vector
The words of a string of 8 to 15 printable ASCII characters were each tested
with the word-at-a-time arithmetic. With SSE2 or NEON, the two overlapping
words are joined into one vector, which takes fewer operations to test; the
stores are unchanged. Targets without either keep the word arithmetic.

Apple M1, Apple clang, retired instructions per dump(), on top of the
previous changes:
  jeopardy 451M -> 430M (-5 %)   citm_catalog 5.60M -> 5.37M (-4 %)
  twitter 3.31M -> 3.25M (-2 %)  keys and values of 1..40 characters -1 %
Linux x86-64 (callgrind), gcc 13 / clang 18: jeopardy -15 % / -7 %,
citm_catalog -6 % / -5 %, twitter -6 % / -4 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:31 +02:00
Niels Lohmann ebcd4ae4ab Copy the short runs between escapes as a block of 16 bytes
The characters between two escapes (a quote, a backslash, a control
character) are copied to the write buffer by memcpy with a length known only
at run time. For a run of up to 16 characters that has 16 or more bytes in
the string behind its start and in the buffer behind its end, a copy of a
fixed block of 16 bytes does the same without the call; the surplus bytes in
the buffer are overwritten by what follows.

Apple M1, Apple clang, retired instructions per dump(), on top of the
previous changes:
  JSON in JSON 8.32M -> 7.52M (-10 %)   paths 8.17M -> 7.49M (-8 %)
  log lines 7.49M -> 7.14M (-5 %)       cjk, emoji text (no escapes): +-1 %
Linux x86-64 (callgrind), gcc 13 / clang 18:
  JSON in JSON -11 % / -10 %, paths -7 % / -6 %, log lines -5 % / -4 %.
The branch is marked unlikely: text without escapes (CJK, accented Latin)
has few short runs, and without the hint it paid up to 5 % for the check.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:28 +02:00
Niels Lohmann 47462527b2 Write a line break and the indentation of the next line together
A pretty-printed array or object wrote ",\n", checked the room for the
indentation, and stored it, for every element. The line break and the
usual indentation (up to 32 characters, with room in the write buffer) are
now written with one check and one run of stores, also after the opening
bracket and in front of the closing one. Other indentations take the
unchanged path.

Apple M1, Apple clang, retired instructions per dump(4), on top of the
previous changes:
  citm_catalog 7.28M -> 7.06M (-3 %)   canada 44.4M -> 43.3M (-3 %)
  twitter 3.91M -> 3.78M (-3 %)        jeopardy -1 %
Linux x86-64 (callgrind), gcc 13 / clang 18, dump(4) / dump(1, '\t'):
  citm_catalog dump(4) -3 % / -4 %, tab -4 % / -5 %, with the function
  inlined (without that GCC wrote the combined step out of line and was
  5 % slower than before the change).
Compact output is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:26 +02:00
Niels Lohmann ae30ba55c3 Write strings and keys of up to 32 characters in one go with vectors
The previous change handles plain strings of up to 16 characters with
two overlapping 8-byte words. With SSE2 or NEON, two overlapping 16-byte
vectors do the same for 16 to 32 characters: one test for a quote,
backslash, control character, DEL or non-ASCII byte over both, and two
stores. Targets without either keep the limit of 16.

Apple M1, Apple clang, retired instructions per dump(), on top of the
previous changes (it replaces the table of #5878 for these strings):
  keys and values of 1..40 characters 7.07M -> 4.94M (-30 %)
  twitter 4.15M -> 3.30M (-21 %)   paths 9.09M -> 8.08M (-11 %)
  citm_catalog 5.76M -> 5.53M (-4 %)   jeopardy 461M -> 451M (-2 %)
Linux x86-64 (callgrind), gcc 13 / clang 18: twitter -11 % / -9 %,
keys and values of 1..40 characters -19 % / -17 %, paths -7 % / -7 %.
Documents without strings in that range are unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:23 +02:00
Niels Lohmann 731363a325 Write the usual indentation with four stores instead of memset
Every line of a pretty-printed document starts with its indentation, which
was written by calling memset with a length known only at run time.

An indentation of up to 32 characters, with room in the write buffer,
stores four words of the indentation character and claims the part it
needs. Wider indentations and a nearly full buffer take the unchanged
path.

Apple M1, Apple clang, retired instructions per dump(4), on top of the
previous changes:
  canada  50.7M -> 44.4M (-13 %)   citm_catalog 8.85M -> 7.48M (-16 %)
  twitter 5.13M -> 4.71M (-8 %)    jeopardy 564M -> 518M (-8 %)
  a 139-byte object 3277 -> 2924 (-11 %)
Linux x86-64 (callgrind), the whole series against develop, gcc 13 / clang 18:
  canada, indent 4: -13 % / -14 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:21 +02:00
Niels Lohmann fdcf3aa390 Do not zero the serializer's buffers
Constructing a serializer value-initialized its three buffers (64 + 512
+ 1024 bytes). Each is written before it is read and only the part that
was written is ever read, so the zeroing was never needed, but it took
about 170 instructions per dump(), more than half of the cost of dumping
a small value.

Apple M1, Apple clang, per dump():
  null                325 -> 153 instructions
  [1,2,3]             629 -> 458 instructions
  small object (134 B) 2795 -> 2624 instructions, 190 -> 165 ns (-13 %)
  a 139-byte object, compact: 2763 -> 2596 instructions (-6 %)
Large documents are unchanged. Checked with valgrind memcheck over the
differential corpus (no use of uninitialized values), and with GCC 13
-Wall -Wextra -Wmaybe-uninitialized at -O0 to -O3 and -Os.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:18 +02:00
Niels Lohmann 209348c151 Escape runs of multi-byte characters straight into the write buffer
With ensure_ascii, every non-ASCII character went through the UTF-8
state machine one byte at a time and its \uXXXX escape was assembled in
string_buffer and copied to the write buffer afterwards.

A run of well-formed multi-byte characters is now decoded and escaped
directly into the write buffer. The first byte that is ASCII or starts an
ill-formed or truncated sequence ends the run and is left to the
unchanged byte-at-a-time path, so error handling and its messages
(positions, replacement, ignore, keep) are as before.

Apple M1, Apple clang, retired instructions per dump(-1, ' ', true):
  cjk text      15.9M -> 6.94M (-56 %)   twitter 9.10M -> 6.16M (-32 %)
  emoji text    8.89M -> 6.08M (-32 %)   latin text 10.3M -> 9.23M (-10 %)
Pure ASCII documents with escapes (logs, JSON in JSON) and all modes
without ensure_ascii are unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:16 +02:00
Niels Lohmann 7c3888fe42 Write the scalar elements of a container without entering dump_internal()
Every element of an array or object went through dump_internal(), whose
frame holds the state for descending into a container (callee-saved
registers, indentation, depth). A scalar, which is what most elements
are, needs none of it, and paid for the entry and exit regardless.

The loops now call dump_scalar() directly for anything that is not an
object or an array, and dump_internal() for those only.

Apple M1, Apple clang, retired instructions per dump(), on top of the
previous change:
  small object 2997 -> 2712 (-10 %)   signed_ints 276M -> 252M (-9 %)
  floats 323M -> 301M (-7 %)          jeopardy 501M -> 462M (-8 %)
  citm_catalog 5.96M -> 5.61M (-6 %)  twitter 4.40M -> 4.12M (-6 %)
  canada 39.2M -> 37.0M (-5 %)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:13 +02:00
Niels Lohmann 5bbc7a1098 Write short plain strings and keys in one go in dump()
Most strings in real documents are object keys and short values. For
those, dump_escaped() scanned the bytes, called memcpy with a length
known only at run time, and the surrounding quote, colon and space were
three more bounds-checked writes.

A string of up to 16 printable ASCII characters (no quote, backslash or
DEL) is now read with two overlapping loads, checked with one SWAR test
per word, and written together with its quotes and the colon with two
overlapping stores. Anything else, and a write buffer without room, takes
the general path unchanged. Little-endian targets only; others keep the
general path.

dump_scalar() held the code of every scalar type and saved and restored
all callee-saved registers on every call. It only dispatches now, and the
string, integer, float and binary writers are functions of their own, so
each pays only for what it uses (the string path keeps the check for
long strings in front of any attempt, which keeps documents of long
strings neutral).

Apple M1, Apple clang, retired instructions per dump(), on top of the
printable-ASCII table of #5878:
  citm_catalog  7.54M -> 5.96M (-21 %)   twitter 4.87M -> 4.40M (-10 %)
  jeopardy      603M -> 501M   (-17 %)   logs     8.65M -> 8.17M (-5 %)
  a 139-byte object 3497 -> 2997 (-14 %)   signed_ints -4 %, canada -3 %
Linux x86-64 (callgrind), the whole series against develop, gcc 13 / clang 18:
  citm_catalog -16 % / -19 % (compact), twitter -22 % / -24 %.
Documents of long strings (paths, latin and CJK text) are within 1 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:11 +02:00
25 changed files with 1211 additions and 482 deletions

No files matched your search

+1 -25
View File
@@ -23,9 +23,6 @@ Checks whether the input is valid JSON.
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
the iterator type with `operator!=`.
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
low surrogate, and a code point above `U+10FFFF` all make this function return `false`.
Unlike the [`parse()`](parse.md) function, this function neither throws an exception in case of invalid JSON input
(i.e., a parse error) nor creates diagnostic information.
@@ -38,12 +35,9 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
- a `#!c FILE` pointer (throws if null)
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters (throws if null)
- a `std::string` or a `std::string_view` (C++17)
- a `std::string`
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
`IteratorType`
: a compatible iterator type, for instance.
@@ -131,21 +125,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
--8<-- "examples/accept__iterator_pair.output"
```
??? example "Example: (3) reading from UTF-16 strings"
The encoding is deduced from the character type of the input. String literals like `u"..."`, `U"..."`, and
`L"..."` work as well.
```cpp
--8<-- "examples/accept__wide_string.cpp"
```
Output:
```json
--8<-- "examples/accept__wide_string.output"
```
## See also
- [parse](parse.md) - deserialize from a compatible input
@@ -172,9 +151,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
a code point above `U+10FFFF`) is rejected in version 3.13.0; before that, some such sequences were accepted or
ended the input silently.
!!! warning "Deprecation"
-19
View File
@@ -44,22 +44,6 @@ are collected in [Template Parameter Requirements](../../features/types/template
- [**json**](../json.md) - default specialization
- [**ordered_json**](../ordered_json.md) - a specialization that maintains the insertion order of object keys
## Documents and views
Besides `basic_json`, the library offers a second way to hold a parsed JSON text: a
[`basic_json_document`](../basic_json_document/index.md), which owns or borrows the text and holds a flat index of the
values in it, and a [`basic_json_view`](../basic_json_view/index.md), a read-only handle to one value of such a document.
Both are defined in the header `<nlohmann/json_view.hpp>`; see
[Zero-copy JSON views](../../features/json_view.md) for the concept and the trade-offs.
- [**json_document**](../json_document.md) / [**ordered_json_document**](../ordered_json_document.md) - read-only
documents of the `basic_json` specialization they belong to
- [**json_editable_document**](../json_editable_document.md) /
[**ordered_json_editable_document**](../ordered_json_editable_document.md) - documents that can be modified in place
- [**json_view**](../json_view.md) / [**ordered_json_view**](../ordered_json_view.md) - views of a document
- [**json_editable_view**](../json_editable_view.md) /
[**ordered_json_editable_view**](../ordered_json_editable_view.md) - views of an editable document
## Iterator invalidation
All operations that add values to an **array** ([`push_back`](push_back.md) , [`operator+=`](operator+=.md),
@@ -359,9 +343,6 @@ Access to the JSON value
## See also
- [RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc8259)
- [basic_json_document](../basic_json_document/index.md) / [basic_json_view](../basic_json_view/index.md) - parse a JSON
text into a flat index and read it through a view
- [Zero-copy JSON views](../../features/json_view.md) - when to use a document and a view instead of a `basic_json`
## Version history
+1 -34
View File
@@ -25,10 +25,6 @@ static basic_json parse(IteratorType first, SentinelType last,
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
the iterator type with `operator!=`.
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
low surrogate, and a code point above `U+10FFFF` all yield a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
## Template parameters
`InputType`
@@ -38,12 +34,9 @@ static basic_json parse(IteratorType first, SentinelType last,
- a `FILE` pointer (throws if null)
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters (throws if null)
- a `std::string` or a `std::string_view` (C++17)
- a `std::string`
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
`IteratorType`
: a compatible iterator type, for instance.
@@ -119,13 +112,6 @@ By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
!!! tip "Parsing into a document instead of a value"
[`basic_json_document::parse`](../basic_json_document/parse.md) accepts the same inputs and the same options, but
keeps the result as a flat index of the parsed values instead of a tree of `basic_json` values. It is faster and uses
less memory when only parts of the document are read, and it also accepts a pointer to a null-terminated wide string,
which `parse()` does not. See [Zero-copy JSON views](../../features/json_view.md).
!!! tip "Performance"
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
@@ -264,22 +250,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
--8<-- "examples/trailing_commas.output"
```
??? example "Example: parse from UTF-16, UTF-32, and wide strings"
The encoding is deduced from the character type of the input; `std::u16string` and `std::u32string` are interpreted
as UTF-16 and UTF-32, and `std::wstring` uses the platform's `wchar_t` width. String literals like `u"..."`,
`U"..."`, and `L"..."` work as well, as do iterators over wide characters.
```cpp
--8<-- "examples/parse__wide_string.cpp"
```
Output:
```json
--8<-- "examples/parse__wide_string.output"
```
## See also
- [accept](accept.md) - check if the input is valid JSON
@@ -310,9 +280,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
time).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) restores `std::fgetc`).
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
a code point above `U+10FFFF`) is rejected with `parse_error.101` in version 3.13.0; before that, some such
sequences were accepted or ended the input silently.
!!! warning "Deprecation"
@@ -31,10 +31,6 @@ Read from input and generate SAX events
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
the iterator type with `operator!=`.
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
low surrogate, and a code point above `U+10FFFF` all yield a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
## Template parameters
@@ -46,12 +42,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- a `std::string` or a `std::string_view` (C++17)
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
`IteratorType`
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
@@ -168,9 +160,6 @@ use the same file with stdio functions, but it must not wait for another thread
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
a code point above `U+10FFFF`) is rejected with `parse_error.101` in version 3.13.0; before that, some such
sequences were accepted or ended the input silently.
!!! warning "Deprecation"
@@ -115,12 +115,6 @@ points apply to every edit:
[`source_offset()`](../basic_json_view/source_offset.md) of a value introduced by an edit is
`#!cpp static_cast<std::size_t>(-1)`, the same value it reports for a decoded string.
## See also
- [basic_json_view](../basic_json_view/index.md) - the read-only handle to a value of a document
- [json_document](../json_document.md) - the document type of the default specialization
- [Zero-copy JSON views](../../features/json_view.md) - the concept behind documents and views
## Version history
- Added in version 3.13.0.
@@ -21,18 +21,17 @@ static basic_json_document parse(IteratorType first, IteratorType last,
Both overloads accept the same JSON text as [`BasicJsonType::parse()`](../basic_json/parse.md), with the same
`ignore_comments`/`ignore_trailing_commas` options, but build a [`basic_json_document`](index.md) (a flat index into
the input) instead of a tree of `BasicJsonType` values. They take the same inputs as `BasicJsonType::parse()`, plus a
pointer to a null-terminated wide string, which `BasicJsonType::parse()` does not accept. Byte inputs that stay alive
can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous iterators) are read into a copy
that the document owns -- see the Notes. Use [`parse_copy()`](parse_copy.md) to get an owned copy of a borrowable input
as well.
the input) instead of a tree of `BasicJsonType` values. They take the same inputs as `BasicJsonType::parse()`. Byte
inputs that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous
iterators) are read into a copy that the document owns -- see the Notes. Use [`parse_copy()`](parse_copy.md) to get an
owned copy of a borrowable input as well.
## Template parameters
`InputType`
: A compatible input, for instance:
- a `#!cpp std::string`, a `#!cpp std::string_view` (C++17), or a C-style array of single-byte characters
- a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of single-byte characters
- a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`,
`#!cpp unsigned char`, `#!cpp std::uint8_t`)
- a container for which `#!cpp obj.data()` and `#!cpp obj.size()` give contiguous single-byte access, e.g.
@@ -48,10 +47,6 @@ as well.
`#!cpp std::string::iterator`; the iterators of single-byte characters are borrowed or read like the byte inputs
above, those of wide characters are converted to UTF-8
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its low
surrogate, and a code point above `U+10FFFF` all yield a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
## Parameters
`input` (in)
@@ -160,5 +155,3 @@ buffer of a given length, pass a pair of pointers: `#!cpp parse(ptr, ptr + len)`
## Version history
- Added in version 3.13.0.
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
a code point above `U+10FFFF`) is rejected with `parse_error.101`.
@@ -143,12 +143,6 @@ still refers to it -- including ones taken before the change -- reads the new va
- [**source_offset**](source_offset.md) - byte offset of this value in the document's source text
## See also
- [basic_json_document](../basic_json_document/index.md) - the document a view refers into
- [materialize](materialize.md) - build the `BasicJsonType` value of a subtree
- [Zero-copy JSON views](../../features/json_view.md) - the concept behind documents and views
## Version history
- Added in version 3.13.0.
@@ -1,18 +0,0 @@
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// a valid JSON text, given as UTF-16
const std::u16string valid_text = u"{\"name\":\"\u00e9\"}";
// an invalid JSON text, given as UTF-16
const std::u16string invalid_text = u"{\"name\":\"\u00e9\",}";
std::cout << std::boolalpha
<< json::accept(valid_text) << ' '
<< json::accept(invalid_text) << '\n';
}
@@ -1 +0,0 @@
true false
@@ -1,27 +0,0 @@
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// the same JSON text, given in different encodings
const std::string utf8 = "{\"name\":\"\u00e9\"}";
const std::u16string utf16 = u"{\"name\":\"\u00e9\"}";
const std::u32string utf32 = U"{\"name\":\"\u00e9\"}";
const std::wstring wide = L"{\"name\":\"\u00e9\"}";
// the encoding is deduced from the character type of the input, and all
// of them yield the same value
std::cout << json::parse(utf8) << '\n'
<< json::parse(utf16) << '\n'
<< json::parse(utf32) << '\n'
<< json::parse(wide) << '\n';
// a wide string literal works as well: u"..." is UTF-16, U"..." is UTF-32
std::cout << json::parse(u"[1, 2, 3]") << '\n';
// iterators over wide characters work as well
std::cout << json::parse(utf16.begin(), utf16.end()) << '\n';
}
@@ -1,6 +0,0 @@
{"name":"é"}
{"name":"é"}
{"name":"é"}
{"name":"é"}
[1,2,3]
{"name":"é"}
-11
View File
@@ -38,16 +38,6 @@ which avoids repeating it:
--8<-- "examples/get_to.output"
```
!!! info "Getting values out of a document or a view"
A [view](../api/basic_json_view/index.md) offers the same `get<T>()` conversion, but reads it straight from the
document's index, without building a `basic_json` value first. Two conversions never copy at all:
[`get_string()`](../api/basic_json_view/get_string.md) returns a string as a view into the source text, and
[`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written. Every other
type is obtained from a subtree that is built on demand with
[`materialize()`](../api/basic_json_view/materialize.md). See
[Zero-copy JSON views](json_view.md#getting-values-out-without-copying).
The library already knows how to convert to and from the scalar types and the STL containers (such as
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
object back to a `#!cpp std::map` or a JSON array back to a `#!cpp std::vector` therefore works without any extra code:
@@ -244,4 +234,3 @@ The conversions above are built in for standard types. To make the same syntax w
- [`get_ref`](../api/basic_json/get_ref.md) / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
- [Zero-copy JSON views](json_view.md) - get values out of a document or a view
+1 -4
View File
@@ -1,9 +1,7 @@
# Creating JSON values
There are several ways to create a JSON value in memory. This page gives an overview; to read a value from JSON text
instead, see [parsing](parsing/index.md). A JSON text can also be parsed into a
[document](../api/basic_json_document/index.md) and read through a [view](../api/basic_json_view/index.md) instead of
into a value; see [Zero-copy JSON views](json_view.md).
instead, see [parsing](parsing/index.md).
## From C++ values
@@ -103,4 +101,3 @@ string `#!json "42"`.
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
- [Converting values](conversions.md) - which C++ types can be used
- [Parsing](parsing/index.md) - create a value from JSON text
- [Zero-copy JSON views](json_view.md) - create a document and a view from JSON text instead of a value
-17
View File
@@ -287,23 +287,6 @@ external dependency and is not defined by default.
See [full documentation of `JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md).
## `JSON_VIEW_NO_SIMD`
When defined, the parser of [`basic_json_document`](../api/basic_json_document/index.md)
(`<nlohmann/json_view.hpp>`) uses only portable C++ to scan strings, instead of vector instructions (SSE2 on x86-64,
NEON on AArch64). The same input is accepted or rejected either way; only the speed differs. It is not defined by
default.
See [full documentation of `JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md).
## `JSON_VIEW_USE_SSSE3`
When defined on x86-64, the parser of [`basic_json_document`](../api/basic_json_document/index.md)
(`<nlohmann/json_view.hpp>`) validates non-ASCII text in strings with SSSE3 without asking the CPU first. It is not
defined by default.
See [full documentation of `JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md).
## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)`
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
@@ -85,16 +85,6 @@ json a = {1, 2, 3, 4};
a.erase(1); // [1,3,4] (erase by index)
```
## Editing a document
The operations above work on a `basic_json` value. A JSON text that was parsed into a
[document](json_view.md#editing-a-document) can also be changed in place, without building a `basic_json` tree for the
parts that are not touched: [`set`](../api/basic_json_document/set.md) replaces a value or an object member,
[`push_back`](../api/basic_json_document/push_back.md) appends to an array,
[`insert`](../api/basic_json_document/insert.md) inserts into an array, and
[`erase`](../api/basic_json_document/erase.md) removes a member or an element. These functions are available on the
editable document types, such as [`json_editable_document`](../api/json_editable_document.md).
## See also
- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array
@@ -103,4 +93,3 @@ editable document types, such as [`json_editable_document`](../api/json_editable
- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch
- [`erase`](../api/basic_json/erase.md) / [`clear`](../api/basic_json/clear.md) - remove elements
- [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) - structured modifications
- [Editing a document](json_view.md#editing-a-document) - change a parsed document in place
+1 -28
View File
@@ -18,7 +18,6 @@ flowchart LR
The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be
- a string (`#!cpp std::string`, C string, or string literal),
- a wide string (`#!cpp std::u16string`, `#!cpp std::u32string`, or `#!cpp std::wstring`, or a wide string literal),
- a `#!cpp std::istream` (e.g., an `#!cpp std::ifstream` reading from a file),
- a `#!cpp FILE*` pointer,
- a pair of iterators over a contiguous range (e.g., a `#!cpp std::vector<std::uint8_t>`), or
@@ -33,18 +32,12 @@ std::ifstream f("example.json");
json data = json::parse(f);
```
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.
The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value.
Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format.
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
[`basic_json_document::parse`](../../api/basic_json_document/parse.md) accepts the same inputs with the same options,
but keeps the result in a document rather than in a `basic_json` value; see
[Documents and views](#documents-and-views) below.
## Strictness and trailing data
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
@@ -105,24 +98,6 @@ The library offers two parsing models:
You can influence a DOM parse without switching to the SAX interface by passing a
[parser callback](parser_callbacks.md), which is called during parsing and can, for example, discard parts of the input.
## Documents and views
A third model sits between DOM and SAX: [`basic_json_document::parse`](../../api/basic_json_document/parse.md) parses
the input into a document that keeps the text and a flat index of the values in it, and
[`basic_json_view`](../../api/basic_json_view/index.md) gives read-only access to the values through that index.
Strings and numbers stay in the source text and are only decoded when they are read, which is faster and uses less
memory than building a tree of `basic_json` values when only parts of the document are needed. A document can also be
modified in place, and it can be saved and loaded again without parsing it.
```cpp
#include <nlohmann/json_view.hpp>
json_document doc = json_document::parse(R"({"happy": true, "pi": 3.141})");
std::cout << doc.root()["happy"].get<bool>() << '\n';
```
See [Zero-copy JSON views](../json_view.md) for the concept and the trade-offs.
## Exceptions
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
@@ -133,7 +108,6 @@ options.
## See also
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
- [`basic_json_document::parse`](../../api/basic_json_document/parse.md) - deserialize into a document instead of a value
- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
@@ -141,4 +115,3 @@ options.
- [SAX interface](sax_interface.md) - implement a custom SAX handler
- [parsing and exceptions](parse_exceptions.md) - control error handling
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
- [Zero-copy JSON views](../json_view.md) - parse into a document and read it through a view
@@ -81,9 +81,6 @@ Invalid UTF-8 is rejected while parsing, not just while serializing:
- In JSON text, an ill-formed UTF-8 byte in a string is a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) ("invalid string: ill-formed UTF-8 byte").
- In UTF-16 or UTF-32 input, an ill-formed sequence is a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) as well: a lone surrogate, a high surrogate
that is not followed by its low surrogate, and a code point above `U+10FFFF` are all rejected.
- In a binary format, a string that is not valid UTF-8 is a
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
+2 -2
View File
@@ -1,7 +1,7 @@
# Debugging
This page collects the library's built-in debugger integrations and other debugging-related features. They are not
covered by any single page elsewhere in the docs, so they are collected here.
This page collects the library's built-in debugger integrations and other debugging-related features. They are
not linked from a single place elsewhere in the docs, so are collected here.
## Visual Studio (natvis)
+1 -2
View File
@@ -99,8 +99,7 @@ As this global context comes at the price of storing one additional pointer per
Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type.
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information, and
[Debugging](debugging.md) for the debugger integrations of the library.
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information.
## Parse errors
+3 -4
View File
@@ -81,9 +81,8 @@ rejected by default, and the library never adds them when serializing.
The library supports **Unicode input** as follows:
- **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1).
- `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively; `std::wstring` works as well and is interpreted as UTF-16 or UTF-32, depending on the width of the platform's `wchar_t`. These encodings cannot be read from a `FILE*` or an `std::istream`, which carry bytes only.
- Ill-formed UTF-16 or UTF-32 input is rejected: a lone surrogate, a high surrogate that is not followed by its low surrogate, and a code point above `U+10FFFF` all yield a parse error. The same holds for iterators over 2- or 4-byte characters.
- Only **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1).
- `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively. These encodings are not supported when reading from files or other input containers.
- Other encodings such as Latin-1 or ISO 8859-1 are **not** supported and will yield parse or serialization errors.
- The library will not replace [Unicode noncharacters](http://www.unicode.org/faq/private_use.html#nonchar1).
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
@@ -147,7 +146,7 @@ json::parse(std::string("\"") + '\0' + "\""); // throws parse_error.101: control
Why are wide strings (e.g., `std::wstring`) dumped as arrays of numbers?
As described [above](#parse-errors-reading-non-ascii-characters), the library stores its strings as UTF-8. To store a wide string, you need to change the encoding.
As described [above](#parse-errors-reading-non-ascii-characters), the library assumes UTF-8 as encoding. To store a wide string, you need to change the encoding.
!!! example
+3 -7
View File
@@ -6,9 +6,7 @@ JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-
magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like
Python. The whole library is available as a single header, `json.hpp`, with no dependencies, no subproject, and no
complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times
down, `<nlohmann/json_literals.hpp>` holds the user-defined string literals on their own, and
`<nlohmann/json_view.hpp>` adds zero-copy views and editable documents. See
[header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
down. See [header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
OSS-Fuzz.
@@ -60,8 +58,7 @@ Conda, and more), and pkg-config.
---
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, zero-copy views,
and more.
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, and more.
[:octicons-arrow-right-24: Features](features/index.md)
@@ -77,8 +74,7 @@ Conda, and more), and pkg-config.
---
The complete reference for `basic_json` and its member functions, types, and related classes, as well as for
`json_document` and `json_view`.
The complete reference for `basic_json` and its member functions, types, and related classes.
[:octicons-arrow-right-24: API documentation](api/basic_json/index.md)
@@ -33,6 +33,7 @@
#undef JSON_SCAN_SSSE3_TARGET
#undef JSON_SCAN_VECTOR
#undef JSON_SCAN_VECTOR_UTF8
#undef JSON_DUMP_SHORT_STRINGS
#ifndef JSON_TEST_KEEP_MACROS
#undef JSON_CATCH
+550 -114
View File
@@ -34,6 +34,14 @@
#include <nlohmann/detail/string_utils.hpp>
#include <nlohmann/detail/value_t.hpp>
// the short-string fast path of the serializer assembles words from little-endian
// loads, see serializer::put_short_quoted()
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#define JSON_DUMP_SHORT_STRINGS 1
#else
#define JSON_DUMP_SHORT_STRINGS 0
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
@@ -80,11 +88,14 @@ class serializer
const std::size_t indent_step_ = 0,
error_handler_t error_handler_ = error_handler_t::strict)
: o(&s)
, number_buffer() // NOLINT(readability-redundant-member-init)
, string_buffer() // NOLINT(readability-redundant-member-init)
, indent_char(ichar)
, pretty_print(pretty_print_)
, ensure_ascii(ensure_ascii_)
, indent_step(indent_step_)
, error_handler(error_handler_)
, write_buffer() // NOLINT(readability-redundant-member-init)
{}
// deleted because of pointer members
@@ -159,34 +170,27 @@ class serializer
if (pretty_print)
{
put_literal("{\n");
// variable to hold indentation for recursive calls
const auto new_indent = next_indent(current_indent, indent_step);
put_literal_indent("{\n", new_indent);
// first n-1 elements
auto i = val.m_data.m_value.object->cbegin();
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
{
put_indent(new_indent);
put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_literal(",\n");
put_quoted(i->first, "\": ");
dump_element(i->second, new_indent, depth + 1);
put_literal_indent(",\n", new_indent);
}
// last element
JSON_ASSERT(i != val.m_data.m_value.object->cend());
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
put_indent(new_indent);
put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_quoted(i->first, "\": ");
dump_element(i->second, new_indent, depth + 1);
put_char('\n');
put_indent(current_indent);
put_literal_indent("\n", current_indent);
put_char('}');
}
else
@@ -197,20 +201,16 @@ class serializer
auto i = val.m_data.m_value.object->cbegin();
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
{
put_char('"');
dump_escaped(i->first);
put_literal("\":");
dump_internal(i->second, current_indent, depth + 1);
put_quoted(i->first, "\":");
dump_element(i->second, current_indent, depth + 1);
put_char(',');
}
// last element
JSON_ASSERT(i != val.m_data.m_value.object->cend());
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
put_char('"');
dump_escaped(i->first);
put_literal("\":");
dump_internal(i->second, current_indent, depth + 1);
put_quoted(i->first, "\":");
dump_element(i->second, current_indent, depth + 1);
put_char('}');
}
@@ -234,27 +234,24 @@ class serializer
if (pretty_print)
{
put_literal("[\n");
// variable to hold indentation for recursive calls
const auto new_indent = next_indent(current_indent, indent_step);
put_literal_indent("[\n", new_indent);
// first n-1 elements
for (auto i = val.m_data.m_value.array->cbegin();
i != val.m_data.m_value.array->cend() - 1; ++i)
{
put_indent(new_indent);
dump_internal(*i, new_indent, depth + 1);
put_literal(",\n");
dump_element(*i, new_indent, depth + 1);
put_literal_indent(",\n", new_indent);
}
// last element
JSON_ASSERT(!val.m_data.m_value.array->empty());
put_indent(new_indent);
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
put_char('\n');
put_indent(current_indent);
put_literal_indent("\n", current_indent);
put_char(']');
}
else
@@ -265,13 +262,13 @@ class serializer
for (auto i = val.m_data.m_value.array->cbegin();
i != val.m_data.m_value.array->cend() - 1; ++i)
{
dump_internal(*i, current_indent, depth + 1);
dump_element(*i, current_indent, depth + 1);
put_char(',');
}
// last element
JSON_ASSERT(!val.m_data.m_value.array->empty());
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1);
put_char(']');
}
@@ -294,6 +291,27 @@ class serializer
}
private:
/*!
@brief serialize an element of a container
Most elements are scalars, which need none of the state dump_internal() keeps
for a container; writing them from here spares each the entry and exit of
that function.
*/
JSON_HEDLEY_ALWAYS_INLINE void dump_element(const BasicJsonType& val,
const std::size_t current_indent,
const std::size_t depth)
{
if (val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array)
{
dump_internal(val, current_indent, depth);
}
else
{
dump_scalar(val, current_indent);
}
}
/*!
@brief write out @a val and everything below it without the call stack
@@ -353,16 +371,13 @@ class serializer
put_indent(frame.child_indent);
}
put_char('"');
dump_escaped(frame.object_it->first);
if (pretty_print)
{
put_literal("\": ");
put_quoted(frame.object_it->first, "\": ");
}
else
{
put_literal("\":");
put_quoted(frame.object_it->first, "\":");
}
const BasicJsonType& element = frame.object_it->second;
@@ -537,78 +552,13 @@ class serializer
{
case value_t::string:
{
put_char('"');
dump_escaped(*val.m_data.m_value.string);
put_char('"');
dump_string_value(*val.m_data.m_value.string);
return;
}
case value_t::binary:
{
if (pretty_print)
{
put_literal("{\n");
// variable to hold indentation for the bytes
const auto new_indent = next_indent(current_indent, indent_step);
put_indent(new_indent);
put_literal("\"bytes\": [");
if (!val.m_data.m_value.binary->empty())
{
for (auto i = val.m_data.m_value.binary->cbegin();
i != val.m_data.m_value.binary->cend() - 1; ++i)
{
dump_byte(*i);
put_literal(", ");
}
dump_byte(val.m_data.m_value.binary->back());
}
put_literal("],\n");
put_indent(new_indent);
put_literal("\"subtype\": ");
if (val.m_data.m_value.binary->has_subtype())
{
dump_integer(val.m_data.m_value.binary->subtype());
}
else
{
put_literal("null");
}
put_char('\n');
put_indent(current_indent);
put_char('}');
}
else
{
put_literal("{\"bytes\":[");
if (!val.m_data.m_value.binary->empty())
{
for (auto i = val.m_data.m_value.binary->cbegin();
i != val.m_data.m_value.binary->cend() - 1; ++i)
{
dump_byte(*i);
put_char(',');
}
dump_byte(val.m_data.m_value.binary->back());
}
put_literal("],\"subtype\":");
if (val.m_data.m_value.binary->has_subtype())
{
dump_integer(val.m_data.m_value.binary->subtype());
put_char('}');
}
else
{
put_literal("null}");
}
}
dump_binary_value(current_indent, *val.m_data.m_value.binary);
return;
}
@@ -627,19 +577,19 @@ class serializer
case value_t::number_integer:
{
dump_integer(val.m_data.m_value.number_integer);
dump_signed_value(val.m_data.m_value.number_integer);
return;
}
case value_t::number_unsigned:
{
dump_integer(val.m_data.m_value.number_unsigned);
dump_unsigned_value(val.m_data.m_value.number_unsigned);
return;
}
case value_t::number_float:
{
dump_float(val.m_data.m_value.number_float);
dump_float_value(val.m_data.m_value.number_float);
return;
}
@@ -662,6 +612,107 @@ class serializer
}
}
/// @brief write the string @a s in quotes; kept out of line, so that the
/// other scalars do not pay for its register needs (and the other way round)
JSON_HEDLEY_NEVER_INLINE void dump_string_value(const string_t& s)
{
#if JSON_DUMP_SHORT_STRINGS
if (s.size() <= short_string_max && put_short_quoted(s, "\""))
{
return;
}
#endif
put_char('"');
dump_escaped(s);
put_char('"');
}
// The numbers, and the rest of what dump_scalar() writes, are left to
// functions of their own as well: a single function holding all of them
// saves and restores every register any of them needs, on every call. Now
// dump_scalar() only dispatches, and each writer pays for what it uses.
JSON_HEDLEY_NEVER_INLINE void dump_signed_value(const number_integer_t x)
{
dump_integer(x);
}
JSON_HEDLEY_NEVER_INLINE void dump_unsigned_value(const number_unsigned_t x)
{
dump_integer(x);
}
JSON_HEDLEY_NEVER_INLINE void dump_float_value(const number_float_t x)
{
dump_float(x);
}
JSON_HEDLEY_NEVER_INLINE void dump_binary_value(const std::size_t current_indent, const typename BasicJsonType::binary_t& binary)
{
if (pretty_print)
{
put_literal("{\n");
// variable to hold indentation for the bytes
const auto new_indent = next_indent(current_indent, indent_step);
put_indent(new_indent);
put_literal("\"bytes\": [");
if (!binary.empty())
{
for (auto i = binary.cbegin();
i != binary.cend() - 1; ++i)
{
dump_byte(*i);
put_literal(", ");
}
dump_byte(binary.back());
}
put_literal("],\n");
put_indent(new_indent);
put_literal("\"subtype\": ");
if (binary.has_subtype())
{
dump_integer(binary.subtype());
}
else
{
put_literal("null");
}
put_char('\n');
put_indent(current_indent);
put_char('}');
}
else
{
put_literal("{\"bytes\":[");
if (!binary.empty())
{
for (auto i = binary.cbegin();
i != binary.cend() - 1; ++i)
{
dump_byte(*i);
put_char(',');
}
dump_byte(binary.back());
}
put_literal("],\"subtype\":");
if (binary.has_subtype())
{
dump_integer(binary.subtype());
put_char('}');
}
else
{
put_literal("null}");
}
}
}
/*!
@brief the indentation level to use for the children of the current value
@@ -678,6 +729,217 @@ class serializer
return new_indent;
}
private:
/*!
@brief append the string @a s in quotes, followed by @a tail
The quote, the string and @a tail (the closing quote, possibly with the
colon of an object key) are the bulk of what most documents consist of, and
most keys and many values are short and plain. Those are handled in one go
by put_short_quoted(); everything else takes the general path.
*/
template<std::size_t N>
void put_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
#if JSON_DUMP_SHORT_STRINGS
if (put_short_quoted(s, tail))
{
return;
}
#endif
put_char('"');
dump_escaped(s);
put_literal(tail);
}
#if JSON_DUMP_SHORT_STRINGS
/// the longest string put_short_quoted() takes
static constexpr std::size_t short_string_max = JSON_SCAN_VECTOR ? 32 : 16;
/// the high bit of every byte of @a v that is not printable ASCII or is a
/// quote, a backslash, or DEL (zero if the bytes can be copied as they are)
static std::uint64_t swar_not_plain(const std::uint64_t v) noexcept
{
constexpr std::uint64_t ones = 0x0101010101010101ull;
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
return (((q - ones) & ~q) | ((b - ones) & ~b) // == '"', == '\\'
| ((v - 0x2020202020202020ull) & ~v) // < 0x20
| v | (v + ones)) // >= 0x7F
& 0x8080808080808080ull;
}
template<typename WordType>
static WordType load_word(const char* p) noexcept
{
WordType w;
std::memcpy(&w, p, sizeof(w));
return w;
}
template<typename WordType>
static void store_word(char* p, const WordType w) noexcept
{
std::memcpy(p, &w, sizeof(w));
}
/*!
@brief write `"`, @a s and @a tail if @a s is short and needs no escaping
A string of up to 16 (32 with vector instructions) printable ASCII
characters (no quote, backslash, or DEL) is read with two overlapping loads
and written with two overlapping stores, instead of scanning it and calling
memcpy with a length that is only known at run time. Returns false, having
changed nothing, if @a s does not qualify or the write buffer lacks the
room.
*/
template<std::size_t N>
bool put_short_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
const std::size_t n = s.size();
if (n > short_string_max || write_buffer_pos + n + N > write_buffer.size())
{
return false;
}
char* d = write_buffer.data() + write_buffer_pos + 1;
const char* const p = s.data();
if (n >= 8)
{
#if JSON_SCAN_VECTOR
if (!(n >= 16 ? copy_plain_vectors(d, p, n) : copy_plain_halves(d, p, n)))
{
return false;
}
#else
const auto a = load_word<std::uint64_t>(p);
const auto b = load_word<std::uint64_t>(p + n - 8);
if ((swar_not_plain(a) | swar_not_plain(b)) != 0)
{
return false;
}
store_word(d, a);
store_word(d + n - 8, b);
#endif
}
else if (n >= 4)
{
const auto a = load_word<std::uint32_t>(p);
const auto b = load_word<std::uint32_t>(p + n - 4);
// the bytes above the string are filled with a plain character
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 4)))
| (0x4141414141414141ull << (8 * n));
if (swar_not_plain(w) != 0)
{
return false;
}
store_word(d, a);
store_word(d + n - 4, b);
}
else if (n >= 2)
{
const auto a = load_word<std::uint16_t>(p);
const auto b = load_word<std::uint16_t>(p + n - 2);
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 2)))
| (0x4141414141414141ull << (8 * n));
if (swar_not_plain(w) != 0)
{
return false;
}
store_word(d, a);
store_word(d + n - 2, b);
}
else if (n == 1)
{
const std::uint64_t w = static_cast<std::uint64_t>(static_cast<unsigned char>(p[0])) | (0x4141414141414141ull << 8u);
if (swar_not_plain(w) != 0)
{
return false;
}
*d = p[0];
}
d[-1] = '"';
std::memcpy(d + n, tail, N - 1);
write_buffer_pos += n + N;
return true;
}
#if JSON_SCAN_VECTOR
/// copy the 8 to 15 bytes [@a p, @a p + @a n) to @a d with two overlapping
/// words, tested together as one vector, see copy_plain_vectors()
static bool copy_plain_halves(char* d, const char* p, const std::size_t n) noexcept
{
#if JSON_SCAN_NEON
const uint8x8_t lo = vld1_u8(reinterpret_cast<const std::uint8_t*>(p));
const uint8x8_t hi = vld1_u8(reinterpret_cast<const std::uint8_t*>(p + n - 8));
const uint8x16_t in = vcombine_u8(lo, hi);
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)), vceqq_u8(in, vdupq_n_u8(0x7F))));
if (vmaxvq_u8(special) != 0)
{
return false;
}
vst1_u8(reinterpret_cast<std::uint8_t*>(d), lo);
vst1_u8(reinterpret_cast<std::uint8_t*>(d + n - 8), hi);
#else
const __m128i lo = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
const __m128i hi = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p + n - 8))); // NOLINT(bugprone-casting-through-void)
const __m128i in = _mm_unpacklo_epi64(lo, hi);
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_or_si128(_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))));
if (_mm_movemask_epi8(special) != 0)
{
return false;
}
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d)), lo); // NOLINT(bugprone-casting-through-void)
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d + n - 8)), hi); // NOLINT(bugprone-casting-through-void)
#endif
return true;
}
/// copy the 16 to 32 bytes [@a p, @a p + @a n) to @a d with two overlapping
/// vectors, unless one of them is a quote, a backslash, a control
/// character, DEL or not ASCII
static bool copy_plain_vectors(char* d, const char* p, const std::size_t n) noexcept
{
#if JSON_SCAN_NEON
const uint8x16_t a = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p));
const uint8x16_t b = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p + n - 16));
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('"')), vceqq_u8(a, vdupq_n_u8('\\'))),
vorrq_u8(vceqq_u8(b, vdupq_n_u8('"')), vceqq_u8(b, vdupq_n_u8('\\')))),
vorrq_u8(vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(a), vdupq_n_s8(0x20)), vcltq_s8(vreinterpretq_s8_u8(b), vdupq_n_s8(0x20))),
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))));
if (vmaxvq_u8(special) != 0)
{
return false;
}
vst1q_u8(reinterpret_cast<std::uint8_t*>(d), a);
vst1q_u8(reinterpret_cast<std::uint8_t*>(d + n - 16), b);
#else
const __m128i a = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
const __m128i b = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + n - 16))); // NOLINT(bugprone-casting-through-void)
const __m128i quote = _mm_set1_epi8('"');
const __m128i backslash = _mm_set1_epi8('\\');
const __m128i space = _mm_set1_epi8(0x20);
const __m128i del = _mm_set1_epi8(0x7F);
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, quote), _mm_cmpeq_epi8(a, backslash)),
_mm_or_si128(_mm_cmpeq_epi8(b, quote), _mm_cmpeq_epi8(b, backslash))),
_mm_or_si128(_mm_or_si128(_mm_cmplt_epi8(a, space), _mm_cmplt_epi8(b, space)),
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))));
if (_mm_movemask_epi8(special) != 0)
{
return false;
}
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d)), a); // NOLINT(bugprone-casting-through-void)
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d + n - 16)), b); // NOLINT(bugprone-casting-through-void)
#endif
return true;
}
#endif
#endif
JSON_PRIVATE_UNLESS_TESTED:
/*!
@brief dump escaped string
@@ -690,14 +952,23 @@ class serializer
@param[in] s the string to escape
Complexity: Linear in the length of string @a s.
@note kept out of line: with the short-string attempts in front of it gone
from here, compilers would otherwise inline it into @ref put_quoted
and then no longer into the loops that call that
*/
void dump_escaped(const string_t& s)
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s)
{
// Short strings of printable ASCII - most object keys and many values -
// are checked byte by byte against a table and then copied in one go.
// For them, this is cheaper than starting the vector scanner below,
// whose setup costs more than such a string takes to scan. The same
// check serves both values of ensure_ascii.
//
// Where put_short_quoted() exists and takes strings of up to 32 bytes,
// the callers have tried it already, and repeating the attempt only
// costs time for strings that need escaping or are not ASCII.
#if !(JSON_DUMP_SHORT_STRINGS && JSON_SCAN_VECTOR)
if (s.size() <= 32)
{
const auto& copyable = ascii_copyable();
@@ -719,6 +990,7 @@ class serializer
return;
}
}
#endif
// dispatch once here rather than test the flag inside the loop: it does
// not change while a string is written, and folding it lets each of the
@@ -796,7 +1068,7 @@ class serializer
put_buffer(string_buffer, bytes);
bytes = 0;
}
put_string(s, i, i + run);
put_run(s, i, run);
bytes_after_last_accept = 0;
undumped_chars = 0;
i += run;
@@ -836,6 +1108,27 @@ class serializer
const auto byte = static_cast<std::uint8_t>(s[i]);
if (EnsureAscii && state == UTF8_ACCEPT && byte >= 0x80)
{
// a run of well-formed multi-byte characters is escaped
// straight into the write buffer; an ill-formed sequence
// ends the run and is left to the byte-at-a-time path
if (bytes != 0)
{
put_buffer(string_buffer, bytes);
bytes = 0;
bytes_after_last_accept = 0;
}
const std::size_t escaped = write_u_escapes(reinterpret_cast<const unsigned char*>(s.data()) + i, s.size() - i);
if (escaped != 0)
{
bytes_after_last_accept = 0;
undumped_chars = 0;
i += escaped - 1;
continue;
}
}
switch (decode(state, codepoint, byte))
{
case UTF8_ACCEPT: // decode found a new code point
@@ -1113,6 +1406,21 @@ class serializer
return;
}
// the usual indentation: four words of the indentation character are
// stored unconditionally and only the part wanted is claimed, which
// spares the call of memset
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + 32 <= write_buffer.size()))
{
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
char* const d = write_buffer.data() + write_buffer_pos;
std::memcpy(d, &word, sizeof(word));
std::memcpy(d + 8, &word, sizeof(word));
std::memcpy(d + 16, &word, sizeof(word));
std::memcpy(d + 24, &word, sizeof(word));
write_buffer_pos += indent;
return;
}
const std::size_t capacity = write_buffer.size();
// fill whatever room is left in the buffer; this is the whole job
@@ -1146,6 +1454,32 @@ class serializer
write_buffer_pos = indent;
}
/*!
@brief append a string literal and then @a indent indentation characters
The usual line break and indentation go into the buffer with one check of
its room and four stores of the indentation, see @ref put_indent.
*/
template<std::size_t N>
JSON_HEDLEY_ALWAYS_INLINE void put_literal_indent(const char (&s)[N], const std::size_t indent) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
constexpr std::size_t length = N - 1;
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + length + 32 <= write_buffer.size()))
{
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
char* const d = write_buffer.data() + write_buffer_pos;
std::memcpy(d, s, length);
std::memcpy(d + length, &word, sizeof(word));
std::memcpy(d + length + 8, &word, sizeof(word));
std::memcpy(d + length + 16, &word, sizeof(word));
std::memcpy(d + length + 24, &word, sizeof(word));
write_buffer_pos += length + indent;
return;
}
put_literal(s);
put_indent(indent);
}
/*!
@brief append a string literal to the write buffer
@@ -1170,6 +1504,28 @@ class serializer
write_buffer_pos += length;
}
/*!
@brief append the characters of @a str in [@a start, @a start + @a length)
A run of up to 16 characters with 16 or more in the string from @a start on
and in the write buffer from its end on is copied as a whole block of 16,
which spares the call of memcpy with a length known only at run time; the
surplus characters in the buffer are overwritten by what follows.
*/
template<typename StringType>
void put_run(const StringType& str, const std::size_t start, const std::size_t length)
{
// a short run is the exception in text without escapes (CJK, accented
// Latin), for which this keeps the long path the fast one
if (JSON_HEDLEY_UNLIKELY(length <= 16 && start + 16 <= str.size() && write_buffer_pos + 16 <= write_buffer.size()))
{
std::memcpy(write_buffer.data() + write_buffer_pos, str.data() + start, 16);
write_buffer_pos += length;
return;
}
put_string(str, start, start + length);
}
/*!
@brief append the characters of @a str in [@a start, @a end)
@@ -1412,6 +1768,75 @@ class serializer
return escapes;
}
/*!
@brief escape the well-formed multi-byte characters at the start of @a data
Writes the "\\uXXXX" escape of each character (two of them for a character
above U+FFFF) straight into the write buffer, which is why the caller must
have written out what is pending in @ref string_buffer. Stops at the first
byte that is below 0x80 or starts an ill-formed or truncated sequence, which
the caller handles.
@return the number of bytes of @a data that have been escaped
*/
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
{
constexpr const char* nibble_to_hex = "0123456789abcdef";
std::size_t i = 0;
while (i < n && data[i] >= 0x80)
{
const std::size_t length = validate_one_utf8(data + i, n - i);
if (length == 0)
{
break;
}
const auto b0 = static_cast<std::uint32_t>(data[i]);
const auto b1 = static_cast<std::uint32_t>(data[i + 1]);
std::uint32_t codepoint = 0;
switch (length)
{
case 2:
codepoint = ((b0 & 0x1Fu) << 6u) | (b1 & 0x3Fu);
break;
case 3:
codepoint = ((b0 & 0x0Fu) << 12u) | ((b1 & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu);
break;
default:
codepoint = ((b0 & 0x07u) << 18u) | ((b1 & 0x3Fu) << 12u) | ((static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 3]) & 0x3Fu);
break;
}
// at most two escapes of six bytes each
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < 12))
{
flush();
}
char* d = write_buffer.data() + write_buffer_pos;
if (codepoint > 0xFFFFu)
{
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
codepoint = 0xDC00u + (codepoint & 0x3FFu);
}
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
i += length;
}
return i;
}
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
{
const auto unit = static_cast<std::uint32_t>(codeunit);
d[0] = '\\';
d[1] = 'u';
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
d[5] = nibble_to_hex[unit & 0x0Fu];
return d + 6;
}
/*!
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
*
@@ -1769,11 +2194,22 @@ class serializer
/// the output of the serializer (non-owning; the adapter lives at the call site)
output_adapter_protocol<char>* o = nullptr;
/// a character buffer left uninitialized: the buffers below are written
/// before they are read (only the part written is ever read), and zeroing
/// 1.6 KiB took more than half of what dumping a small value costs (the
/// user-provided constructor keeps the member initialization, which
/// -Weffc++ asks for, from zeroing them)
template<std::size_t N>
struct uninitialized_buffer : std::array<char, N>
{
uninitialized_buffer() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
};
/// a (hopefully) large enough character buffer
std::array<char, 64> number_buffer{{}};
uninitialized_buffer<64> number_buffer;
/// string buffer
std::array<char, 512> string_buffer{{}};
uninitialized_buffer<512> string_buffer;
/// the indentation character
const char indent_char;
@@ -1793,7 +2229,7 @@ class serializer
/// buffer collecting output before it is flushed to the output adapter, so
/// that the many small structural writes become few bulk writes
static constexpr std::size_t write_buffer_size = 1024;
std::array<char, write_buffer_size> write_buffer{{}};
uninitialized_buffer<write_buffer_size> write_buffer;
/// number of valid bytes currently held in @ref write_buffer
std::size_t write_buffer_pos = 0;
};
+551 -114
View File
@@ -30178,6 +30178,14 @@ NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/value_t.hpp>
// the short-string fast path of the serializer assembles words from little-endian
// loads, see serializer::put_short_quoted()
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#define JSON_DUMP_SHORT_STRINGS 1
#else
#define JSON_DUMP_SHORT_STRINGS 0
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
@@ -30224,11 +30232,14 @@ class serializer
const std::size_t indent_step_ = 0,
error_handler_t error_handler_ = error_handler_t::strict)
: o(&s)
, number_buffer() // NOLINT(readability-redundant-member-init)
, string_buffer() // NOLINT(readability-redundant-member-init)
, indent_char(ichar)
, pretty_print(pretty_print_)
, ensure_ascii(ensure_ascii_)
, indent_step(indent_step_)
, error_handler(error_handler_)
, write_buffer() // NOLINT(readability-redundant-member-init)
{}
// deleted because of pointer members
@@ -30303,34 +30314,27 @@ class serializer
if (pretty_print)
{
put_literal("{\n");
// variable to hold indentation for recursive calls
const auto new_indent = next_indent(current_indent, indent_step);
put_literal_indent("{\n", new_indent);
// first n-1 elements
auto i = val.m_data.m_value.object->cbegin();
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
{
put_indent(new_indent);
put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_literal(",\n");
put_quoted(i->first, "\": ");
dump_element(i->second, new_indent, depth + 1);
put_literal_indent(",\n", new_indent);
}
// last element
JSON_ASSERT(i != val.m_data.m_value.object->cend());
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
put_indent(new_indent);
put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_quoted(i->first, "\": ");
dump_element(i->second, new_indent, depth + 1);
put_char('\n');
put_indent(current_indent);
put_literal_indent("\n", current_indent);
put_char('}');
}
else
@@ -30341,20 +30345,16 @@ class serializer
auto i = val.m_data.m_value.object->cbegin();
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
{
put_char('"');
dump_escaped(i->first);
put_literal("\":");
dump_internal(i->second, current_indent, depth + 1);
put_quoted(i->first, "\":");
dump_element(i->second, current_indent, depth + 1);
put_char(',');
}
// last element
JSON_ASSERT(i != val.m_data.m_value.object->cend());
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
put_char('"');
dump_escaped(i->first);
put_literal("\":");
dump_internal(i->second, current_indent, depth + 1);
put_quoted(i->first, "\":");
dump_element(i->second, current_indent, depth + 1);
put_char('}');
}
@@ -30378,27 +30378,24 @@ class serializer
if (pretty_print)
{
put_literal("[\n");
// variable to hold indentation for recursive calls
const auto new_indent = next_indent(current_indent, indent_step);
put_literal_indent("[\n", new_indent);
// first n-1 elements
for (auto i = val.m_data.m_value.array->cbegin();
i != val.m_data.m_value.array->cend() - 1; ++i)
{
put_indent(new_indent);
dump_internal(*i, new_indent, depth + 1);
put_literal(",\n");
dump_element(*i, new_indent, depth + 1);
put_literal_indent(",\n", new_indent);
}
// last element
JSON_ASSERT(!val.m_data.m_value.array->empty());
put_indent(new_indent);
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
put_char('\n');
put_indent(current_indent);
put_literal_indent("\n", current_indent);
put_char(']');
}
else
@@ -30409,13 +30406,13 @@ class serializer
for (auto i = val.m_data.m_value.array->cbegin();
i != val.m_data.m_value.array->cend() - 1; ++i)
{
dump_internal(*i, current_indent, depth + 1);
dump_element(*i, current_indent, depth + 1);
put_char(',');
}
// last element
JSON_ASSERT(!val.m_data.m_value.array->empty());
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1);
put_char(']');
}
@@ -30438,6 +30435,27 @@ class serializer
}
private:
/*!
@brief serialize an element of a container
Most elements are scalars, which need none of the state dump_internal() keeps
for a container; writing them from here spares each the entry and exit of
that function.
*/
JSON_HEDLEY_ALWAYS_INLINE void dump_element(const BasicJsonType& val,
const std::size_t current_indent,
const std::size_t depth)
{
if (val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array)
{
dump_internal(val, current_indent, depth);
}
else
{
dump_scalar(val, current_indent);
}
}
/*!
@brief write out @a val and everything below it without the call stack
@@ -30497,16 +30515,13 @@ class serializer
put_indent(frame.child_indent);
}
put_char('"');
dump_escaped(frame.object_it->first);
if (pretty_print)
{
put_literal("\": ");
put_quoted(frame.object_it->first, "\": ");
}
else
{
put_literal("\":");
put_quoted(frame.object_it->first, "\":");
}
const BasicJsonType& element = frame.object_it->second;
@@ -30681,78 +30696,13 @@ class serializer
{
case value_t::string:
{
put_char('"');
dump_escaped(*val.m_data.m_value.string);
put_char('"');
dump_string_value(*val.m_data.m_value.string);
return;
}
case value_t::binary:
{
if (pretty_print)
{
put_literal("{\n");
// variable to hold indentation for the bytes
const auto new_indent = next_indent(current_indent, indent_step);
put_indent(new_indent);
put_literal("\"bytes\": [");
if (!val.m_data.m_value.binary->empty())
{
for (auto i = val.m_data.m_value.binary->cbegin();
i != val.m_data.m_value.binary->cend() - 1; ++i)
{
dump_byte(*i);
put_literal(", ");
}
dump_byte(val.m_data.m_value.binary->back());
}
put_literal("],\n");
put_indent(new_indent);
put_literal("\"subtype\": ");
if (val.m_data.m_value.binary->has_subtype())
{
dump_integer(val.m_data.m_value.binary->subtype());
}
else
{
put_literal("null");
}
put_char('\n');
put_indent(current_indent);
put_char('}');
}
else
{
put_literal("{\"bytes\":[");
if (!val.m_data.m_value.binary->empty())
{
for (auto i = val.m_data.m_value.binary->cbegin();
i != val.m_data.m_value.binary->cend() - 1; ++i)
{
dump_byte(*i);
put_char(',');
}
dump_byte(val.m_data.m_value.binary->back());
}
put_literal("],\"subtype\":");
if (val.m_data.m_value.binary->has_subtype())
{
dump_integer(val.m_data.m_value.binary->subtype());
put_char('}');
}
else
{
put_literal("null}");
}
}
dump_binary_value(current_indent, *val.m_data.m_value.binary);
return;
}
@@ -30771,19 +30721,19 @@ class serializer
case value_t::number_integer:
{
dump_integer(val.m_data.m_value.number_integer);
dump_signed_value(val.m_data.m_value.number_integer);
return;
}
case value_t::number_unsigned:
{
dump_integer(val.m_data.m_value.number_unsigned);
dump_unsigned_value(val.m_data.m_value.number_unsigned);
return;
}
case value_t::number_float:
{
dump_float(val.m_data.m_value.number_float);
dump_float_value(val.m_data.m_value.number_float);
return;
}
@@ -30806,6 +30756,107 @@ class serializer
}
}
/// @brief write the string @a s in quotes; kept out of line, so that the
/// other scalars do not pay for its register needs (and the other way round)
JSON_HEDLEY_NEVER_INLINE void dump_string_value(const string_t& s)
{
#if JSON_DUMP_SHORT_STRINGS
if (s.size() <= short_string_max && put_short_quoted(s, "\""))
{
return;
}
#endif
put_char('"');
dump_escaped(s);
put_char('"');
}
// The numbers, and the rest of what dump_scalar() writes, are left to
// functions of their own as well: a single function holding all of them
// saves and restores every register any of them needs, on every call. Now
// dump_scalar() only dispatches, and each writer pays for what it uses.
JSON_HEDLEY_NEVER_INLINE void dump_signed_value(const number_integer_t x)
{
dump_integer(x);
}
JSON_HEDLEY_NEVER_INLINE void dump_unsigned_value(const number_unsigned_t x)
{
dump_integer(x);
}
JSON_HEDLEY_NEVER_INLINE void dump_float_value(const number_float_t x)
{
dump_float(x);
}
JSON_HEDLEY_NEVER_INLINE void dump_binary_value(const std::size_t current_indent, const typename BasicJsonType::binary_t& binary)
{
if (pretty_print)
{
put_literal("{\n");
// variable to hold indentation for the bytes
const auto new_indent = next_indent(current_indent, indent_step);
put_indent(new_indent);
put_literal("\"bytes\": [");
if (!binary.empty())
{
for (auto i = binary.cbegin();
i != binary.cend() - 1; ++i)
{
dump_byte(*i);
put_literal(", ");
}
dump_byte(binary.back());
}
put_literal("],\n");
put_indent(new_indent);
put_literal("\"subtype\": ");
if (binary.has_subtype())
{
dump_integer(binary.subtype());
}
else
{
put_literal("null");
}
put_char('\n');
put_indent(current_indent);
put_char('}');
}
else
{
put_literal("{\"bytes\":[");
if (!binary.empty())
{
for (auto i = binary.cbegin();
i != binary.cend() - 1; ++i)
{
dump_byte(*i);
put_char(',');
}
dump_byte(binary.back());
}
put_literal("],\"subtype\":");
if (binary.has_subtype())
{
dump_integer(binary.subtype());
put_char('}');
}
else
{
put_literal("null}");
}
}
}
/*!
@brief the indentation level to use for the children of the current value
@@ -30822,6 +30873,217 @@ class serializer
return new_indent;
}
private:
/*!
@brief append the string @a s in quotes, followed by @a tail
The quote, the string and @a tail (the closing quote, possibly with the
colon of an object key) are the bulk of what most documents consist of, and
most keys and many values are short and plain. Those are handled in one go
by put_short_quoted(); everything else takes the general path.
*/
template<std::size_t N>
void put_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
#if JSON_DUMP_SHORT_STRINGS
if (put_short_quoted(s, tail))
{
return;
}
#endif
put_char('"');
dump_escaped(s);
put_literal(tail);
}
#if JSON_DUMP_SHORT_STRINGS
/// the longest string put_short_quoted() takes
static constexpr std::size_t short_string_max = JSON_SCAN_VECTOR ? 32 : 16;
/// the high bit of every byte of @a v that is not printable ASCII or is a
/// quote, a backslash, or DEL (zero if the bytes can be copied as they are)
static std::uint64_t swar_not_plain(const std::uint64_t v) noexcept
{
constexpr std::uint64_t ones = 0x0101010101010101ull;
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
return (((q - ones) & ~q) | ((b - ones) & ~b) // == '"', == '\\'
| ((v - 0x2020202020202020ull) & ~v) // < 0x20
| v | (v + ones)) // >= 0x7F
& 0x8080808080808080ull;
}
template<typename WordType>
static WordType load_word(const char* p) noexcept
{
WordType w;
std::memcpy(&w, p, sizeof(w));
return w;
}
template<typename WordType>
static void store_word(char* p, const WordType w) noexcept
{
std::memcpy(p, &w, sizeof(w));
}
/*!
@brief write `"`, @a s and @a tail if @a s is short and needs no escaping
A string of up to 16 (32 with vector instructions) printable ASCII
characters (no quote, backslash, or DEL) is read with two overlapping loads
and written with two overlapping stores, instead of scanning it and calling
memcpy with a length that is only known at run time. Returns false, having
changed nothing, if @a s does not qualify or the write buffer lacks the
room.
*/
template<std::size_t N>
bool put_short_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
const std::size_t n = s.size();
if (n > short_string_max || write_buffer_pos + n + N > write_buffer.size())
{
return false;
}
char* d = write_buffer.data() + write_buffer_pos + 1;
const char* const p = s.data();
if (n >= 8)
{
#if JSON_SCAN_VECTOR
if (!(n >= 16 ? copy_plain_vectors(d, p, n) : copy_plain_halves(d, p, n)))
{
return false;
}
#else
const auto a = load_word<std::uint64_t>(p);
const auto b = load_word<std::uint64_t>(p + n - 8);
if ((swar_not_plain(a) | swar_not_plain(b)) != 0)
{
return false;
}
store_word(d, a);
store_word(d + n - 8, b);
#endif
}
else if (n >= 4)
{
const auto a = load_word<std::uint32_t>(p);
const auto b = load_word<std::uint32_t>(p + n - 4);
// the bytes above the string are filled with a plain character
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 4)))
| (0x4141414141414141ull << (8 * n));
if (swar_not_plain(w) != 0)
{
return false;
}
store_word(d, a);
store_word(d + n - 4, b);
}
else if (n >= 2)
{
const auto a = load_word<std::uint16_t>(p);
const auto b = load_word<std::uint16_t>(p + n - 2);
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 2)))
| (0x4141414141414141ull << (8 * n));
if (swar_not_plain(w) != 0)
{
return false;
}
store_word(d, a);
store_word(d + n - 2, b);
}
else if (n == 1)
{
const std::uint64_t w = static_cast<std::uint64_t>(static_cast<unsigned char>(p[0])) | (0x4141414141414141ull << 8u);
if (swar_not_plain(w) != 0)
{
return false;
}
*d = p[0];
}
d[-1] = '"';
std::memcpy(d + n, tail, N - 1);
write_buffer_pos += n + N;
return true;
}
#if JSON_SCAN_VECTOR
/// copy the 8 to 15 bytes [@a p, @a p + @a n) to @a d with two overlapping
/// words, tested together as one vector, see copy_plain_vectors()
static bool copy_plain_halves(char* d, const char* p, const std::size_t n) noexcept
{
#if JSON_SCAN_NEON
const uint8x8_t lo = vld1_u8(reinterpret_cast<const std::uint8_t*>(p));
const uint8x8_t hi = vld1_u8(reinterpret_cast<const std::uint8_t*>(p + n - 8));
const uint8x16_t in = vcombine_u8(lo, hi);
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)), vceqq_u8(in, vdupq_n_u8(0x7F))));
if (vmaxvq_u8(special) != 0)
{
return false;
}
vst1_u8(reinterpret_cast<std::uint8_t*>(d), lo);
vst1_u8(reinterpret_cast<std::uint8_t*>(d + n - 8), hi);
#else
const __m128i lo = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
const __m128i hi = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p + n - 8))); // NOLINT(bugprone-casting-through-void)
const __m128i in = _mm_unpacklo_epi64(lo, hi);
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_or_si128(_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))));
if (_mm_movemask_epi8(special) != 0)
{
return false;
}
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d)), lo); // NOLINT(bugprone-casting-through-void)
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d + n - 8)), hi); // NOLINT(bugprone-casting-through-void)
#endif
return true;
}
/// copy the 16 to 32 bytes [@a p, @a p + @a n) to @a d with two overlapping
/// vectors, unless one of them is a quote, a backslash, a control
/// character, DEL or not ASCII
static bool copy_plain_vectors(char* d, const char* p, const std::size_t n) noexcept
{
#if JSON_SCAN_NEON
const uint8x16_t a = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p));
const uint8x16_t b = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p + n - 16));
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('"')), vceqq_u8(a, vdupq_n_u8('\\'))),
vorrq_u8(vceqq_u8(b, vdupq_n_u8('"')), vceqq_u8(b, vdupq_n_u8('\\')))),
vorrq_u8(vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(a), vdupq_n_s8(0x20)), vcltq_s8(vreinterpretq_s8_u8(b), vdupq_n_s8(0x20))),
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))));
if (vmaxvq_u8(special) != 0)
{
return false;
}
vst1q_u8(reinterpret_cast<std::uint8_t*>(d), a);
vst1q_u8(reinterpret_cast<std::uint8_t*>(d + n - 16), b);
#else
const __m128i a = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
const __m128i b = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + n - 16))); // NOLINT(bugprone-casting-through-void)
const __m128i quote = _mm_set1_epi8('"');
const __m128i backslash = _mm_set1_epi8('\\');
const __m128i space = _mm_set1_epi8(0x20);
const __m128i del = _mm_set1_epi8(0x7F);
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, quote), _mm_cmpeq_epi8(a, backslash)),
_mm_or_si128(_mm_cmpeq_epi8(b, quote), _mm_cmpeq_epi8(b, backslash))),
_mm_or_si128(_mm_or_si128(_mm_cmplt_epi8(a, space), _mm_cmplt_epi8(b, space)),
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))));
if (_mm_movemask_epi8(special) != 0)
{
return false;
}
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d)), a); // NOLINT(bugprone-casting-through-void)
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d + n - 16)), b); // NOLINT(bugprone-casting-through-void)
#endif
return true;
}
#endif
#endif
JSON_PRIVATE_UNLESS_TESTED:
/*!
@brief dump escaped string
@@ -30834,14 +31096,23 @@ class serializer
@param[in] s the string to escape
Complexity: Linear in the length of string @a s.
@note kept out of line: with the short-string attempts in front of it gone
from here, compilers would otherwise inline it into @ref put_quoted
and then no longer into the loops that call that
*/
void dump_escaped(const string_t& s)
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s)
{
// Short strings of printable ASCII - most object keys and many values -
// are checked byte by byte against a table and then copied in one go.
// For them, this is cheaper than starting the vector scanner below,
// whose setup costs more than such a string takes to scan. The same
// check serves both values of ensure_ascii.
//
// Where put_short_quoted() exists and takes strings of up to 32 bytes,
// the callers have tried it already, and repeating the attempt only
// costs time for strings that need escaping or are not ASCII.
#if !(JSON_DUMP_SHORT_STRINGS && JSON_SCAN_VECTOR)
if (s.size() <= 32)
{
const auto& copyable = ascii_copyable();
@@ -30863,6 +31134,7 @@ class serializer
return;
}
}
#endif
// dispatch once here rather than test the flag inside the loop: it does
// not change while a string is written, and folding it lets each of the
@@ -30940,7 +31212,7 @@ class serializer
put_buffer(string_buffer, bytes);
bytes = 0;
}
put_string(s, i, i + run);
put_run(s, i, run);
bytes_after_last_accept = 0;
undumped_chars = 0;
i += run;
@@ -30980,6 +31252,27 @@ class serializer
const auto byte = static_cast<std::uint8_t>(s[i]);
if (EnsureAscii && state == UTF8_ACCEPT && byte >= 0x80)
{
// a run of well-formed multi-byte characters is escaped
// straight into the write buffer; an ill-formed sequence
// ends the run and is left to the byte-at-a-time path
if (bytes != 0)
{
put_buffer(string_buffer, bytes);
bytes = 0;
bytes_after_last_accept = 0;
}
const std::size_t escaped = write_u_escapes(reinterpret_cast<const unsigned char*>(s.data()) + i, s.size() - i);
if (escaped != 0)
{
bytes_after_last_accept = 0;
undumped_chars = 0;
i += escaped - 1;
continue;
}
}
switch (decode(state, codepoint, byte))
{
case UTF8_ACCEPT: // decode found a new code point
@@ -31257,6 +31550,21 @@ class serializer
return;
}
// the usual indentation: four words of the indentation character are
// stored unconditionally and only the part wanted is claimed, which
// spares the call of memset
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + 32 <= write_buffer.size()))
{
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
char* const d = write_buffer.data() + write_buffer_pos;
std::memcpy(d, &word, sizeof(word));
std::memcpy(d + 8, &word, sizeof(word));
std::memcpy(d + 16, &word, sizeof(word));
std::memcpy(d + 24, &word, sizeof(word));
write_buffer_pos += indent;
return;
}
const std::size_t capacity = write_buffer.size();
// fill whatever room is left in the buffer; this is the whole job
@@ -31290,6 +31598,32 @@ class serializer
write_buffer_pos = indent;
}
/*!
@brief append a string literal and then @a indent indentation characters
The usual line break and indentation go into the buffer with one check of
its room and four stores of the indentation, see @ref put_indent.
*/
template<std::size_t N>
JSON_HEDLEY_ALWAYS_INLINE void put_literal_indent(const char (&s)[N], const std::size_t indent) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
constexpr std::size_t length = N - 1;
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + length + 32 <= write_buffer.size()))
{
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
char* const d = write_buffer.data() + write_buffer_pos;
std::memcpy(d, s, length);
std::memcpy(d + length, &word, sizeof(word));
std::memcpy(d + length + 8, &word, sizeof(word));
std::memcpy(d + length + 16, &word, sizeof(word));
std::memcpy(d + length + 24, &word, sizeof(word));
write_buffer_pos += length + indent;
return;
}
put_literal(s);
put_indent(indent);
}
/*!
@brief append a string literal to the write buffer
@@ -31314,6 +31648,28 @@ class serializer
write_buffer_pos += length;
}
/*!
@brief append the characters of @a str in [@a start, @a start + @a length)
A run of up to 16 characters with 16 or more in the string from @a start on
and in the write buffer from its end on is copied as a whole block of 16,
which spares the call of memcpy with a length known only at run time; the
surplus characters in the buffer are overwritten by what follows.
*/
template<typename StringType>
void put_run(const StringType& str, const std::size_t start, const std::size_t length)
{
// a short run is the exception in text without escapes (CJK, accented
// Latin), for which this keeps the long path the fast one
if (JSON_HEDLEY_UNLIKELY(length <= 16 && start + 16 <= str.size() && write_buffer_pos + 16 <= write_buffer.size()))
{
std::memcpy(write_buffer.data() + write_buffer_pos, str.data() + start, 16);
write_buffer_pos += length;
return;
}
put_string(str, start, start + length);
}
/*!
@brief append the characters of @a str in [@a start, @a end)
@@ -31556,6 +31912,75 @@ class serializer
return escapes;
}
/*!
@brief escape the well-formed multi-byte characters at the start of @a data
Writes the "\\uXXXX" escape of each character (two of them for a character
above U+FFFF) straight into the write buffer, which is why the caller must
have written out what is pending in @ref string_buffer. Stops at the first
byte that is below 0x80 or starts an ill-formed or truncated sequence, which
the caller handles.
@return the number of bytes of @a data that have been escaped
*/
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
{
constexpr const char* nibble_to_hex = "0123456789abcdef";
std::size_t i = 0;
while (i < n && data[i] >= 0x80)
{
const std::size_t length = validate_one_utf8(data + i, n - i);
if (length == 0)
{
break;
}
const auto b0 = static_cast<std::uint32_t>(data[i]);
const auto b1 = static_cast<std::uint32_t>(data[i + 1]);
std::uint32_t codepoint = 0;
switch (length)
{
case 2:
codepoint = ((b0 & 0x1Fu) << 6u) | (b1 & 0x3Fu);
break;
case 3:
codepoint = ((b0 & 0x0Fu) << 12u) | ((b1 & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu);
break;
default:
codepoint = ((b0 & 0x07u) << 18u) | ((b1 & 0x3Fu) << 12u) | ((static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 3]) & 0x3Fu);
break;
}
// at most two escapes of six bytes each
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < 12))
{
flush();
}
char* d = write_buffer.data() + write_buffer_pos;
if (codepoint > 0xFFFFu)
{
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
codepoint = 0xDC00u + (codepoint & 0x3FFu);
}
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
i += length;
}
return i;
}
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
{
const auto unit = static_cast<std::uint32_t>(codeunit);
d[0] = '\\';
d[1] = 'u';
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
d[5] = nibble_to_hex[unit & 0x0Fu];
return d + 6;
}
/*!
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
*
@@ -31913,11 +32338,22 @@ class serializer
/// the output of the serializer (non-owning; the adapter lives at the call site)
output_adapter_protocol<char>* o = nullptr;
/// a character buffer left uninitialized: the buffers below are written
/// before they are read (only the part written is ever read), and zeroing
/// 1.6 KiB took more than half of what dumping a small value costs (the
/// user-provided constructor keeps the member initialization, which
/// -Weffc++ asks for, from zeroing them)
template<std::size_t N>
struct uninitialized_buffer : std::array<char, N>
{
uninitialized_buffer() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
};
/// a (hopefully) large enough character buffer
std::array<char, 64> number_buffer{{}};
uninitialized_buffer<64> number_buffer;
/// string buffer
std::array<char, 512> string_buffer{{}};
uninitialized_buffer<512> string_buffer;
/// the indentation character
const char indent_char;
@@ -31937,7 +32373,7 @@ class serializer
/// buffer collecting output before it is flushed to the output adapter, so
/// that the many small structural writes become few bulk writes
static constexpr std::size_t write_buffer_size = 1024;
std::array<char, write_buffer_size> write_buffer{{}};
uninitialized_buffer<write_buffer_size> write_buffer;
/// number of valid bytes currently held in @ref write_buffer
std::size_t write_buffer_pos = 0;
};
@@ -40701,6 +41137,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
#undef JSON_SCAN_SSSE3_TARGET
#undef JSON_SCAN_VECTOR
#undef JSON_SCAN_VECTOR_UTF8
#undef JSON_DUMP_SHORT_STRINGS
#ifndef JSON_TEST_KEEP_MACROS
#undef JSON_CATCH
+91
View File
@@ -1580,3 +1580,94 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
}
}
}
TEST_CASE("short strings are written like the reference, as values and as keys")
{
// strings of up to 16 characters (32 with vector instructions) that need no
// escaping are written as a whole (together with the quotes and, for a key,
// the colon); everything else, including a string that is merely short,
// takes the general path.
// Every length around the edges of the size classes of the fast path is
// combined with each kind of byte that must send a string to the general
// path, at each position, and compared with the reference for values and,
// via the value, for keys.
const std::vector<std::string> specials =
{
"\"", "\\", "\n", "\x1f", "\x7f", "\xc3\xa9", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xff", "\x80", "\xc3"
};
for (std::size_t length = 0; length <= 36; ++length)
{
const std::string plain(length, 'k');
check_dump_against_reference(plain, false);
for (const auto& special : specials)
{
for (std::size_t pos = 0; pos <= length; ++pos)
{
std::string s = plain;
s.insert(pos, special);
check_dump_against_reference(s, false);
for (const bool ensure_ascii :
{
false, true
})
{
// a key and a value are written alike, in compact and
// in pretty-printed form
const json value = s;
const json object = json::object({{s, 1}});
for (const auto error_handler :
{
json::error_handler_t::replace, json::error_handler_t::keep
})
{
const std::string quoted = value.dump(-1, ' ', ensure_ascii, error_handler);
CHECK(object.dump(-1, ' ', ensure_ascii, error_handler) == "{" + quoted + ":1}");
CHECK(object.dump(2, ' ', ensure_ascii, error_handler) == "{\n " + quoted + ": 1\n}");
}
}
}
}
}
}
TEST_CASE("runs of multi-byte characters are escaped like the reference under ensure_ascii")
{
// a run of well-formed multi-byte characters is escaped straight into the
// write buffer; it has to flush that mid-run, stop at an ASCII character or
// an ill-formed sequence and leave those to the general path, and pick up
// behind what the general path has pending
const std::vector<std::string> characters =
{
"\xc2\x80", "\xdf\xbf", "\xe0\xa0\x80", "\xe2\x82\xac", "\xed\x9f\xbf", "\xef\xbf\xbf",
"\xf0\x90\x80\x80", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf"
};
const std::vector<std::string> interruptions =
{
"", "a", "\n", "\"", "\x7f", "\xff", "\x80", "\xc3", "\xe2\x82", "\xf0\x9f\x98", "\xed\xa0\x80"
};
for (std::size_t shift = 0; shift <= 13; ++shift)
{
for (const auto& interruption : interruptions)
{
for (const std::size_t count :
{
std::size_t{1}, std::size_t{2}, std::size_t{85}, std::size_t{200}
})
{
std::string s(shift, 'x');
for (std::size_t i = 0; i < count; ++i)
{
s += characters[(i + shift) % characters.size()];
}
s += interruption;
s += characters[shift % characters.size()];
s += characters[(shift + 3) % characters.size()];
check_dump_against_reference(s, shift < 2);
}
}
}
}