Compare commits

..
Author SHA1 Message Date
Niels Lohmann 0a4c1a2d42 docs: cross-reference json_view and json_document for 3.13.0
Document the new zero-copy document/view API across the docs: add a
"Documents and views" section to the basic_json API hub linking to all
specializations, point parsing/conversion/modifying pages to the view-based
alternatives, add document/view sections to feature pages, update the
landing page to mention json_view.hpp/json_literals.hpp, and cross-link
debugging/exceptions.

Also document wide-string inputs (and ill-formed UTF-16/32 rejection),
add parse/accept examples for wide strings, fix related doc gaps, and
align features/parsing and FAQ with the added support.

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

No files matched your search

+25 -1
View File
@@ -23,6 +23,9 @@ 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.
@@ -35,9 +38,12 @@ 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`
- 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 instance.
@@ -125,6 +131,21 @@ 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
@@ -151,6 +172,9 @@ 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,6 +44,22 @@ 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),
@@ -343,6 +359,9 @@ 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
+34 -1
View File
@@ -25,6 +25,10 @@ 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`
@@ -34,9 +38,12 @@ 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`
- 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 instance.
@@ -112,6 +119,13 @@ 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
@@ -250,6 +264,22 @@ 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
@@ -280,6 +310,9 @@ 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,6 +31,10 @@ 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
@@ -42,8 +46,12 @@ 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
@@ -160,6 +168,9 @@ 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,6 +115,12 @@ 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,17 +21,18 @@ 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()`. 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()`, 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.
## Template parameters
`InputType`
: A compatible input, for instance:
- a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of single-byte characters
- a `#!cpp std::string`, a `#!cpp std::string_view` (C++17), 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.
@@ -47,6 +48,10 @@ owned copy of a borrowable input 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)
@@ -155,3 +160,5 @@ 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,6 +143,12 @@ 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.
@@ -0,0 +1,18 @@
#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';
}
@@ -0,0 +1 @@
true false
@@ -0,0 +1,27 @@
#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';
}
@@ -0,0 +1,6 @@
{"name":"é"}
{"name":"é"}
{"name":"é"}
{"name":"é"}
[1,2,3]
{"name":"é"}
+11
View File
@@ -38,6 +38,16 @@ 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:
@@ -234,3 +244,4 @@ 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
+4 -1
View File
@@ -1,7 +1,9 @@
# 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).
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).
## From C++ values
@@ -101,3 +103,4 @@ 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,6 +287,23 @@ 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,6 +85,16 @@ 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
@@ -93,3 +103,4 @@ a.erase(1); // [1,3,4] (erase by index)
- [`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
+28 -1
View File
@@ -18,6 +18,7 @@ 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
@@ -32,12 +33,18 @@ std::ifstream f("example.json");
json data = json::parse(f);
```
The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value.
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.
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
@@ -98,6 +105,24 @@ 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
@@ -108,6 +133,7 @@ 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
@@ -115,3 +141,4 @@ 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,6 +81,9 @@ 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 linked from a single place elsewhere in the docs, so are collected here.
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.
## Visual Studio (natvis)
+2 -1
View File
@@ -99,7 +99,8 @@ 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.
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information, and
[Debugging](debugging.md) for the debugger integrations of the library.
## Parse errors
+4 -3
View File
@@ -81,8 +81,9 @@ rejected by default, and the library never adds them when serializing.
The library supports **Unicode input** as follows:
- Only **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](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.
- **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.
- 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.
@@ -146,7 +147,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 assumes UTF-8 as encoding. To store a wide string, you need to change the encoding.
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.
!!! example
+7 -3
View File
@@ -6,7 +6,9 @@ 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. See [header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
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
coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
OSS-Fuzz.
@@ -58,7 +60,8 @@ Conda, and more), and pkg-config.
---
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, and more.
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, zero-copy views,
and more.
[:octicons-arrow-right-24: Features](features/index.md)
@@ -74,7 +77,8 @@ Conda, and more), and pkg-config.
---
The complete reference for `basic_json` and its member functions, types, and related classes.
The complete reference for `basic_json` and its member functions, types, and related classes, as well as for
`json_document` and `json_view`.
[:octicons-arrow-right-24: API documentation](api/basic_json/index.md)
@@ -33,7 +33,6 @@
#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
+114 -550
View File
@@ -34,14 +34,6 @@
#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
{
@@ -88,14 +80,11 @@ 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
@@ -170,27 +159,34 @@ 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_quoted(i->first, "\": ");
dump_element(i->second, new_indent, depth + 1);
put_literal_indent(",\n", new_indent);
put_indent(new_indent);
put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_literal(",\n");
}
// 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_quoted(i->first, "\": ");
dump_element(i->second, new_indent, depth + 1);
put_indent(new_indent);
put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_literal_indent("\n", current_indent);
put_char('\n');
put_indent(current_indent);
put_char('}');
}
else
@@ -201,16 +197,20 @@ 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_quoted(i->first, "\":");
dump_element(i->second, current_indent, depth + 1);
put_char('"');
dump_escaped(i->first);
put_literal("\":");
dump_internal(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_quoted(i->first, "\":");
dump_element(i->second, current_indent, depth + 1);
put_char('"');
dump_escaped(i->first);
put_literal("\":");
dump_internal(i->second, current_indent, depth + 1);
put_char('}');
}
@@ -234,24 +234,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
for (auto i = val.m_data.m_value.array->cbegin();
i != val.m_data.m_value.array->cend() - 1; ++i)
{
dump_element(*i, new_indent, depth + 1);
put_literal_indent(",\n", new_indent);
put_indent(new_indent);
dump_internal(*i, new_indent, depth + 1);
put_literal(",\n");
}
// last element
JSON_ASSERT(!val.m_data.m_value.array->empty());
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
put_indent(new_indent);
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
put_literal_indent("\n", current_indent);
put_char('\n');
put_indent(current_indent);
put_char(']');
}
else
@@ -262,13 +265,13 @@ class serializer
for (auto i = val.m_data.m_value.array->cbegin();
i != val.m_data.m_value.array->cend() - 1; ++i)
{
dump_element(*i, current_indent, depth + 1);
dump_internal(*i, current_indent, depth + 1);
put_char(',');
}
// last element
JSON_ASSERT(!val.m_data.m_value.array->empty());
dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1);
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
put_char(']');
}
@@ -291,27 +294,6 @@ 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
@@ -371,13 +353,16 @@ class serializer
put_indent(frame.child_indent);
}
put_char('"');
dump_escaped(frame.object_it->first);
if (pretty_print)
{
put_quoted(frame.object_it->first, "\": ");
put_literal("\": ");
}
else
{
put_quoted(frame.object_it->first, "\":");
put_literal("\":");
}
const BasicJsonType& element = frame.object_it->second;
@@ -552,13 +537,78 @@ class serializer
{
case value_t::string:
{
dump_string_value(*val.m_data.m_value.string);
put_char('"');
dump_escaped(*val.m_data.m_value.string);
put_char('"');
return;
}
case value_t::binary:
{
dump_binary_value(current_indent, *val.m_data.m_value.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}");
}
}
return;
}
@@ -577,19 +627,19 @@ class serializer
case value_t::number_integer:
{
dump_signed_value(val.m_data.m_value.number_integer);
dump_integer(val.m_data.m_value.number_integer);
return;
}
case value_t::number_unsigned:
{
dump_unsigned_value(val.m_data.m_value.number_unsigned);
dump_integer(val.m_data.m_value.number_unsigned);
return;
}
case value_t::number_float:
{
dump_float_value(val.m_data.m_value.number_float);
dump_float(val.m_data.m_value.number_float);
return;
}
@@ -612,107 +662,6 @@ 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
@@ -729,217 +678,6 @@ 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
@@ -952,23 +690,14 @@ 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
*/
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s)
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();
@@ -990,7 +719,6 @@ 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
@@ -1068,7 +796,7 @@ class serializer
put_buffer(string_buffer, bytes);
bytes = 0;
}
put_run(s, i, run);
put_string(s, i, i + run);
bytes_after_last_accept = 0;
undumped_chars = 0;
i += run;
@@ -1108,27 +836,6 @@ 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
@@ -1406,21 +1113,6 @@ 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
@@ -1454,32 +1146,6 @@ 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
@@ -1504,28 +1170,6 @@ 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)
@@ -1768,75 +1412,6 @@ 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
*
@@ -2194,22 +1769,11 @@ 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
uninitialized_buffer<64> number_buffer;
std::array<char, 64> number_buffer{{}};
/// string buffer
uninitialized_buffer<512> string_buffer;
std::array<char, 512> string_buffer{{}};
/// the indentation character
const char indent_char;
@@ -2229,7 +1793,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;
uninitialized_buffer<write_buffer_size> write_buffer;
std::array<char, write_buffer_size> write_buffer{{}};
/// number of valid bytes currently held in @ref write_buffer
std::size_t write_buffer_pos = 0;
};
+114 -551
View File
@@ -30178,14 +30178,6 @@ 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
{
@@ -30232,14 +30224,11 @@ 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
@@ -30314,27 +30303,34 @@ 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_quoted(i->first, "\": ");
dump_element(i->second, new_indent, depth + 1);
put_literal_indent(",\n", new_indent);
put_indent(new_indent);
put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_literal(",\n");
}
// 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_quoted(i->first, "\": ");
dump_element(i->second, new_indent, depth + 1);
put_indent(new_indent);
put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_literal_indent("\n", current_indent);
put_char('\n');
put_indent(current_indent);
put_char('}');
}
else
@@ -30345,16 +30341,20 @@ 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_quoted(i->first, "\":");
dump_element(i->second, current_indent, depth + 1);
put_char('"');
dump_escaped(i->first);
put_literal("\":");
dump_internal(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_quoted(i->first, "\":");
dump_element(i->second, current_indent, depth + 1);
put_char('"');
dump_escaped(i->first);
put_literal("\":");
dump_internal(i->second, current_indent, depth + 1);
put_char('}');
}
@@ -30378,24 +30378,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
for (auto i = val.m_data.m_value.array->cbegin();
i != val.m_data.m_value.array->cend() - 1; ++i)
{
dump_element(*i, new_indent, depth + 1);
put_literal_indent(",\n", new_indent);
put_indent(new_indent);
dump_internal(*i, new_indent, depth + 1);
put_literal(",\n");
}
// last element
JSON_ASSERT(!val.m_data.m_value.array->empty());
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
put_indent(new_indent);
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
put_literal_indent("\n", current_indent);
put_char('\n');
put_indent(current_indent);
put_char(']');
}
else
@@ -30406,13 +30409,13 @@ class serializer
for (auto i = val.m_data.m_value.array->cbegin();
i != val.m_data.m_value.array->cend() - 1; ++i)
{
dump_element(*i, current_indent, depth + 1);
dump_internal(*i, current_indent, depth + 1);
put_char(',');
}
// last element
JSON_ASSERT(!val.m_data.m_value.array->empty());
dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1);
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
put_char(']');
}
@@ -30435,27 +30438,6 @@ 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
@@ -30515,13 +30497,16 @@ class serializer
put_indent(frame.child_indent);
}
put_char('"');
dump_escaped(frame.object_it->first);
if (pretty_print)
{
put_quoted(frame.object_it->first, "\": ");
put_literal("\": ");
}
else
{
put_quoted(frame.object_it->first, "\":");
put_literal("\":");
}
const BasicJsonType& element = frame.object_it->second;
@@ -30696,13 +30681,78 @@ class serializer
{
case value_t::string:
{
dump_string_value(*val.m_data.m_value.string);
put_char('"');
dump_escaped(*val.m_data.m_value.string);
put_char('"');
return;
}
case value_t::binary:
{
dump_binary_value(current_indent, *val.m_data.m_value.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}");
}
}
return;
}
@@ -30721,19 +30771,19 @@ class serializer
case value_t::number_integer:
{
dump_signed_value(val.m_data.m_value.number_integer);
dump_integer(val.m_data.m_value.number_integer);
return;
}
case value_t::number_unsigned:
{
dump_unsigned_value(val.m_data.m_value.number_unsigned);
dump_integer(val.m_data.m_value.number_unsigned);
return;
}
case value_t::number_float:
{
dump_float_value(val.m_data.m_value.number_float);
dump_float(val.m_data.m_value.number_float);
return;
}
@@ -30756,107 +30806,6 @@ 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
@@ -30873,217 +30822,6 @@ 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
@@ -31096,23 +30834,14 @@ 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
*/
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s)
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();
@@ -31134,7 +30863,6 @@ 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
@@ -31212,7 +30940,7 @@ class serializer
put_buffer(string_buffer, bytes);
bytes = 0;
}
put_run(s, i, run);
put_string(s, i, i + run);
bytes_after_last_accept = 0;
undumped_chars = 0;
i += run;
@@ -31252,27 +30980,6 @@ 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
@@ -31550,21 +31257,6 @@ 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
@@ -31598,32 +31290,6 @@ 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
@@ -31648,28 +31314,6 @@ 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)
@@ -31912,75 +31556,6 @@ 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
*
@@ -32338,22 +31913,11 @@ 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
uninitialized_buffer<64> number_buffer;
std::array<char, 64> number_buffer{{}};
/// string buffer
uninitialized_buffer<512> string_buffer;
std::array<char, 512> string_buffer{{}};
/// the indentation character
const char indent_char;
@@ -32373,7 +31937,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;
uninitialized_buffer<write_buffer_size> write_buffer;
std::array<char, write_buffer_size> write_buffer{{}};
/// number of valid bytes currently held in @ref write_buffer
std::size_t write_buffer_pos = 0;
};
@@ -41137,7 +40701,6 @@ 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,94 +1580,3 @@ 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);
}
}
}
}