mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 22:08:55 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5464ccab0e | ||
|
|
0a4c1a2d42 |
No files matched your search
@@ -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
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
the iterator type with `operator!=`.
|
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
|
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.
|
(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 FILE` pointer (throws if null)
|
||||||
- a C-style array of characters
|
- a C-style array of characters
|
||||||
- a pointer to a null-terminated string of single byte characters (throws if null)
|
- 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
|
- 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`)
|
(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`
|
`IteratorType`
|
||||||
: a compatible iterator type, for instance.
|
: 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"
|
--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
|
## See also
|
||||||
|
|
||||||
- [parse](parse.md) - deserialize from a compatible input
|
- [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)).
|
([`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
|
- 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)).
|
([`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"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,22 @@ are collected in [Template Parameter Requirements](../../features/types/template
|
|||||||
- [**json**](../json.md) - default specialization
|
- [**json**](../json.md) - default specialization
|
||||||
- [**ordered_json**](../ordered_json.md) - a specialization that maintains the insertion order of object keys
|
- [**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
|
## Iterator invalidation
|
||||||
|
|
||||||
All operations that add values to an **array** ([`push_back`](push_back.md) , [`operator+=`](operator+=.md),
|
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
|
## See also
|
||||||
|
|
||||||
- [RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc8259)
|
- [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
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
the iterator type with `operator!=`.
|
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
|
## Template parameters
|
||||||
|
|
||||||
`InputType`
|
`InputType`
|
||||||
@@ -34,9 +38,12 @@ static basic_json parse(IteratorType first, SentinelType last,
|
|||||||
- a `FILE` pointer (throws if null)
|
- a `FILE` pointer (throws if null)
|
||||||
- a C-style array of characters
|
- a C-style array of characters
|
||||||
- a pointer to a null-terminated string of single byte characters (throws if null)
|
- 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
|
- 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`)
|
(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`
|
`IteratorType`
|
||||||
: a compatible iterator type, for instance.
|
: 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
|
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.
|
[`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"
|
!!! tip "Performance"
|
||||||
|
|
||||||
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
|
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"
|
--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
|
## See also
|
||||||
|
|
||||||
- [accept](accept.md) - check if the input is valid JSON
|
- [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).
|
time).
|
||||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
- 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`).
|
([`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"
|
!!! 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
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
the iterator type with `operator!=`.
|
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).
|
The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
@@ -42,8 +46,12 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
- a `FILE` pointer
|
- a `FILE` pointer
|
||||||
- a C-style array of characters
|
- a C-style array of characters
|
||||||
- a pointer to a null-terminated string of single byte 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
|
- 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`)
|
(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`
|
`IteratorType`
|
||||||
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
|
: 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)).
|
([`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
|
- 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)).
|
([`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"
|
!!! 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
|
[`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.
|
`#!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
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
- 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
|
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
|
`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
|
the input) instead of a tree of `BasicJsonType` values. They take the same inputs as `BasicJsonType::parse()`, plus a
|
||||||
inputs that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous
|
pointer to a null-terminated wide string, which `BasicJsonType::parse()` does not accept. Byte inputs that stay alive
|
||||||
iterators) are read into a copy that the document owns -- see the Notes. Use [`parse_copy()`](parse_copy.md) to get an
|
can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous iterators) are read into a copy
|
||||||
owned copy of a borrowable input as well.
|
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
|
## Template parameters
|
||||||
|
|
||||||
`InputType`
|
`InputType`
|
||||||
: A compatible input, for instance:
|
: 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`,
|
- a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`,
|
||||||
`#!cpp unsigned char`, `#!cpp std::uint8_t`)
|
`#!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.
|
- 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
|
`#!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
|
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
|
## Parameters
|
||||||
|
|
||||||
`input` (in)
|
`input` (in)
|
||||||
@@ -155,3 +160,5 @@ buffer of a given length, pass a pair of pointers: `#!cpp parse(ptr, ptr + len)`
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
- 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
|
- [**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
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
- 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":"é"}
|
||||||
@@ -38,6 +38,16 @@ which avoids repeating it:
|
|||||||
--8<-- "examples/get_to.output"
|
--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
|
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
|
`#!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:
|
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
|
- [`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
|
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
|
||||||
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
|
- [`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,7 +1,9 @@
|
|||||||
# Creating JSON values
|
# 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
|
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
|
## From C++ values
|
||||||
|
|
||||||
@@ -101,3 +103,4 @@ string `#!json "42"`.
|
|||||||
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
|
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
|
||||||
- [Converting values](conversions.md) - which C++ types can be used
|
- [Converting values](conversions.md) - which C++ types can be used
|
||||||
- [Parsing](parsing/index.md) - create a value from JSON text
|
- [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
|
||||||
@@ -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).
|
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_*(...)`
|
## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)`
|
||||||
|
|
||||||
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
|
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)
|
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
|
## See also
|
||||||
|
|
||||||
- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array
|
- [`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
|
- [`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
|
- [`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
|
- [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
|
||||||
@@ -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
|
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 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 std::istream` (e.g., an `#!cpp std::ifstream` reading from a file),
|
||||||
- a `#!cpp FILE*` pointer,
|
- a `#!cpp FILE*` pointer,
|
||||||
- a pair of iterators over a contiguous range (e.g., a `#!cpp std::vector<std::uint8_t>`), or
|
- 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);
|
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.
|
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`
|
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).
|
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
|
## Strictness and trailing data
|
||||||
|
|
||||||
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
|
[`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
|
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.
|
[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
|
## Exceptions
|
||||||
|
|
||||||
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
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
|
## See also
|
||||||
|
|
||||||
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
|
- [`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
|
- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON
|
||||||
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||||
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
|
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
|
||||||
@@ -115,3 +141,4 @@ options.
|
|||||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||||
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
- [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
|
- 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").
|
[`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
|
- In a binary format, a string that is not valid UTF-8 is a
|
||||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
|
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Debugging
|
# Debugging
|
||||||
|
|
||||||
This page collects the library's built-in debugger integrations and other debugging-related features. They are
|
This page collects the library's built-in debugger integrations and other debugging-related features. They are not
|
||||||
not linked from a single place elsewhere in the docs, so are collected here.
|
covered by any single page elsewhere in the docs, so they are collected here.
|
||||||
|
|
||||||
## Visual Studio (natvis)
|
## Visual Studio (natvis)
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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
|
## Parse errors
|
||||||
|
|
||||||
|
|||||||
@@ -81,8 +81,9 @@ rejected by default, and the library never adds them when serializing.
|
|||||||
|
|
||||||
The library supports **Unicode input** as follows:
|
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).
|
- **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.
|
- `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 a `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.
|
- 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).
|
- 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.
|
- 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?
|
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
|
!!! example
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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
|
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
|
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
|
coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
|
||||||
OSS-Fuzz.
|
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)
|
[: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)
|
[:octicons-arrow-right-24: API documentation](api/basic_json/index.md)
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class serializer
|
|||||||
|
|
||||||
None of @a pretty_print_, @a ensure_ascii_ and @a indent_step_ change over
|
None of @a pretty_print_, @a ensure_ascii_ and @a indent_step_ change over
|
||||||
the life of the serializer, so they are captured once here instead of
|
the life of the serializer, so they are captured once here instead of
|
||||||
being threaded through every call to @ref dump, @ref dump_container and
|
being threaded through every call to @ref dump, @ref dump_internal and
|
||||||
@ref dump_iteratively.
|
@ref dump_iteratively.
|
||||||
*/
|
*/
|
||||||
serializer(output_adapter_protocol<char>& s, const char ichar,
|
serializer(output_adapter_protocol<char>& s, const char ichar,
|
||||||
@@ -80,14 +80,11 @@ class serializer
|
|||||||
const std::size_t indent_step_ = 0,
|
const std::size_t indent_step_ = 0,
|
||||||
error_handler_t error_handler_ = error_handler_t::strict)
|
error_handler_t error_handler_ = error_handler_t::strict)
|
||||||
: o(&s)
|
: o(&s)
|
||||||
, number_buffer() // NOLINT(readability-redundant-member-init)
|
|
||||||
, string_buffer() // NOLINT(readability-redundant-member-init)
|
|
||||||
, indent_char(ichar)
|
, indent_char(ichar)
|
||||||
, pretty_print(pretty_print_)
|
, pretty_print(pretty_print_)
|
||||||
, ensure_ascii(ensure_ascii_)
|
, ensure_ascii(ensure_ascii_)
|
||||||
, indent_step(indent_step_)
|
, indent_step(indent_step_)
|
||||||
, error_handler(error_handler_)
|
, error_handler(error_handler_)
|
||||||
, write_buffer() // NOLINT(readability-redundant-member-init)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// deleted because of pointer members
|
// deleted because of pointer members
|
||||||
@@ -118,18 +115,18 @@ class serializer
|
|||||||
void dump(const BasicJsonType& val,
|
void dump(const BasicJsonType& val,
|
||||||
const std::size_t current_indent = 0)
|
const std::size_t current_indent = 0)
|
||||||
{
|
{
|
||||||
dump_element(val, current_indent, 0);
|
dump_internal(val, current_indent);
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief serialize the object or array @a val
|
@brief worker for @ref dump
|
||||||
|
|
||||||
Writes into the serializer's internal @ref write_buffer instead of issuing
|
Identical in behavior to the historical @ref dump, but writes into the
|
||||||
a virtual call per token; the public @ref dump flushes the buffer once the
|
serializer's internal @ref write_buffer instead of issuing a virtual call
|
||||||
top-level value has been serialized. Scalars are written by @ref dump_scalar,
|
per token. The public @ref dump wraps this and flushes the buffer once the
|
||||||
which @ref dump_element calls without entering this function.
|
top-level value has been serialized.
|
||||||
|
|
||||||
Serializing a container descends into its elements, so a value nested deeply
|
Serializing a container descends into its elements, so a value nested deeply
|
||||||
enough used to exhaust the call stack and terminate the process with no
|
enough used to exhaust the call stack and terminate the process with no
|
||||||
@@ -140,20 +137,20 @@ class serializer
|
|||||||
|
|
||||||
@sa https://github.com/nlohmann/json/issues/5387
|
@sa https://github.com/nlohmann/json/issues/5387
|
||||||
*/
|
*/
|
||||||
void dump_container(const BasicJsonType& val,
|
void dump_internal(const BasicJsonType& val,
|
||||||
const std::size_t current_indent,
|
const std::size_t current_indent = 0,
|
||||||
const std::size_t depth)
|
const std::size_t depth = 0)
|
||||||
|
{
|
||||||
|
switch (val.m_data.m_type)
|
||||||
|
{
|
||||||
|
case value_t::object:
|
||||||
{
|
{
|
||||||
JSON_ASSERT(val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array);
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
dump_iteratively(val, current_indent);
|
dump_iteratively(val, current_indent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.m_data.m_type == value_t::object)
|
|
||||||
{
|
|
||||||
if (val.m_data.m_value.object->empty())
|
if (val.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
put_literal("{}");
|
put_literal("{}");
|
||||||
@@ -162,27 +159,34 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
|
put_literal("{\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
put_literal_indent("{\n", new_indent);
|
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
auto i = val.m_data.m_value.object->cbegin();
|
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)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_object_key(i->first, "\": ");
|
put_indent(new_indent);
|
||||||
dump_element(i->second, new_indent, depth + 1);
|
put_char('"');
|
||||||
put_literal_indent(",\n", new_indent);
|
dump_escaped(i->first);
|
||||||
|
put_literal("\": ");
|
||||||
|
dump_internal(i->second, new_indent, depth + 1);
|
||||||
|
put_literal(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_object_key(i->first, "\": ");
|
put_indent(new_indent);
|
||||||
dump_element(i->second, new_indent, depth + 1);
|
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('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -193,19 +197,32 @@ class serializer
|
|||||||
auto i = val.m_data.m_value.object->cbegin();
|
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)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_object_key(i->first, "\":");
|
put_char('"');
|
||||||
dump_element(i->second, 0, depth + 1);
|
dump_escaped(i->first);
|
||||||
|
put_literal("\":");
|
||||||
|
dump_internal(i->second, current_indent, depth + 1);
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_object_key(i->first, "\":");
|
put_char('"');
|
||||||
dump_element(i->second, 0, depth + 1);
|
dump_escaped(i->first);
|
||||||
|
put_literal("\":");
|
||||||
|
dump_internal(i->second, current_indent, depth + 1);
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::array:
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||||
|
{
|
||||||
|
dump_iteratively(val, current_indent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,24 +234,27 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
|
put_literal("[\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
put_literal_indent("[\n", new_indent);
|
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_element(*i, new_indent, depth + 1);
|
put_indent(new_indent);
|
||||||
put_literal_indent(",\n", new_indent);
|
dump_internal(*i, new_indent, depth + 1);
|
||||||
|
put_literal(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
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(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -245,44 +265,39 @@ class serializer
|
|||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_element(*i, 0, depth + 1);
|
dump_internal(*i, current_indent, depth + 1);
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
dump_element(val.m_data.m_value.array->back(), 0, depth + 1);
|
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
|
||||||
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::string:
|
||||||
|
case value_t::binary:
|
||||||
|
case value_t::boolean:
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
case value_t::number_float:
|
||||||
|
case value_t::discarded:
|
||||||
|
case value_t::null:
|
||||||
|
default:
|
||||||
|
dump_scalar(val, current_indent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/*!
|
|
||||||
@brief serialize an element of a container
|
|
||||||
|
|
||||||
Most elements are scalars, which need none of the state dump_container() 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_container(val, current_indent, depth);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dump_scalar(val, current_indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief write out @a val and everything below it without the call stack
|
@brief write out @a val and everything below it without the call stack
|
||||||
|
|
||||||
Emits the same bytes as @ref dump_container, keeping the containers it has
|
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
||||||
entered on an explicit stack instead of descending into them. Only reached
|
entered on an explicit stack instead of descending into them. Only reached
|
||||||
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
||||||
written for speed: walking every value this way measured up to 20% slower on
|
written for speed: walking every value this way measured up to 20% slower on
|
||||||
@@ -310,7 +325,8 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal_indent("\n", frame.current_indent);
|
put_char('\n');
|
||||||
|
put_indent(frame.current_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
@@ -337,13 +353,16 @@ class serializer
|
|||||||
put_indent(frame.child_indent);
|
put_indent(frame.child_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
put_char('"');
|
||||||
|
dump_escaped(frame.object_it->first);
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_object_key(frame.object_it->first, "\": ");
|
put_literal("\": ");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_object_key(frame.object_it->first, "\":");
|
put_literal("\":");
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicJsonType& element = frame.object_it->second;
|
const BasicJsonType& element = frame.object_it->second;
|
||||||
@@ -362,7 +381,8 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal_indent("\n", frame.current_indent);
|
put_char('\n');
|
||||||
|
put_indent(frame.current_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
@@ -503,7 +523,7 @@ class serializer
|
|||||||
/*!
|
/*!
|
||||||
@brief serialize the value @a val, which is neither an object nor an array
|
@brief serialize the value @a val, which is neither an object nor an array
|
||||||
|
|
||||||
Shared by @ref dump_element and @ref dump_value, so that a value is written
|
Shared by @ref dump_internal and @ref dump_value, so that a value is written
|
||||||
the same way however deeply it is nested. A binary value is written out here
|
the same way however deeply it is nested. A binary value is written out here
|
||||||
in full: it looks like an object, but has no elements to descend into.
|
in full: it looks like an object, but has no elements to descend into.
|
||||||
|
|
||||||
@@ -517,13 +537,78 @@ class serializer
|
|||||||
{
|
{
|
||||||
case value_t::string:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::binary:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,19 +627,19 @@ class serializer
|
|||||||
|
|
||||||
case value_t::number_integer:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_float:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,100 +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_LITTLE_ENDIAN_TARGET
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// variable to hold indentation for the bytes
|
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
|
||||||
|
|
||||||
put_literal_indent("{\n", 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_indent("],\n", new_indent);
|
|
||||||
|
|
||||||
put_literal("\"subtype\": ");
|
|
||||||
if (binary.has_subtype())
|
|
||||||
{
|
|
||||||
dump_integer(binary.subtype());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
put_literal("null");
|
|
||||||
}
|
|
||||||
put_literal_indent("\n", 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
|
@brief the indentation level to use for the children of the current value
|
||||||
@@ -687,220 +678,6 @@ class serializer
|
|||||||
return new_indent;
|
return new_indent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/*!
|
|
||||||
@brief append the object key @a s in quotes, followed by @a tail
|
|
||||||
|
|
||||||
@a tail is the closing quote and the colon, followed by a space when
|
|
||||||
pretty-printing. The callers pass it because each of them already sits on
|
|
||||||
the pretty-printing or the compact path; choosing it here would test
|
|
||||||
@ref pretty_print once more for every key. Most keys are short and plain
|
|
||||||
and are written in one go by put_short_quoted(); everything else takes the
|
|
||||||
general path.
|
|
||||||
*/
|
|
||||||
template<std::size_t N>
|
|
||||||
void put_object_key(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
{
|
|
||||||
#if JSON_LITTLE_ENDIAN_TARGET
|
|
||||||
if (put_short_quoted(s, tail))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
put_char('"');
|
|
||||||
dump_escaped(s);
|
|
||||||
put_literal(tail);
|
|
||||||
}
|
|
||||||
|
|
||||||
// put_short_quoted() assembles words from little-endian loads
|
|
||||||
#if JSON_LITTLE_ENDIAN_TARGET
|
|
||||||
/// 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:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief dump escaped string
|
@brief dump escaped string
|
||||||
@@ -913,23 +690,14 @@ class serializer
|
|||||||
@param[in] s the string to escape
|
@param[in] s the string to escape
|
||||||
|
|
||||||
Complexity: Linear in the length of string @a s.
|
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_object_key
|
|
||||||
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 -
|
// 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.
|
// 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,
|
// For them, this is cheaper than starting the vector scanner below,
|
||||||
// whose setup costs more than such a string takes to scan. The same
|
// whose setup costs more than such a string takes to scan. The same
|
||||||
// check serves both values of ensure_ascii.
|
// 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_LITTLE_ENDIAN_TARGET || !JSON_SCAN_VECTOR
|
|
||||||
if (s.size() <= 32)
|
if (s.size() <= 32)
|
||||||
{
|
{
|
||||||
const auto& copyable = ascii_copyable();
|
const auto& copyable = ascii_copyable();
|
||||||
@@ -951,7 +719,6 @@ class serializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// dispatch once here rather than test the flag inside the loop: it does
|
// 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
|
// not change while a string is written, and folding it lets each of the
|
||||||
@@ -1029,7 +796,7 @@ class serializer
|
|||||||
put_buffer(string_buffer, bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_run(s, i, run);
|
put_string(s, i, i + run);
|
||||||
bytes_after_last_accept = 0;
|
bytes_after_last_accept = 0;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
i += run;
|
i += run;
|
||||||
@@ -1069,27 +836,6 @@ class serializer
|
|||||||
|
|
||||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
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))
|
switch (decode(state, codepoint, byte))
|
||||||
{
|
{
|
||||||
case UTF8_ACCEPT: // decode found a new code point
|
case UTF8_ACCEPT: // decode found a new code point
|
||||||
@@ -1367,12 +1113,6 @@ class serializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the usual indentation, without a call of memset
|
|
||||||
if (JSON_HEDLEY_LIKELY(put_short_literal_indent("", indent)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t capacity = write_buffer.size();
|
const std::size_t capacity = write_buffer.size();
|
||||||
|
|
||||||
// fill whatever room is left in the buffer; this is the whole job
|
// fill whatever room is left in the buffer; this is the whole job
|
||||||
@@ -1406,51 +1146,6 @@ class serializer
|
|||||||
write_buffer_pos = indent;
|
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_short_literal_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)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(put_short_literal_indent(s, indent)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
put_literal(s);
|
|
||||||
put_indent(indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief append a string literal and up to 32 indentation characters in one go
|
|
||||||
|
|
||||||
Four words of the indentation character are stored unconditionally and only
|
|
||||||
the part wanted is claimed, which spares the call of memset. Shared by
|
|
||||||
@ref put_indent (with an empty literal, whose copy compiles to nothing) and
|
|
||||||
@ref put_literal_indent. Returns false, having written nothing, if
|
|
||||||
@a indent is wider or the write buffer lacks the room.
|
|
||||||
*/
|
|
||||||
template<std::size_t N>
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE bool put_short_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 (indent > 32 || write_buffer_pos + length + 32 > write_buffer.size())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
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 true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief append a string literal to the write buffer
|
@brief append a string literal to the write buffer
|
||||||
|
|
||||||
@@ -1475,28 +1170,6 @@ class serializer
|
|||||||
write_buffer_pos += length;
|
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)
|
@brief append the characters of @a str in [@a start, @a end)
|
||||||
|
|
||||||
@@ -1739,78 +1412,6 @@ class serializer
|
|||||||
return escapes;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a lead byte of 0x80 or above starts a sequence of 2 to 4 bytes
|
|
||||||
JSON_ASSERT(length >= 2 && length <= 4);
|
|
||||||
|
|
||||||
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: // 4
|
|
||||||
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
|
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
||||||
*
|
*
|
||||||
@@ -2168,22 +1769,11 @@ class serializer
|
|||||||
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||||
output_adapter_protocol<char>* o = nullptr;
|
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
|
/// a (hopefully) large enough character buffer
|
||||||
uninitialized_buffer<64> number_buffer;
|
std::array<char, 64> number_buffer{{}};
|
||||||
|
|
||||||
/// string buffer
|
/// string buffer
|
||||||
uninitialized_buffer<512> string_buffer;
|
std::array<char, 512> string_buffer{{}};
|
||||||
|
|
||||||
/// the indentation character
|
/// the indentation character
|
||||||
const char indent_char;
|
const char indent_char;
|
||||||
@@ -2203,7 +1793,7 @@ class serializer
|
|||||||
/// buffer collecting output before it is flushed to the output adapter, so
|
/// buffer collecting output before it is flushed to the output adapter, so
|
||||||
/// that the many small structural writes become few bulk writes
|
/// that the many small structural writes become few bulk writes
|
||||||
static constexpr std::size_t write_buffer_size = 1024;
|
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
|
/// number of valid bytes currently held in @ref write_buffer
|
||||||
std::size_t write_buffer_pos = 0;
|
std::size_t write_buffer_pos = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+159
-569
@@ -30215,7 +30215,7 @@ class serializer
|
|||||||
|
|
||||||
None of @a pretty_print_, @a ensure_ascii_ and @a indent_step_ change over
|
None of @a pretty_print_, @a ensure_ascii_ and @a indent_step_ change over
|
||||||
the life of the serializer, so they are captured once here instead of
|
the life of the serializer, so they are captured once here instead of
|
||||||
being threaded through every call to @ref dump, @ref dump_container and
|
being threaded through every call to @ref dump, @ref dump_internal and
|
||||||
@ref dump_iteratively.
|
@ref dump_iteratively.
|
||||||
*/
|
*/
|
||||||
serializer(output_adapter_protocol<char>& s, const char ichar,
|
serializer(output_adapter_protocol<char>& s, const char ichar,
|
||||||
@@ -30224,14 +30224,11 @@ class serializer
|
|||||||
const std::size_t indent_step_ = 0,
|
const std::size_t indent_step_ = 0,
|
||||||
error_handler_t error_handler_ = error_handler_t::strict)
|
error_handler_t error_handler_ = error_handler_t::strict)
|
||||||
: o(&s)
|
: o(&s)
|
||||||
, number_buffer() // NOLINT(readability-redundant-member-init)
|
|
||||||
, string_buffer() // NOLINT(readability-redundant-member-init)
|
|
||||||
, indent_char(ichar)
|
, indent_char(ichar)
|
||||||
, pretty_print(pretty_print_)
|
, pretty_print(pretty_print_)
|
||||||
, ensure_ascii(ensure_ascii_)
|
, ensure_ascii(ensure_ascii_)
|
||||||
, indent_step(indent_step_)
|
, indent_step(indent_step_)
|
||||||
, error_handler(error_handler_)
|
, error_handler(error_handler_)
|
||||||
, write_buffer() // NOLINT(readability-redundant-member-init)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// deleted because of pointer members
|
// deleted because of pointer members
|
||||||
@@ -30262,18 +30259,18 @@ class serializer
|
|||||||
void dump(const BasicJsonType& val,
|
void dump(const BasicJsonType& val,
|
||||||
const std::size_t current_indent = 0)
|
const std::size_t current_indent = 0)
|
||||||
{
|
{
|
||||||
dump_element(val, current_indent, 0);
|
dump_internal(val, current_indent);
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief serialize the object or array @a val
|
@brief worker for @ref dump
|
||||||
|
|
||||||
Writes into the serializer's internal @ref write_buffer instead of issuing
|
Identical in behavior to the historical @ref dump, but writes into the
|
||||||
a virtual call per token; the public @ref dump flushes the buffer once the
|
serializer's internal @ref write_buffer instead of issuing a virtual call
|
||||||
top-level value has been serialized. Scalars are written by @ref dump_scalar,
|
per token. The public @ref dump wraps this and flushes the buffer once the
|
||||||
which @ref dump_element calls without entering this function.
|
top-level value has been serialized.
|
||||||
|
|
||||||
Serializing a container descends into its elements, so a value nested deeply
|
Serializing a container descends into its elements, so a value nested deeply
|
||||||
enough used to exhaust the call stack and terminate the process with no
|
enough used to exhaust the call stack and terminate the process with no
|
||||||
@@ -30284,20 +30281,20 @@ class serializer
|
|||||||
|
|
||||||
@sa https://github.com/nlohmann/json/issues/5387
|
@sa https://github.com/nlohmann/json/issues/5387
|
||||||
*/
|
*/
|
||||||
void dump_container(const BasicJsonType& val,
|
void dump_internal(const BasicJsonType& val,
|
||||||
const std::size_t current_indent,
|
const std::size_t current_indent = 0,
|
||||||
const std::size_t depth)
|
const std::size_t depth = 0)
|
||||||
|
{
|
||||||
|
switch (val.m_data.m_type)
|
||||||
|
{
|
||||||
|
case value_t::object:
|
||||||
{
|
{
|
||||||
JSON_ASSERT(val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array);
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
dump_iteratively(val, current_indent);
|
dump_iteratively(val, current_indent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.m_data.m_type == value_t::object)
|
|
||||||
{
|
|
||||||
if (val.m_data.m_value.object->empty())
|
if (val.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
put_literal("{}");
|
put_literal("{}");
|
||||||
@@ -30306,27 +30303,34 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
|
put_literal("{\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
put_literal_indent("{\n", new_indent);
|
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
auto i = val.m_data.m_value.object->cbegin();
|
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)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_object_key(i->first, "\": ");
|
put_indent(new_indent);
|
||||||
dump_element(i->second, new_indent, depth + 1);
|
put_char('"');
|
||||||
put_literal_indent(",\n", new_indent);
|
dump_escaped(i->first);
|
||||||
|
put_literal("\": ");
|
||||||
|
dump_internal(i->second, new_indent, depth + 1);
|
||||||
|
put_literal(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_object_key(i->first, "\": ");
|
put_indent(new_indent);
|
||||||
dump_element(i->second, new_indent, depth + 1);
|
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('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -30337,19 +30341,32 @@ class serializer
|
|||||||
auto i = val.m_data.m_value.object->cbegin();
|
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)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_object_key(i->first, "\":");
|
put_char('"');
|
||||||
dump_element(i->second, 0, depth + 1);
|
dump_escaped(i->first);
|
||||||
|
put_literal("\":");
|
||||||
|
dump_internal(i->second, current_indent, depth + 1);
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_object_key(i->first, "\":");
|
put_char('"');
|
||||||
dump_element(i->second, 0, depth + 1);
|
dump_escaped(i->first);
|
||||||
|
put_literal("\":");
|
||||||
|
dump_internal(i->second, current_indent, depth + 1);
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::array:
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||||
|
{
|
||||||
|
dump_iteratively(val, current_indent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30361,24 +30378,27 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
|
put_literal("[\n");
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
put_literal_indent("[\n", new_indent);
|
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_element(*i, new_indent, depth + 1);
|
put_indent(new_indent);
|
||||||
put_literal_indent(",\n", new_indent);
|
dump_internal(*i, new_indent, depth + 1);
|
||||||
|
put_literal(",\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
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(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -30389,44 +30409,39 @@ class serializer
|
|||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_element(*i, 0, depth + 1);
|
dump_internal(*i, current_indent, depth + 1);
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
dump_element(val.m_data.m_value.array->back(), 0, depth + 1);
|
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
|
||||||
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::string:
|
||||||
|
case value_t::binary:
|
||||||
|
case value_t::boolean:
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
case value_t::number_float:
|
||||||
|
case value_t::discarded:
|
||||||
|
case value_t::null:
|
||||||
|
default:
|
||||||
|
dump_scalar(val, current_indent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/*!
|
|
||||||
@brief serialize an element of a container
|
|
||||||
|
|
||||||
Most elements are scalars, which need none of the state dump_container() 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_container(val, current_indent, depth);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dump_scalar(val, current_indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief write out @a val and everything below it without the call stack
|
@brief write out @a val and everything below it without the call stack
|
||||||
|
|
||||||
Emits the same bytes as @ref dump_container, keeping the containers it has
|
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
||||||
entered on an explicit stack instead of descending into them. Only reached
|
entered on an explicit stack instead of descending into them. Only reached
|
||||||
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
||||||
written for speed: walking every value this way measured up to 20% slower on
|
written for speed: walking every value this way measured up to 20% slower on
|
||||||
@@ -30454,7 +30469,8 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal_indent("\n", frame.current_indent);
|
put_char('\n');
|
||||||
|
put_indent(frame.current_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
@@ -30481,13 +30497,16 @@ class serializer
|
|||||||
put_indent(frame.child_indent);
|
put_indent(frame.child_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
put_char('"');
|
||||||
|
dump_escaped(frame.object_it->first);
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_object_key(frame.object_it->first, "\": ");
|
put_literal("\": ");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_object_key(frame.object_it->first, "\":");
|
put_literal("\":");
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicJsonType& element = frame.object_it->second;
|
const BasicJsonType& element = frame.object_it->second;
|
||||||
@@ -30506,7 +30525,8 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal_indent("\n", frame.current_indent);
|
put_char('\n');
|
||||||
|
put_indent(frame.current_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
@@ -30647,7 +30667,7 @@ class serializer
|
|||||||
/*!
|
/*!
|
||||||
@brief serialize the value @a val, which is neither an object nor an array
|
@brief serialize the value @a val, which is neither an object nor an array
|
||||||
|
|
||||||
Shared by @ref dump_element and @ref dump_value, so that a value is written
|
Shared by @ref dump_internal and @ref dump_value, so that a value is written
|
||||||
the same way however deeply it is nested. A binary value is written out here
|
the same way however deeply it is nested. A binary value is written out here
|
||||||
in full: it looks like an object, but has no elements to descend into.
|
in full: it looks like an object, but has no elements to descend into.
|
||||||
|
|
||||||
@@ -30661,13 +30681,78 @@ class serializer
|
|||||||
{
|
{
|
||||||
case value_t::string:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::binary:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30686,19 +30771,19 @@ class serializer
|
|||||||
|
|
||||||
case value_t::number_integer:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_float:
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30721,100 +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_LITTLE_ENDIAN_TARGET
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// variable to hold indentation for the bytes
|
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
|
||||||
|
|
||||||
put_literal_indent("{\n", 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_indent("],\n", new_indent);
|
|
||||||
|
|
||||||
put_literal("\"subtype\": ");
|
|
||||||
if (binary.has_subtype())
|
|
||||||
{
|
|
||||||
dump_integer(binary.subtype());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
put_literal("null");
|
|
||||||
}
|
|
||||||
put_literal_indent("\n", 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
|
@brief the indentation level to use for the children of the current value
|
||||||
@@ -30831,220 +30822,6 @@ class serializer
|
|||||||
return new_indent;
|
return new_indent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/*!
|
|
||||||
@brief append the object key @a s in quotes, followed by @a tail
|
|
||||||
|
|
||||||
@a tail is the closing quote and the colon, followed by a space when
|
|
||||||
pretty-printing. The callers pass it because each of them already sits on
|
|
||||||
the pretty-printing or the compact path; choosing it here would test
|
|
||||||
@ref pretty_print once more for every key. Most keys are short and plain
|
|
||||||
and are written in one go by put_short_quoted(); everything else takes the
|
|
||||||
general path.
|
|
||||||
*/
|
|
||||||
template<std::size_t N>
|
|
||||||
void put_object_key(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
{
|
|
||||||
#if JSON_LITTLE_ENDIAN_TARGET
|
|
||||||
if (put_short_quoted(s, tail))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
put_char('"');
|
|
||||||
dump_escaped(s);
|
|
||||||
put_literal(tail);
|
|
||||||
}
|
|
||||||
|
|
||||||
// put_short_quoted() assembles words from little-endian loads
|
|
||||||
#if JSON_LITTLE_ENDIAN_TARGET
|
|
||||||
/// 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:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief dump escaped string
|
@brief dump escaped string
|
||||||
@@ -31057,23 +30834,14 @@ class serializer
|
|||||||
@param[in] s the string to escape
|
@param[in] s the string to escape
|
||||||
|
|
||||||
Complexity: Linear in the length of string @a s.
|
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_object_key
|
|
||||||
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 -
|
// 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.
|
// 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,
|
// For them, this is cheaper than starting the vector scanner below,
|
||||||
// whose setup costs more than such a string takes to scan. The same
|
// whose setup costs more than such a string takes to scan. The same
|
||||||
// check serves both values of ensure_ascii.
|
// 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_LITTLE_ENDIAN_TARGET || !JSON_SCAN_VECTOR
|
|
||||||
if (s.size() <= 32)
|
if (s.size() <= 32)
|
||||||
{
|
{
|
||||||
const auto& copyable = ascii_copyable();
|
const auto& copyable = ascii_copyable();
|
||||||
@@ -31095,7 +30863,6 @@ class serializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// dispatch once here rather than test the flag inside the loop: it does
|
// 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
|
// not change while a string is written, and folding it lets each of the
|
||||||
@@ -31173,7 +30940,7 @@ class serializer
|
|||||||
put_buffer(string_buffer, bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_run(s, i, run);
|
put_string(s, i, i + run);
|
||||||
bytes_after_last_accept = 0;
|
bytes_after_last_accept = 0;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
i += run;
|
i += run;
|
||||||
@@ -31213,27 +30980,6 @@ class serializer
|
|||||||
|
|
||||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
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))
|
switch (decode(state, codepoint, byte))
|
||||||
{
|
{
|
||||||
case UTF8_ACCEPT: // decode found a new code point
|
case UTF8_ACCEPT: // decode found a new code point
|
||||||
@@ -31511,12 +31257,6 @@ class serializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the usual indentation, without a call of memset
|
|
||||||
if (JSON_HEDLEY_LIKELY(put_short_literal_indent("", indent)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t capacity = write_buffer.size();
|
const std::size_t capacity = write_buffer.size();
|
||||||
|
|
||||||
// fill whatever room is left in the buffer; this is the whole job
|
// fill whatever room is left in the buffer; this is the whole job
|
||||||
@@ -31550,51 +31290,6 @@ class serializer
|
|||||||
write_buffer_pos = indent;
|
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_short_literal_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)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(put_short_literal_indent(s, indent)))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
put_literal(s);
|
|
||||||
put_indent(indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief append a string literal and up to 32 indentation characters in one go
|
|
||||||
|
|
||||||
Four words of the indentation character are stored unconditionally and only
|
|
||||||
the part wanted is claimed, which spares the call of memset. Shared by
|
|
||||||
@ref put_indent (with an empty literal, whose copy compiles to nothing) and
|
|
||||||
@ref put_literal_indent. Returns false, having written nothing, if
|
|
||||||
@a indent is wider or the write buffer lacks the room.
|
|
||||||
*/
|
|
||||||
template<std::size_t N>
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE bool put_short_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 (indent > 32 || write_buffer_pos + length + 32 > write_buffer.size())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
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 true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief append a string literal to the write buffer
|
@brief append a string literal to the write buffer
|
||||||
|
|
||||||
@@ -31619,28 +31314,6 @@ class serializer
|
|||||||
write_buffer_pos += length;
|
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)
|
@brief append the characters of @a str in [@a start, @a end)
|
||||||
|
|
||||||
@@ -31883,78 +31556,6 @@ class serializer
|
|||||||
return escapes;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a lead byte of 0x80 or above starts a sequence of 2 to 4 bytes
|
|
||||||
JSON_ASSERT(length >= 2 && length <= 4);
|
|
||||||
|
|
||||||
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: // 4
|
|
||||||
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
|
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
||||||
*
|
*
|
||||||
@@ -32312,22 +31913,11 @@ class serializer
|
|||||||
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||||
output_adapter_protocol<char>* o = nullptr;
|
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
|
/// a (hopefully) large enough character buffer
|
||||||
uninitialized_buffer<64> number_buffer;
|
std::array<char, 64> number_buffer{{}};
|
||||||
|
|
||||||
/// string buffer
|
/// string buffer
|
||||||
uninitialized_buffer<512> string_buffer;
|
std::array<char, 512> string_buffer{{}};
|
||||||
|
|
||||||
/// the indentation character
|
/// the indentation character
|
||||||
const char indent_char;
|
const char indent_char;
|
||||||
@@ -32347,7 +31937,7 @@ class serializer
|
|||||||
/// buffer collecting output before it is flushed to the output adapter, so
|
/// buffer collecting output before it is flushed to the output adapter, so
|
||||||
/// that the many small structural writes become few bulk writes
|
/// that the many small structural writes become few bulk writes
|
||||||
static constexpr std::size_t write_buffer_size = 1024;
|
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
|
/// number of valid bytes currently held in @ref write_buffer
|
||||||
std::size_t write_buffer_pos = 0;
|
std::size_t write_buffer_pos = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user