mirror of
https://github.com/nlohmann/json.git
synced 2026-10-28 08:45:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7d39622a8 | ||
|
|
da83aa92c8 | ||
|
|
9fe832104a | ||
|
|
7ab30dbf37 | ||
|
|
3d70a35bef | ||
|
|
18fdd81212 | ||
|
|
43beef41b9 | ||
|
|
b38c056204 |
No files matched your search
@@ -32,24 +32,9 @@ uses, so the result matches it exactly — including, for an object, that a repe
|
|||||||
last value. The replay is iterative, so it is not limited by the call stack the way a naive recursive conversion
|
last value. The replay is iterative, so it is not limited by the call stack the way a naive recursive conversion
|
||||||
would be; the JSON nesting depth is limited only by available memory, as for `BasicJsonType::parse()` itself.
|
would be; the JSON nesting depth is limited only by available memory, as for `BasicJsonType::parse()` itself.
|
||||||
|
|
||||||
With [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values get the source
|
Unlike parsing with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values
|
||||||
positions [`BasicJsonType::parse()`](../basic_json/parse.md) would give them, also for the subtree of a view (the
|
produced by `materialize()` do not carry source positions. The index has the start offset of every value, but not the
|
||||||
positions are those in the document's whole source text), and exceptions about them name the same bytes. For values of
|
end of arrays, objects, and escaped strings, which the positions also need.
|
||||||
an [editable document](../json_editable_document.md), positions are all-or-nothing, and [`start_pos()`](../basic_json/start_pos.md)
|
|
||||||
and [`end_pos()`](../basic_json/end_pos.md) return `std::string::npos` for
|
|
||||||
|
|
||||||
- a value written by an edit (a copy of an array or object of the document is also new; copies of its scalars keep
|
|
||||||
their positions),
|
|
||||||
- an array or object whose elements were changed (its elements keep theirs), and
|
|
||||||
- an array or object whose last element has no positions (its end is found from that element).
|
|
||||||
|
|
||||||
This differs from a `basic_json` value, whose positions are not updated when it is changed: they stay as they were
|
|
||||||
parsed, even if they no longer match the value (see [`start_pos()`](../basic_json/start_pos.md)).
|
|
||||||
|
|
||||||
Documents [loaded](../basic_json_document/load.md) from an image have the positions of the saved document. Its strings
|
|
||||||
with escapes have positions only if the document was parsed with `JSON_DIAGNOSTIC_POSITIONS`: the position of their
|
|
||||||
opening quote is recorded while parsing, and `save()` and `load()` keep it whether or not they are compiled with the
|
|
||||||
macro.
|
|
||||||
|
|
||||||
Calling `materialize()` on the same view repeatedly builds a new, independent `BasicJsonType` value each time; it
|
Calling `materialize()` on the same view repeatedly builds a new, independent `BasicJsonType` value each time; it
|
||||||
never caches the result.
|
never caches the result.
|
||||||
@@ -77,7 +62,8 @@ never caches the result.
|
|||||||
|
|
||||||
- [`root`](../basic_json_document/root.md) - the view of a document's root value
|
- [`root`](../basic_json_document/root.md) - the view of a document's root value
|
||||||
- [`BasicJsonType::parse`](../basic_json/parse.md) - build a `BasicJsonType` value directly from a JSON text
|
- [`BasicJsonType::parse`](../basic_json/parse.md) - build a `BasicJsonType` value directly from a JSON text
|
||||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values
|
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values (not
|
||||||
|
produced by `materialize()`)
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - when to build a basic_json value from a view
|
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - when to build a basic_json value from a view
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ When the macro is not defined, the library will define it to its default value.
|
|||||||
|
|
||||||
- [:simple-cmake: JSON_Diagnostic_Positions](../../integration/cmake.md#json_diagnostic_positions) - CMake option to control the macro
|
- [:simple-cmake: JSON_Diagnostic_Positions](../../integration/cmake.md#json_diagnostic_positions) - CMake option to control the macro
|
||||||
- [`JSON_DIAGNOSTICS`](json_diagnostics.md) - macro to control extended diagnostics
|
- [`JSON_DIAGNOSTICS`](json_diagnostics.md) - macro to control extended diagnostics
|
||||||
- [`materialize()`](../basic_json_view/materialize.md) - values of a JSON view, with the same positions
|
|
||||||
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input
|
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|||||||
@@ -149,12 +149,10 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
|||||||
end must still fit.
|
end must still fit.
|
||||||
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
|
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
|
||||||
is read to its end into a buffer the document owns, and parsing starts after that.
|
is read to its end into a buffer the document owns, and parsing starts after that.
|
||||||
- **Fewer source positions after edits.** With
|
- **No source positions on `materialize()`.** Even with
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled,
|
[`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled,
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`.
|
[`materialize()`](../api/basic_json_view/materialize.md) does not set them. The index has the start offset of every
|
||||||
In an [editable document](../api/json_editable_document.md), a value written by an edit has none, and neither has
|
value, but not the end of arrays, objects, and escaped strings, which the positions also need.
|
||||||
an array or object whose elements changed or whose last element has none; a `basic_json` value keeps its positions
|
|
||||||
when it is changed. See [`materialize()`](../api/basic_json_view/materialize.md#notes).
|
|
||||||
- **Objects iterate in document order.** [`begin()`](../api/basic_json_view/begin.md)/
|
- **Objects iterate in document order.** [`begin()`](../api/basic_json_view/begin.md)/
|
||||||
[`end()`](../api/basic_json_view/end.md) and [`items()`](../api/basic_json_view/items.md) visit an object's
|
[`end()`](../api/basic_json_view/end.md) and [`items()`](../api/basic_json_view/items.md) visit an object's
|
||||||
members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which
|
members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ packet-beta
|
|||||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 |
|
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 |
|
||||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||||
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself; strings with escapes: the position of the opening quote in the source plus one if the document was parsed with [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), or 0 if it was not; all other values: 0 |
|
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself |
|
||||||
|
|
||||||
<p class="nj-table-caption" markdown>Fields of a node, by their byte offset within the 16-byte node. Integers use bytes 8-15 differently, see below.</p>
|
<p class="nj-table-caption" markdown>Fields of a node, by their byte offset within the 16-byte node. Integers use bytes 8-15 differently, see below.</p>
|
||||||
|
|
||||||
@@ -218,12 +218,6 @@ packet-beta
|
|||||||
step from element to element this way and skip whole subtrees in constant time.
|
step from element to element this way and skip whole subtrees in constant time.
|
||||||
- **Offsets** are 32 bits wide, so a document is limited to 4294967279 bytes, 4 GiB minus 16 bytes (a margin below
|
- **Offsets** are 32 bits wide, so a document is limited to 4294967279 bytes, 4 GiB minus 16 bytes (a margin below
|
||||||
2^32 for positions one scanner step past the end of the text; `out_of_range.416`).
|
2^32 for positions one scanner step past the end of the text; `out_of_range.416`).
|
||||||
- **Source positions** ([`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md)) are derived by
|
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md) and cost almost nothing while parsing: the only work is
|
|
||||||
storing the offset of the opening quote of a string with escapes in its `next`, which is otherwise unused. A
|
|
||||||
scalar's range follows from `off` and its length, a string with escapes is scanned from its opening quote (`next`) to the closing one,
|
|
||||||
and the end of an array or object is found by scanning from the end of its last element over whitespace, comments,
|
|
||||||
and a trailing comma. `materialize()` hands these positions to the SAX builder of `parse()` in place of its lexer.
|
|
||||||
- **Large objects** (128 members or more) get a hash index for their keys after parsing
|
- **Large objects** (128 members or more) get a hash index for their keys after parsing
|
||||||
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
|
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
|
||||||
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
|
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
|
||||||
@@ -283,7 +277,7 @@ Images ([`save`](../api/basic_json_document/save.md) and [`load`](../api/basic_j
|
|||||||
the nodes as they are: a 64-byte header (the magic bytes `NJVI`, a format version, the sizes, and reserved bytes that
|
the nodes as they are: a 64-byte header (the magic bytes `NJVI`, a format version, the sizes, and reserved bytes that
|
||||||
must be zero), the nodes, the text, and the decoded strings. An edited document is first written in document order, as
|
must be zero), the nodes, the text, and the decoded strings. An edited document is first written in document order, as
|
||||||
the parser would have written it (without links), and the numbers of the hash indexes are cleared, since `load`
|
the parser would have written it (without links), and the numbers of the hash indexes are cleared, since `load`
|
||||||
rebuilds the indexes. The flag *new* is kept, as these values have no position in the text. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
rebuilds the indexes. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
||||||
and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them.
|
and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them.
|
||||||
|
|
||||||
## Input adapters
|
## Input adapters
|
||||||
|
|||||||
@@ -230,10 +230,7 @@ struct diagnostic_positions
|
|||||||
{
|
{
|
||||||
// Lexer has read the first character of the container, so
|
// Lexer has read the first character of the container, so
|
||||||
// subtract 1 from the position to get the correct start position.
|
// subtract 1 from the position to get the correct start position.
|
||||||
// (npos: a value without a source position, see materialize() of
|
v.start_position = lexer->get_chars_read_total() - 1;
|
||||||
// json_view)
|
|
||||||
const std::size_t read = lexer->get_chars_read_total();
|
|
||||||
v.start_position = read == std::string::npos ? std::string::npos : read - 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,12 +245,6 @@ struct diagnostic_positions
|
|||||||
{
|
{
|
||||||
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
||||||
v.end_position = lexer->get_chars_read_total();
|
v.end_position = lexer->get_chars_read_total();
|
||||||
if (v.end_position == std::string::npos)
|
|
||||||
{
|
|
||||||
// a range has both ends or none (exception::diagnostics()
|
|
||||||
// prints only complete ones)
|
|
||||||
v.start_position = std::string::npos;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,12 +261,6 @@ struct diagnostic_positions
|
|||||||
// The start position will be set below based on the length of the string representation
|
// The start position will be set below based on the length of the string representation
|
||||||
// of the value.
|
// of the value.
|
||||||
v.end_position = lexer->get_chars_read_total();
|
v.end_position = lexer->get_chars_read_total();
|
||||||
if (v.end_position == std::string::npos)
|
|
||||||
{
|
|
||||||
// a value without a source position
|
|
||||||
v.start_position = std::string::npos;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
{
|
{
|
||||||
@@ -488,12 +473,8 @@ read up to it.
|
|||||||
@tparam Collect whether to collect the values of containers of unknown size on
|
@tparam Collect whether to collect the values of containers of unknown size on
|
||||||
a stack (only for a caller that does not use the value after a
|
a stack (only for a caller that does not use the value after a
|
||||||
parse error)
|
parse error)
|
||||||
@tparam LexerType the type of the lexer that reports the source positions
|
|
||||||
(JSON_DIAGNOSTIC_POSITIONS); only diagnostic_positions uses
|
|
||||||
it, so any type with its three functions works (as
|
|
||||||
materialize() of json_view uses it)
|
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false, typename LexerType = lexer<BasicJsonType, InputAdapterType>>
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false>
|
||||||
class json_sax_dom_parser : public json_sax_dom_error_state
|
class json_sax_dom_parser : public json_sax_dom_error_state
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -502,7 +483,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
using number_float_t = typename BasicJsonType::number_float_t;
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using binary_t = typename BasicJsonType::binary_t;
|
using binary_t = typename BasicJsonType::binary_t;
|
||||||
using lexer_t = LexerType;
|
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in,out] r reference to a JSON value that is manipulated while
|
@param[in,out] r reference to a JSON value that is manipulated while
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ using parser_callback_t =
|
|||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
struct sax_ignores_number_text : std::false_type {};
|
struct sax_ignores_number_text : std::false_type {};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect, LexerType>> : std::true_type {};
|
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
|
|||||||
@@ -173,16 +173,31 @@ class builder
|
|||||||
}
|
}
|
||||||
if (p[1] == '/')
|
if (p[1] == '/')
|
||||||
{
|
{
|
||||||
return skip_line_comment<NulIsEnd>(p + 2, m_end);
|
p += 2;
|
||||||
|
// (as in parse(), a null byte is the end of the input, so it is
|
||||||
|
// left for the caller to see)
|
||||||
|
while (p != m_end && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
if (p[1] == '*')
|
if (p[1] == '*')
|
||||||
{
|
{
|
||||||
p = find_block_comment_end<NulIsEnd>(p + 2, m_end);
|
p += 2;
|
||||||
if (p == m_end || *p != '*')
|
for (;;)
|
||||||
|
{
|
||||||
|
if (p == m_end || (NulIsEnd && *p == 0))
|
||||||
{
|
{
|
||||||
return fail_at(error_code::comment_unterminated, p);
|
return fail_at(error_code::comment_unterminated, p);
|
||||||
}
|
}
|
||||||
return p + 2;
|
if (*p == '*' && p + 1 != m_end && p[1] == '/')
|
||||||
|
{
|
||||||
|
p += 2;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
++p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
++p;
|
++p;
|
||||||
return fail_at(error_code::comment_start, p);
|
return fail_at(error_code::comment_start, p);
|
||||||
@@ -1178,14 +1193,7 @@ indent_done:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
p = r.p;
|
p = r.p;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len);
|
||||||
// the decoded string is in the arena: next keeps the source offset
|
|
||||||
// of the opening quote plus one (0: unknown) for materialize()
|
|
||||||
const std::uint64_t second = static_cast<std::uint64_t>(r.len) | (static_cast<std::uint64_t>(s - b) << 32);
|
|
||||||
#else
|
|
||||||
const std::uint64_t second = r.len;
|
|
||||||
#endif
|
|
||||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, second);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
|
|||||||
/// Copy the current structure of an edited document into nodes in document
|
/// Copy the current structure of an edited document into nodes in document
|
||||||
/// order, as the parser would have written them. Text written by edits is
|
/// order, as the parser would have written them. Text written by edits is
|
||||||
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
||||||
/// are not finite become null, as dump() writes them. Nodes keep the flag
|
/// are not finite become null, as dump() writes them.
|
||||||
/// is_new: they have no source position (JSON_DIAGNOSTIC_POSITIONS).
|
|
||||||
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
|
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
|
||||||
{
|
{
|
||||||
struct frame
|
struct frame
|
||||||
@@ -104,18 +103,16 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
arena_tail.append(d.str(s), s.len);
|
arena_tail.append(d.str(s), s.len);
|
||||||
r.flags = node_flags::escaped;
|
r.flags = node_flags::escaped;
|
||||||
}
|
}
|
||||||
r.flags = static_cast<std::uint8_t>(r.flags | (s.flags & node_flags::is_new));
|
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
const auto copy_node = [&](const node * v)
|
const auto copy_node = [&](const node * v)
|
||||||
{
|
{
|
||||||
node r = *v;
|
node r = *v;
|
||||||
const auto is_new = static_cast<std::uint8_t>(v->flags & node_flags::is_new);
|
|
||||||
switch (static_cast<value_t>(v->kind))
|
switch (static_cast<value_t>(v->kind))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
r.flags = is_new;
|
r.flags = 0;
|
||||||
r.extra = 0;
|
r.extra = 0;
|
||||||
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
||||||
r.len = 0; // counted below
|
r.len = 0; // counted below
|
||||||
@@ -132,7 +129,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||||
text_tail.append(d.str(*v), number_length(*v));
|
text_tail.append(d.str(*v), number_length(*v));
|
||||||
}
|
}
|
||||||
r.flags = is_new;
|
r.flags = 0;
|
||||||
break;
|
break;
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||||
@@ -142,23 +139,22 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
{
|
{
|
||||||
r = node{}; // nan and infinity: null, as dump() writes them
|
r = node{}; // nan and infinity: null, as dump() writes them
|
||||||
r.kind = node_kind::null;
|
r.kind = node_kind::null;
|
||||||
r.flags = is_new;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||||
text_tail.append(t, v->len);
|
text_tail.append(t, v->len);
|
||||||
r.extra = 0xFFFFu; // the digit layout is not recorded
|
r.extra = 0xFFFFu; // the digit layout is not recorded
|
||||||
}
|
}
|
||||||
r.flags = is_new;
|
r.flags = 0;
|
||||||
break;
|
break;
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
r.flags = static_cast<std::uint8_t>(v->flags & (node_flags::is_true | node_flags::is_new));
|
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
|
||||||
break;
|
break;
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
r.flags = is_new;
|
r.flags = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
out.push_back(r);
|
out.push_back(r);
|
||||||
@@ -519,15 +515,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
std::vector<float_range> floats;
|
std::vector<float_range> floats;
|
||||||
const auto check_string = [&](const node & n) -> bool
|
const auto check_string = [&](const node & n) -> bool
|
||||||
{
|
{
|
||||||
if ((n.flags & ~(node_flags::escaped | node_flags::is_new)) != 0 || n.extra != 0)
|
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
||||||
const std::size_t limit = decoded ? arena_size : text_size;
|
const std::size_t limit = decoded ? arena_size : text_size;
|
||||||
// (next: the source offset of a decoded string's opening quote plus
|
if (n.off > limit || n.len > limit - n.off)
|
||||||
// one, or 0; see node)
|
|
||||||
if (n.off > limit || n.len > limit - n.off || n.next > text_size)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -599,13 +593,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
{
|
{
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
// (the offset of a literal is read to size the output of dump())
|
// (the offset of a literal is read to size the output of dump())
|
||||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.off > text_size)
|
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
if ((n.flags & ~(node_flags::is_true | node_flags::is_new)) != 0 || n.extra != 0 || n.off > text_size)
|
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -619,7 +613,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
if ((n.flags & ~node_flags::is_new) != 0 || !number_in_bounds(n))
|
if (n.flags != 0 || !number_in_bounds(n))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -641,7 +635,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
||||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
#include <nlohmann/detail/view/number.hpp>
|
#include <nlohmann/detail/view/number.hpp>
|
||||||
#include <nlohmann/detail/view/scan.hpp>
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -27,189 +26,6 @@ namespace detail
|
|||||||
namespace view
|
namespace view
|
||||||
{
|
{
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
/*!
|
|
||||||
@brief the source positions of the values materialize() replays
|
|
||||||
|
|
||||||
Stands in for the lexer of json_sax_dom_parser, which reads the positions of
|
|
||||||
the values it stores from these three functions (diagnostic_positions). npos
|
|
||||||
is a value without a source position. One position_source serves a whole
|
|
||||||
materialize() call: before each value is handed to the handler, the range of
|
|
||||||
that value is set (or cleared), overwriting that of the value before.
|
|
||||||
*/
|
|
||||||
struct position_source
|
|
||||||
{
|
|
||||||
/// the token, of which diagnostic_positions only reads the length
|
|
||||||
struct token
|
|
||||||
{
|
|
||||||
std::size_t length;
|
|
||||||
std::size_t size() const noexcept
|
|
||||||
{
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::size_t start = std::string::npos; ///< the first byte of the next value
|
|
||||||
std::size_t end = std::string::npos; ///< one past the next value (start_object/start_array: one past the bracket)
|
|
||||||
|
|
||||||
std::size_t get_chars_read_total() const noexcept
|
|
||||||
{
|
|
||||||
return end;
|
|
||||||
}
|
|
||||||
std::size_t get_token_start_position() const noexcept
|
|
||||||
{
|
|
||||||
return start;
|
|
||||||
}
|
|
||||||
token get_string() const noexcept
|
|
||||||
{
|
|
||||||
return token{end - start};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the range [first, last), or none if either is npos
|
|
||||||
void set(std::size_t first, std::size_t last) noexcept
|
|
||||||
{
|
|
||||||
if (first == std::string::npos || last == std::string::npos)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
start = first;
|
|
||||||
end = last;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// no range: the next value has no source position
|
|
||||||
void clear() noexcept
|
|
||||||
{
|
|
||||||
start = std::string::npos;
|
|
||||||
end = std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// one past the closing quote of the string whose opening quote is at
|
|
||||||
/// src[quote], or npos (the scan is bounded by size, as images can be
|
|
||||||
/// loaded without a check)
|
|
||||||
static std::size_t string_end(const char* src, std::size_t size, std::size_t quote) noexcept
|
|
||||||
{
|
|
||||||
for (std::size_t i = quote + 1; i < size; ++i)
|
|
||||||
{
|
|
||||||
if (src[i] == '\\')
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
else if (src[i] == '"')
|
|
||||||
{
|
|
||||||
return i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// one past the closing bracket of the array or object whose last element
|
|
||||||
/// ends at src[pos] (or whose opening bracket is at src[pos - 1]), or npos;
|
|
||||||
/// in between, there can only be whitespace, comments, and a trailing comma
|
|
||||||
/// (the source was validated when it was parsed)
|
|
||||||
static std::size_t container_end(const char* src, std::size_t size, std::size_t pos) noexcept
|
|
||||||
{
|
|
||||||
const auto* const b = reinterpret_cast<const unsigned char*>(src); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the text as bytes, as the builder reads it
|
|
||||||
const unsigned char* const e = b + size;
|
|
||||||
while (pos < size)
|
|
||||||
{
|
|
||||||
switch (src[pos])
|
|
||||||
{
|
|
||||||
case ' ':
|
|
||||||
case '\t':
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
case ',':
|
|
||||||
++pos;
|
|
||||||
break;
|
|
||||||
case ']':
|
|
||||||
case '}':
|
|
||||||
return pos + 1;
|
|
||||||
case '/':
|
|
||||||
if (pos + 1 < size && src[pos + 1] == '/')
|
|
||||||
{
|
|
||||||
pos = static_cast<std::size_t>(skip_line_comment(b + pos + 2, e) - b);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pos + 1 < size && src[pos + 1] == '*')
|
|
||||||
{
|
|
||||||
// (unterminated: pos is size, which ends the loop)
|
|
||||||
pos = static_cast<std::size_t>(find_block_comment_end(b + pos + 2, e) - b) + 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return std::string::npos;
|
|
||||||
default:
|
|
||||||
return std::string::npos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the range of the scalar n of document d
|
|
||||||
void set_scalar(const document_data& d, const node& n) noexcept
|
|
||||||
{
|
|
||||||
if ((n.flags & node_flags::is_new) != 0)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::size_t first = std::string::npos;
|
|
||||||
std::size_t last = std::string::npos;
|
|
||||||
switch (n.kind)
|
|
||||||
{
|
|
||||||
case node_kind::string:
|
|
||||||
if ((n.flags & node_flags::storage) == 0)
|
|
||||||
{
|
|
||||||
// off is the first byte after the opening quote
|
|
||||||
if (n.off != 0)
|
|
||||||
{
|
|
||||||
first = n.off - 1;
|
|
||||||
last = n.off + std::size_t{n.len} + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ((n.flags & node_flags::storage) == node_flags::escaped && n.next != 0)
|
|
||||||
{
|
|
||||||
// next is the offset of the opening quote plus one
|
|
||||||
first = n.next - 1;
|
|
||||||
last = string_end(d.base[0], d.size, first);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case node_kind::number_integer:
|
|
||||||
case node_kind::number_unsigned:
|
|
||||||
case node_kind::number_float:
|
|
||||||
if ((n.flags & node_flags::storage) == 0)
|
|
||||||
{
|
|
||||||
first = n.off;
|
|
||||||
last = n.off + number_length(n);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case node_kind::boolean:
|
|
||||||
first = n.off;
|
|
||||||
last = n.off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u);
|
|
||||||
break;
|
|
||||||
case node_kind::null:
|
|
||||||
first = n.off;
|
|
||||||
last = n.off + 4u;
|
|
||||||
break;
|
|
||||||
case node_kind::object:
|
|
||||||
case node_kind::array:
|
|
||||||
case node_kind::link:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// (a crafted image can point past the text)
|
|
||||||
if (last == std::string::npos || last > d.size)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
set(first, last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the basic_json value of the subtree at n
|
@brief the basic_json value of the subtree at n
|
||||||
|
|
||||||
@@ -217,24 +33,14 @@ The subtree is replayed into the SAX handler that parse() uses to build its
|
|||||||
values, so the result is the value parse() would produce: duplicate keys keep
|
values, so the result is the value parse() would produce: duplicate keys keep
|
||||||
the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is
|
the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is
|
||||||
iterative, so the nesting depth is limited by memory only, as for parse().
|
iterative, so the nesting depth is limited by memory only, as for parse().
|
||||||
|
Without a lexer the handler records no source positions
|
||||||
With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with
|
(JSON_DIAGNOSTIC_POSITIONS).
|
||||||
parse(): position_source stands in for the lexer. Values written by edits have
|
|
||||||
none (npos), and neither has an array or object whose elements changed, or
|
|
||||||
whose last element has none (the end of its closing bracket is found by
|
|
||||||
scanning from the end of the last element).
|
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, bool Editable>
|
template<typename BasicJsonType, bool Editable>
|
||||||
BasicJsonType materialize(const document_data& d, const node* n)
|
BasicJsonType materialize(const document_data& d, const node* n)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using input_t = iterator_input_adapter<const char*>;
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
using lexer_t = position_source;
|
|
||||||
#else
|
|
||||||
using lexer_t = lexer<BasicJsonType, input_t>; // (the default: unused, as no lexer is passed)
|
|
||||||
#endif
|
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, input_t, true, lexer_t>;
|
|
||||||
using nav = navigation<Editable>;
|
using nav = navigation<Editable>;
|
||||||
|
|
||||||
// the open containers: their next element (or key of the next member),
|
// the open containers: their next element (or key of the next member),
|
||||||
@@ -246,15 +52,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
const node* pos;
|
const node* pos;
|
||||||
const node* end;
|
const node* end;
|
||||||
bool object;
|
bool object;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
std::size_t start; ///< the opening bracket (npos: unknown)
|
|
||||||
std::size_t last_end; ///< the end of the last element read (npos: unknown)
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
// a scalar needs neither the handler nor the stack of open containers
|
// a scalar needs neither the handler nor the stack of open containers
|
||||||
// (with positions, it goes through the handler, which sets them)
|
|
||||||
switch (static_cast<value_t>(n->kind))
|
switch (static_cast<value_t>(n->kind))
|
||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
@@ -276,16 +76,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
BasicJsonType result;
|
BasicJsonType result;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
position_source positions;
|
|
||||||
sax_t sax(result, true, &positions);
|
|
||||||
#else
|
|
||||||
sax_t sax(result, true);
|
sax_t sax(result, true);
|
||||||
#endif
|
const string_t no_token{};
|
||||||
const string_t no_token {};
|
|
||||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||||
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
||||||
std::vector<frame> deep{};
|
std::vector<frame> deep{};
|
||||||
@@ -300,16 +94,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
std::size_t depth = 0;
|
std::size_t depth = 0;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
if (!is_container(*n))
|
|
||||||
{
|
|
||||||
positions.set_scalar(d, *n);
|
|
||||||
if (depth != 0)
|
|
||||||
{
|
|
||||||
open[depth - 1].last_end = positions.end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// false positive: n comes from nav::value(), which never returns null for a valid index
|
// false positive: n comes from nav::value(), which never returns null for a valid index
|
||||||
// @infer-ignore NULLPTR_DEREFERENCE
|
// @infer-ignore NULLPTR_DEREFERENCE
|
||||||
switch (static_cast<value_t>(n->kind))
|
switch (static_cast<value_t>(n->kind))
|
||||||
@@ -318,17 +102,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
const bool object = n->kind == node_kind::object;
|
const bool object = n->kind == node_kind::object;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
// (moved containers are also new)
|
|
||||||
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
|
|
||||||
{
|
|
||||||
positions.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
positions.set(std::size_t{n->off}, std::size_t{n->off} + 1);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (object)
|
if (object)
|
||||||
{
|
{
|
||||||
sax.start_object(n->len);
|
sax.start_object(n->len);
|
||||||
@@ -345,13 +118,7 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
open = deep.data();
|
open = deep.data();
|
||||||
capacity = deep.size();
|
capacity = deep.size();
|
||||||
}
|
}
|
||||||
// (with positions: until an element is read, the container's
|
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||||
// own bracket is the last thing read)
|
|
||||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
, positions.start, positions.end
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
@@ -392,31 +159,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
frame& f = open[depth - 1];
|
frame& f = open[depth - 1];
|
||||||
if (f.pos == f.end)
|
if (f.pos == f.end)
|
||||||
{
|
{
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
if (f.start == std::string::npos || f.last_end == std::string::npos)
|
|
||||||
{
|
|
||||||
positions.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const std::size_t end = position_source::container_end(d.base[0], d.size, f.last_end);
|
|
||||||
if (end == std::string::npos)
|
|
||||||
{
|
|
||||||
positions.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// the closing bracket, as the lexer reports it (end_object()
|
|
||||||
// and end_array() only read its end; the start of the
|
|
||||||
// container was set when it was opened)
|
|
||||||
positions.set(end - 1, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (depth > 1)
|
|
||||||
{
|
|
||||||
open[depth - 2].last_end = positions.end;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (f.object)
|
if (f.object)
|
||||||
{
|
{
|
||||||
sax.end_object();
|
sax.end_object();
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ struct node
|
|||||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0
|
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0
|
||||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
||||||
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
||||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence); escaped string: source offset of the opening quote plus one (JSON_DIAGNOSTIC_POSITIONS; 0: unknown)
|
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||||
};
|
};
|
||||||
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
||||||
|
|
||||||
|
|||||||
@@ -172,32 +172,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// advance over the text of a line comment (after its "//") to the line
|
|
||||||
/// break or the end (with NulIsEnd also a null byte, which is left for the
|
|
||||||
/// caller to see, as in parse())
|
|
||||||
template<bool NulIsEnd = false>
|
|
||||||
inline const unsigned char* skip_line_comment(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
|
||||||
{
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the "*/" that ends a block comment whose text starts at p (after its "/*");
|
|
||||||
/// if it is unterminated, the end (with NulIsEnd also a null byte), which is
|
|
||||||
/// where the caller reports the error
|
|
||||||
template<bool NulIsEnd = false>
|
|
||||||
inline const unsigned char* find_block_comment_end(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
while (p != e && !(NulIsEnd && *p == 0) && !(*p == '*' && p + 1 != e && p[1] == '/'))
|
|
||||||
{
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// powers of ten up to 10^19 as integers
|
/// powers of ten up to 10^19 as integers
|
||||||
inline std::uint64_t int_pow10(unsigned k) noexcept
|
inline std::uint64_t int_pow10(unsigned k) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
friend class ::nlohmann::detail::binary_writer;
|
friend class ::nlohmann::detail::binary_writer;
|
||||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||||
friend class ::nlohmann::detail::binary_reader;
|
friend class ::nlohmann::detail::binary_reader;
|
||||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
||||||
|
|||||||
Generated
+6
-25
@@ -15629,10 +15629,7 @@ struct diagnostic_positions
|
|||||||
{
|
{
|
||||||
// Lexer has read the first character of the container, so
|
// Lexer has read the first character of the container, so
|
||||||
// subtract 1 from the position to get the correct start position.
|
// subtract 1 from the position to get the correct start position.
|
||||||
// (npos: a value without a source position, see materialize() of
|
v.start_position = lexer->get_chars_read_total() - 1;
|
||||||
// json_view)
|
|
||||||
const std::size_t read = lexer->get_chars_read_total();
|
|
||||||
v.start_position = read == std::string::npos ? std::string::npos : read - 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15647,12 +15644,6 @@ struct diagnostic_positions
|
|||||||
{
|
{
|
||||||
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
||||||
v.end_position = lexer->get_chars_read_total();
|
v.end_position = lexer->get_chars_read_total();
|
||||||
if (v.end_position == std::string::npos)
|
|
||||||
{
|
|
||||||
// a range has both ends or none (exception::diagnostics()
|
|
||||||
// prints only complete ones)
|
|
||||||
v.start_position = std::string::npos;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15669,12 +15660,6 @@ struct diagnostic_positions
|
|||||||
// The start position will be set below based on the length of the string representation
|
// The start position will be set below based on the length of the string representation
|
||||||
// of the value.
|
// of the value.
|
||||||
v.end_position = lexer->get_chars_read_total();
|
v.end_position = lexer->get_chars_read_total();
|
||||||
if (v.end_position == std::string::npos)
|
|
||||||
{
|
|
||||||
// a value without a source position
|
|
||||||
v.start_position = std::string::npos;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
{
|
{
|
||||||
@@ -15887,12 +15872,8 @@ read up to it.
|
|||||||
@tparam Collect whether to collect the values of containers of unknown size on
|
@tparam Collect whether to collect the values of containers of unknown size on
|
||||||
a stack (only for a caller that does not use the value after a
|
a stack (only for a caller that does not use the value after a
|
||||||
parse error)
|
parse error)
|
||||||
@tparam LexerType the type of the lexer that reports the source positions
|
|
||||||
(JSON_DIAGNOSTIC_POSITIONS); only diagnostic_positions uses
|
|
||||||
it, so any type with its three functions works (as
|
|
||||||
materialize() of json_view uses it)
|
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false, typename LexerType = lexer<BasicJsonType, InputAdapterType>>
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false>
|
||||||
class json_sax_dom_parser : public json_sax_dom_error_state
|
class json_sax_dom_parser : public json_sax_dom_error_state
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -15901,7 +15882,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
using number_float_t = typename BasicJsonType::number_float_t;
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using binary_t = typename BasicJsonType::binary_t;
|
using binary_t = typename BasicJsonType::binary_t;
|
||||||
using lexer_t = LexerType;
|
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in,out] r reference to a JSON value that is manipulated while
|
@param[in,out] r reference to a JSON value that is manipulated while
|
||||||
@@ -22534,8 +22515,8 @@ using parser_callback_t =
|
|||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
struct sax_ignores_number_text : std::false_type {};
|
struct sax_ignores_number_text : std::false_type {};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect, LexerType>> : std::true_type {};
|
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
@@ -36929,7 +36910,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
friend class ::nlohmann::detail::binary_writer;
|
friend class ::nlohmann::detail::binary_writer;
|
||||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||||
friend class ::nlohmann::detail::binary_reader;
|
friend class ::nlohmann::detail::binary_reader;
|
||||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
||||||
|
|||||||
Generated
+38
-321
@@ -269,7 +269,7 @@ struct node
|
|||||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0
|
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0
|
||||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
||||||
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
||||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence); escaped string: source offset of the opening quote plus one (JSON_DIAGNOSTIC_POSITIONS; 0: unknown)
|
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||||
};
|
};
|
||||||
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
||||||
|
|
||||||
@@ -1185,32 +1185,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// advance over the text of a line comment (after its "//") to the line
|
|
||||||
/// break or the end (with NulIsEnd also a null byte, which is left for the
|
|
||||||
/// caller to see, as in parse())
|
|
||||||
template<bool NulIsEnd = false>
|
|
||||||
inline const unsigned char* skip_line_comment(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
|
||||||
{
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the "*/" that ends a block comment whose text starts at p (after its "/*");
|
|
||||||
/// if it is unterminated, the end (with NulIsEnd also a null byte), which is
|
|
||||||
/// where the caller reports the error
|
|
||||||
template<bool NulIsEnd = false>
|
|
||||||
inline const unsigned char* find_block_comment_end(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
while (p != e && !(NulIsEnd && *p == 0) && !(*p == '*' && p + 1 != e && p[1] == '/'))
|
|
||||||
{
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// powers of ten up to 10^19 as integers
|
/// powers of ten up to 10^19 as integers
|
||||||
inline std::uint64_t int_pow10(unsigned k) noexcept
|
inline std::uint64_t int_pow10(unsigned k) noexcept
|
||||||
{
|
{
|
||||||
@@ -1408,16 +1382,31 @@ class builder
|
|||||||
}
|
}
|
||||||
if (p[1] == '/')
|
if (p[1] == '/')
|
||||||
{
|
{
|
||||||
return skip_line_comment<NulIsEnd>(p + 2, m_end);
|
p += 2;
|
||||||
|
// (as in parse(), a null byte is the end of the input, so it is
|
||||||
|
// left for the caller to see)
|
||||||
|
while (p != m_end && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
if (p[1] == '*')
|
if (p[1] == '*')
|
||||||
{
|
{
|
||||||
p = find_block_comment_end<NulIsEnd>(p + 2, m_end);
|
p += 2;
|
||||||
if (p == m_end || *p != '*')
|
for (;;)
|
||||||
|
{
|
||||||
|
if (p == m_end || (NulIsEnd && *p == 0))
|
||||||
{
|
{
|
||||||
return fail_at(error_code::comment_unterminated, p);
|
return fail_at(error_code::comment_unterminated, p);
|
||||||
}
|
}
|
||||||
return p + 2;
|
if (*p == '*' && p + 1 != m_end && p[1] == '/')
|
||||||
|
{
|
||||||
|
p += 2;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
++p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
++p;
|
++p;
|
||||||
return fail_at(error_code::comment_start, p);
|
return fail_at(error_code::comment_start, p);
|
||||||
@@ -2413,14 +2402,7 @@ indent_done:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
p = r.p;
|
p = r.p;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len);
|
||||||
// the decoded string is in the arena: next keeps the source offset
|
|
||||||
// of the opening quote plus one (0: unknown) for materialize()
|
|
||||||
const std::uint64_t second = static_cast<std::uint64_t>(r.len) | (static_cast<std::uint64_t>(s - b) << 32);
|
|
||||||
#else
|
|
||||||
const std::uint64_t second = r.len;
|
|
||||||
#endif
|
|
||||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, second);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -4961,8 +4943,7 @@ constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
|
|||||||
/// Copy the current structure of an edited document into nodes in document
|
/// Copy the current structure of an edited document into nodes in document
|
||||||
/// order, as the parser would have written them. Text written by edits is
|
/// order, as the parser would have written them. Text written by edits is
|
||||||
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
||||||
/// are not finite become null, as dump() writes them. Nodes keep the flag
|
/// are not finite become null, as dump() writes them.
|
||||||
/// is_new: they have no source position (JSON_DIAGNOSTIC_POSITIONS).
|
|
||||||
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
|
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
|
||||||
{
|
{
|
||||||
struct frame
|
struct frame
|
||||||
@@ -4985,18 +4966,16 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
arena_tail.append(d.str(s), s.len);
|
arena_tail.append(d.str(s), s.len);
|
||||||
r.flags = node_flags::escaped;
|
r.flags = node_flags::escaped;
|
||||||
}
|
}
|
||||||
r.flags = static_cast<std::uint8_t>(r.flags | (s.flags & node_flags::is_new));
|
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
const auto copy_node = [&](const node * v)
|
const auto copy_node = [&](const node * v)
|
||||||
{
|
{
|
||||||
node r = *v;
|
node r = *v;
|
||||||
const auto is_new = static_cast<std::uint8_t>(v->flags & node_flags::is_new);
|
|
||||||
switch (static_cast<value_t>(v->kind))
|
switch (static_cast<value_t>(v->kind))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
r.flags = is_new;
|
r.flags = 0;
|
||||||
r.extra = 0;
|
r.extra = 0;
|
||||||
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
||||||
r.len = 0; // counted below
|
r.len = 0; // counted below
|
||||||
@@ -5013,7 +4992,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||||
text_tail.append(d.str(*v), number_length(*v));
|
text_tail.append(d.str(*v), number_length(*v));
|
||||||
}
|
}
|
||||||
r.flags = is_new;
|
r.flags = 0;
|
||||||
break;
|
break;
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||||
@@ -5023,23 +5002,22 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
{
|
{
|
||||||
r = node{}; // nan and infinity: null, as dump() writes them
|
r = node{}; // nan and infinity: null, as dump() writes them
|
||||||
r.kind = node_kind::null;
|
r.kind = node_kind::null;
|
||||||
r.flags = is_new;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||||
text_tail.append(t, v->len);
|
text_tail.append(t, v->len);
|
||||||
r.extra = 0xFFFFu; // the digit layout is not recorded
|
r.extra = 0xFFFFu; // the digit layout is not recorded
|
||||||
}
|
}
|
||||||
r.flags = is_new;
|
r.flags = 0;
|
||||||
break;
|
break;
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
r.flags = static_cast<std::uint8_t>(v->flags & (node_flags::is_true | node_flags::is_new));
|
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
|
||||||
break;
|
break;
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
r.flags = is_new;
|
r.flags = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
out.push_back(r);
|
out.push_back(r);
|
||||||
@@ -5400,15 +5378,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
std::vector<float_range> floats;
|
std::vector<float_range> floats;
|
||||||
const auto check_string = [&](const node & n) -> bool
|
const auto check_string = [&](const node & n) -> bool
|
||||||
{
|
{
|
||||||
if ((n.flags & ~(node_flags::escaped | node_flags::is_new)) != 0 || n.extra != 0)
|
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
||||||
const std::size_t limit = decoded ? arena_size : text_size;
|
const std::size_t limit = decoded ? arena_size : text_size;
|
||||||
// (next: the source offset of a decoded string's opening quote plus
|
if (n.off > limit || n.len > limit - n.off)
|
||||||
// one, or 0; see node)
|
|
||||||
if (n.off > limit || n.len > limit - n.off || n.next > text_size)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -5480,13 +5456,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
{
|
{
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
// (the offset of a literal is read to size the output of dump())
|
// (the offset of a literal is read to size the output of dump())
|
||||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.off > text_size)
|
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
if ((n.flags & ~(node_flags::is_true | node_flags::is_new)) != 0 || n.extra != 0 || n.off > text_size)
|
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -5500,7 +5476,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
if ((n.flags & ~node_flags::is_new) != 0 || !number_in_bounds(n))
|
if (n.flags != 0 || !number_in_bounds(n))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -5522,7 +5498,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
||||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -6125,8 +6101,6 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
// #include <nlohmann/detail/view/number.hpp>
|
// #include <nlohmann/detail/view/number.hpp>
|
||||||
|
|
||||||
// #include <nlohmann/detail/view/scan.hpp>
|
|
||||||
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -6134,189 +6108,6 @@ namespace detail
|
|||||||
namespace view
|
namespace view
|
||||||
{
|
{
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
/*!
|
|
||||||
@brief the source positions of the values materialize() replays
|
|
||||||
|
|
||||||
Stands in for the lexer of json_sax_dom_parser, which reads the positions of
|
|
||||||
the values it stores from these three functions (diagnostic_positions). npos
|
|
||||||
is a value without a source position. One position_source serves a whole
|
|
||||||
materialize() call: before each value is handed to the handler, the range of
|
|
||||||
that value is set (or cleared), overwriting that of the value before.
|
|
||||||
*/
|
|
||||||
struct position_source
|
|
||||||
{
|
|
||||||
/// the token, of which diagnostic_positions only reads the length
|
|
||||||
struct token
|
|
||||||
{
|
|
||||||
std::size_t length;
|
|
||||||
std::size_t size() const noexcept
|
|
||||||
{
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::size_t start = std::string::npos; ///< the first byte of the next value
|
|
||||||
std::size_t end = std::string::npos; ///< one past the next value (start_object/start_array: one past the bracket)
|
|
||||||
|
|
||||||
std::size_t get_chars_read_total() const noexcept
|
|
||||||
{
|
|
||||||
return end;
|
|
||||||
}
|
|
||||||
std::size_t get_token_start_position() const noexcept
|
|
||||||
{
|
|
||||||
return start;
|
|
||||||
}
|
|
||||||
token get_string() const noexcept
|
|
||||||
{
|
|
||||||
return token{end - start};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the range [first, last), or none if either is npos
|
|
||||||
void set(std::size_t first, std::size_t last) noexcept
|
|
||||||
{
|
|
||||||
if (first == std::string::npos || last == std::string::npos)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
start = first;
|
|
||||||
end = last;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// no range: the next value has no source position
|
|
||||||
void clear() noexcept
|
|
||||||
{
|
|
||||||
start = std::string::npos;
|
|
||||||
end = std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// one past the closing quote of the string whose opening quote is at
|
|
||||||
/// src[quote], or npos (the scan is bounded by size, as images can be
|
|
||||||
/// loaded without a check)
|
|
||||||
static std::size_t string_end(const char* src, std::size_t size, std::size_t quote) noexcept
|
|
||||||
{
|
|
||||||
for (std::size_t i = quote + 1; i < size; ++i)
|
|
||||||
{
|
|
||||||
if (src[i] == '\\')
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
else if (src[i] == '"')
|
|
||||||
{
|
|
||||||
return i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// one past the closing bracket of the array or object whose last element
|
|
||||||
/// ends at src[pos] (or whose opening bracket is at src[pos - 1]), or npos;
|
|
||||||
/// in between, there can only be whitespace, comments, and a trailing comma
|
|
||||||
/// (the source was validated when it was parsed)
|
|
||||||
static std::size_t container_end(const char* src, std::size_t size, std::size_t pos) noexcept
|
|
||||||
{
|
|
||||||
const auto* const b = reinterpret_cast<const unsigned char*>(src); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the text as bytes, as the builder reads it
|
|
||||||
const unsigned char* const e = b + size;
|
|
||||||
while (pos < size)
|
|
||||||
{
|
|
||||||
switch (src[pos])
|
|
||||||
{
|
|
||||||
case ' ':
|
|
||||||
case '\t':
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
case ',':
|
|
||||||
++pos;
|
|
||||||
break;
|
|
||||||
case ']':
|
|
||||||
case '}':
|
|
||||||
return pos + 1;
|
|
||||||
case '/':
|
|
||||||
if (pos + 1 < size && src[pos + 1] == '/')
|
|
||||||
{
|
|
||||||
pos = static_cast<std::size_t>(skip_line_comment(b + pos + 2, e) - b);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pos + 1 < size && src[pos + 1] == '*')
|
|
||||||
{
|
|
||||||
// (unterminated: pos is size, which ends the loop)
|
|
||||||
pos = static_cast<std::size_t>(find_block_comment_end(b + pos + 2, e) - b) + 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return std::string::npos;
|
|
||||||
default:
|
|
||||||
return std::string::npos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the range of the scalar n of document d
|
|
||||||
void set_scalar(const document_data& d, const node& n) noexcept
|
|
||||||
{
|
|
||||||
if ((n.flags & node_flags::is_new) != 0)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::size_t first = std::string::npos;
|
|
||||||
std::size_t last = std::string::npos;
|
|
||||||
switch (n.kind)
|
|
||||||
{
|
|
||||||
case node_kind::string:
|
|
||||||
if ((n.flags & node_flags::storage) == 0)
|
|
||||||
{
|
|
||||||
// off is the first byte after the opening quote
|
|
||||||
if (n.off != 0)
|
|
||||||
{
|
|
||||||
first = n.off - 1;
|
|
||||||
last = n.off + std::size_t{n.len} + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ((n.flags & node_flags::storage) == node_flags::escaped && n.next != 0)
|
|
||||||
{
|
|
||||||
// next is the offset of the opening quote plus one
|
|
||||||
first = n.next - 1;
|
|
||||||
last = string_end(d.base[0], d.size, first);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case node_kind::number_integer:
|
|
||||||
case node_kind::number_unsigned:
|
|
||||||
case node_kind::number_float:
|
|
||||||
if ((n.flags & node_flags::storage) == 0)
|
|
||||||
{
|
|
||||||
first = n.off;
|
|
||||||
last = n.off + number_length(n);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case node_kind::boolean:
|
|
||||||
first = n.off;
|
|
||||||
last = n.off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u);
|
|
||||||
break;
|
|
||||||
case node_kind::null:
|
|
||||||
first = n.off;
|
|
||||||
last = n.off + 4u;
|
|
||||||
break;
|
|
||||||
case node_kind::object:
|
|
||||||
case node_kind::array:
|
|
||||||
case node_kind::link:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// (a crafted image can point past the text)
|
|
||||||
if (last == std::string::npos || last > d.size)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
set(first, last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the basic_json value of the subtree at n
|
@brief the basic_json value of the subtree at n
|
||||||
|
|
||||||
@@ -6324,24 +6115,14 @@ The subtree is replayed into the SAX handler that parse() uses to build its
|
|||||||
values, so the result is the value parse() would produce: duplicate keys keep
|
values, so the result is the value parse() would produce: duplicate keys keep
|
||||||
the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is
|
the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is
|
||||||
iterative, so the nesting depth is limited by memory only, as for parse().
|
iterative, so the nesting depth is limited by memory only, as for parse().
|
||||||
|
Without a lexer the handler records no source positions
|
||||||
With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with
|
(JSON_DIAGNOSTIC_POSITIONS).
|
||||||
parse(): position_source stands in for the lexer. Values written by edits have
|
|
||||||
none (npos), and neither has an array or object whose elements changed, or
|
|
||||||
whose last element has none (the end of its closing bracket is found by
|
|
||||||
scanning from the end of the last element).
|
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, bool Editable>
|
template<typename BasicJsonType, bool Editable>
|
||||||
BasicJsonType materialize(const document_data& d, const node* n)
|
BasicJsonType materialize(const document_data& d, const node* n)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using input_t = iterator_input_adapter<const char*>;
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
using lexer_t = position_source;
|
|
||||||
#else
|
|
||||||
using lexer_t = lexer<BasicJsonType, input_t>; // (the default: unused, as no lexer is passed)
|
|
||||||
#endif
|
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, input_t, true, lexer_t>;
|
|
||||||
using nav = navigation<Editable>;
|
using nav = navigation<Editable>;
|
||||||
|
|
||||||
// the open containers: their next element (or key of the next member),
|
// the open containers: their next element (or key of the next member),
|
||||||
@@ -6353,15 +6134,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
const node* pos;
|
const node* pos;
|
||||||
const node* end;
|
const node* end;
|
||||||
bool object;
|
bool object;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
std::size_t start; ///< the opening bracket (npos: unknown)
|
|
||||||
std::size_t last_end; ///< the end of the last element read (npos: unknown)
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
// a scalar needs neither the handler nor the stack of open containers
|
// a scalar needs neither the handler nor the stack of open containers
|
||||||
// (with positions, it goes through the handler, which sets them)
|
|
||||||
switch (static_cast<value_t>(n->kind))
|
switch (static_cast<value_t>(n->kind))
|
||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
@@ -6383,16 +6158,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
BasicJsonType result;
|
BasicJsonType result;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
position_source positions;
|
|
||||||
sax_t sax(result, true, &positions);
|
|
||||||
#else
|
|
||||||
sax_t sax(result, true);
|
sax_t sax(result, true);
|
||||||
#endif
|
const string_t no_token{};
|
||||||
const string_t no_token {};
|
|
||||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||||
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
||||||
std::vector<frame> deep{};
|
std::vector<frame> deep{};
|
||||||
@@ -6407,16 +6176,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
std::size_t depth = 0;
|
std::size_t depth = 0;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
if (!is_container(*n))
|
|
||||||
{
|
|
||||||
positions.set_scalar(d, *n);
|
|
||||||
if (depth != 0)
|
|
||||||
{
|
|
||||||
open[depth - 1].last_end = positions.end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// false positive: n comes from nav::value(), which never returns null for a valid index
|
// false positive: n comes from nav::value(), which never returns null for a valid index
|
||||||
// @infer-ignore NULLPTR_DEREFERENCE
|
// @infer-ignore NULLPTR_DEREFERENCE
|
||||||
switch (static_cast<value_t>(n->kind))
|
switch (static_cast<value_t>(n->kind))
|
||||||
@@ -6425,17 +6184,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
const bool object = n->kind == node_kind::object;
|
const bool object = n->kind == node_kind::object;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
// (moved containers are also new)
|
|
||||||
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
|
|
||||||
{
|
|
||||||
positions.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
positions.set(std::size_t{n->off}, std::size_t{n->off} + 1);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (object)
|
if (object)
|
||||||
{
|
{
|
||||||
sax.start_object(n->len);
|
sax.start_object(n->len);
|
||||||
@@ -6452,13 +6200,7 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
open = deep.data();
|
open = deep.data();
|
||||||
capacity = deep.size();
|
capacity = deep.size();
|
||||||
}
|
}
|
||||||
// (with positions: until an element is read, the container's
|
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||||
// own bracket is the last thing read)
|
|
||||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
, positions.start, positions.end
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
@@ -6499,31 +6241,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
frame& f = open[depth - 1];
|
frame& f = open[depth - 1];
|
||||||
if (f.pos == f.end)
|
if (f.pos == f.end)
|
||||||
{
|
{
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
if (f.start == std::string::npos || f.last_end == std::string::npos)
|
|
||||||
{
|
|
||||||
positions.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const std::size_t end = position_source::container_end(d.base[0], d.size, f.last_end);
|
|
||||||
if (end == std::string::npos)
|
|
||||||
{
|
|
||||||
positions.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// the closing bracket, as the lexer reports it (end_object()
|
|
||||||
// and end_array() only read its end; the start of the
|
|
||||||
// container was set when it was opened)
|
|
||||||
positions.set(end - 1, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (depth > 1)
|
|
||||||
{
|
|
||||||
open[depth - 2].last_end = positions.end;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (f.object)
|
if (f.object)
|
||||||
{
|
{
|
||||||
sax.end_object();
|
sax.end_object();
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ python3 tests/benchmarks/json_view/compare.py --data <json_test_data directory>
|
|||||||
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
|
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
|
||||||
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
|
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
|
||||||
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
|
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
|
||||||
|
- `--aligned` additionally builds and runs everything with all functions aligned to 64 bytes (`-mllvm
|
||||||
|
-align-all-functions=6` for Clang, `-falign-functions=64 -falign-loops=64` for GCC), and reports both result
|
||||||
|
tables as "default build" and "aligned build". Code layout alone can move a result by a few percent; a difference
|
||||||
|
that appears in only one of the two tables is a layout artifact. Off by default, which doubles the run time.
|
||||||
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
|
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
|
||||||
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
|
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
|
||||||
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
|
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
|
||||||
@@ -39,7 +43,7 @@ where `json_view` stands on another architecture; they are not meant for publica
|
|||||||
|
|
||||||
## What is measured
|
## What is measured
|
||||||
|
|
||||||
`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
`bench_view.cpp` runs these workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||||
JSON-RPC request (`rpc`):
|
JSON-RPC request (`rpc`):
|
||||||
|
|
||||||
| workload | what it does |
|
| workload | what it does |
|
||||||
@@ -48,6 +52,7 @@ JSON-RPC request (`rpc`):
|
|||||||
| traverse | parse, then visit every value, convert every number, touch every string and key |
|
| traverse | parse, then visit every value, convert every number, touch every string and key |
|
||||||
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
|
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
|
||||||
| dump | serialize a parsed document (compact) |
|
| dump | serialize a parsed document (compact) |
|
||||||
|
| parse+dump | build a fresh document, then serialize it (compact), in one timed call |
|
||||||
|
|
||||||
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
|
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
|
||||||
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
|
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
|
||||||
@@ -72,11 +77,32 @@ call follows an untimed call of the same engine: otherwise the engine after `jso
|
|||||||
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
|
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
|
||||||
slower on citm_catalog traverse).
|
slower on citm_catalog traverse).
|
||||||
|
|
||||||
|
`json_view` is an index into the source text, not a tree of owning nodes. Its like-for-like peers are yyjson and
|
||||||
|
simdjson DOM (immutable, indexed, no per-node allocation). Boost.JSON and `json::parse` build mutable, owning DOMs and
|
||||||
|
are reference points for what that costs, not equals; simdjson On-Demand is lazy and forward-only, so it is the other
|
||||||
|
way round: it has no random access to pay for. All engines validate UTF-8 and syntax by default.
|
||||||
|
|
||||||
|
Number conversion differs: `json_view` converts integers while parsing but converts floating-point numbers lazily,
|
||||||
|
when they are read or dumped; yyjson (default flags) and simdjson DOM convert every number while parsing. The cost of
|
||||||
|
float conversion therefore appears in the traverse and dump rows for `json_view`, and in the parse row for the others.
|
||||||
|
For float-heavy documents (canada), the fair reading is a sum of parse and use: the `traverse` workload already
|
||||||
|
includes the parse, and `parse+dump` measures a fresh document and its dump in one timed call; the `dump` row alone
|
||||||
|
leaves the conversion in `json_view` and out of the others. Eager conversion at parse time was prototyped as an opt-in
|
||||||
|
and measured: canada traverse (with its parse) was 4-7 % faster, but parse+dump 11-30 % slower and parse 36-90 % slower. It was
|
||||||
|
dropped, so lazy conversion is the only mode.
|
||||||
|
|
||||||
|
`json_view` can also be edited: `json_editable_document` has the same read API, and the `(editable)` rows in
|
||||||
|
`bench_view` measure it on the same workloads. They are the fair counterpart of Boost.JSON and `json::parse`, which are
|
||||||
|
mutable too; the plain `json_view` rows are the counterpart of yyjson and simdjson DOM, which are not. `bench_edit` adds
|
||||||
|
the read-modify-write workloads. Read the tables with that in mind: an immutable-only library is not compared with an
|
||||||
|
editable `json_view` unless it says so.
|
||||||
|
|
||||||
The engines do not all offer the same features, which the numbers should be read with:
|
The engines do not all offer the same features, which the numbers should be read with:
|
||||||
|
|
||||||
| engine | document | random access | editable | notes |
|
| engine | document | random access | editable | notes |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
|
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
|
||||||
|
| `json_view` (editable) | index into the text plus edits | yes | yes, in place | `json_editable_document`; keeps member order and number spellings |
|
||||||
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
|
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
|
||||||
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
|
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
|
||||||
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
|
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
// Same-feature-set benchmark: read-only JSON documents with random access.
|
// Same-feature-set benchmark: read-only JSON documents with random access.
|
||||||
//
|
//
|
||||||
// json_view nlohmann/json_view.hpp (fresh document per parse / reused)
|
// json_view nlohmann/json_view.hpp (fresh document per parse / reused;
|
||||||
|
// "(editable)": json_editable_document, same read API)
|
||||||
// yyjson yyjson_read(): immutable document, random access
|
// yyjson yyjson_read(): immutable document, random access
|
||||||
// simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser
|
// simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser
|
||||||
// per parse): immutable, random access
|
// per parse): immutable, random access
|
||||||
@@ -19,7 +20,11 @@
|
|||||||
//
|
//
|
||||||
// Workloads: parse (build + free), traverse (visit everything, convert every
|
// Workloads: parse (build + free), traverse (visit everything, convert every
|
||||||
// number, touch every string and key), select (a few fields per document),
|
// number, touch every string and key), select (a few fields per document),
|
||||||
// dump (compact serialization of the parsed document).
|
// dump (compact serialization of the parsed document), and parse+dump (a fresh
|
||||||
|
// document, then its dump, in one timed call). json_view converts floating-point
|
||||||
|
// numbers lazily (when read or dumped), yyjson and simdjson DOM while parsing:
|
||||||
|
// for float-heavy documents, traverse (which includes the parse) and parse+dump
|
||||||
|
// are the fair reading of the parse and dump rows.
|
||||||
// All engines run interleaved in every round, each timed call after an untimed
|
// All engines run interleaved in every round, each timed call after an untimed
|
||||||
// one of the same engine; the best round is reported.
|
// one of the same engine; the best round is reported.
|
||||||
#include <nlohmann/json_view.hpp>
|
#include <nlohmann/json_view.hpp>
|
||||||
@@ -43,6 +48,8 @@
|
|||||||
|
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
using nlohmann::json_document;
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_editable_document;
|
||||||
|
using nlohmann::json_editable_view;
|
||||||
using nlohmann::json_view;
|
using nlohmann::json_view;
|
||||||
|
|
||||||
static volatile double g_sink;
|
static volatile double g_sink;
|
||||||
@@ -55,7 +62,8 @@ struct stats
|
|||||||
|
|
||||||
// ---------------- traversal ----------------
|
// ---------------- traversal ----------------
|
||||||
|
|
||||||
static void walk(json_view v, stats& st)
|
template<class V>
|
||||||
|
static void walk_view(V v, stats& st)
|
||||||
{
|
{
|
||||||
++st.nodes;
|
++st.nodes;
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
@@ -64,32 +72,42 @@ static void walk(json_view v, stats& st)
|
|||||||
for (auto it = v.begin(); it != v.end(); ++it)
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
{
|
{
|
||||||
st.str += it.key().size();
|
st.str += it.key().size();
|
||||||
walk(*it, st);
|
walk_view<V>(*it, st);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case json::value_t::array:
|
case json::value_t::array:
|
||||||
for (const json_view e : v)
|
for (const V e : v)
|
||||||
{
|
{
|
||||||
walk(e, st);
|
walk_view<V>(e, st);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case json::value_t::string:
|
case json::value_t::string:
|
||||||
st.str += v.get_string().size();
|
st.str += v.get_string().size();
|
||||||
break;
|
break;
|
||||||
case json::value_t::number_integer:
|
case json::value_t::number_integer:
|
||||||
st.num += static_cast<double>(v.get<std::int64_t>());
|
st.num += static_cast<double>(v.template get<std::int64_t>());
|
||||||
break;
|
break;
|
||||||
case json::value_t::number_unsigned:
|
case json::value_t::number_unsigned:
|
||||||
st.num += static_cast<double>(v.get<std::uint64_t>());
|
st.num += static_cast<double>(v.template get<std::uint64_t>());
|
||||||
break;
|
break;
|
||||||
case json::value_t::number_float:
|
case json::value_t::number_float:
|
||||||
st.num += v.get<double>();
|
st.num += v.template get<double>();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
walk_view<json_view>(v, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(json_editable_view v, stats& st)
|
||||||
|
{
|
||||||
|
walk_view<json_editable_view>(v, st);
|
||||||
|
}
|
||||||
|
|
||||||
static void walk(const json& j, stats& st)
|
static void walk(const json& j, stats& st)
|
||||||
{
|
{
|
||||||
++st.nodes;
|
++st.nodes;
|
||||||
@@ -300,20 +318,21 @@ static void walk(const boost::json::value& v, stats& st)
|
|||||||
// status (one tweet): id, user.screen_name, retweet_count
|
// status (one tweet): id, user.screen_name, retweet_count
|
||||||
// rpc: method, params.minuend, id
|
// rpc: method, params.minuend, id
|
||||||
|
|
||||||
static double pick(const std::string& name, json_view r)
|
template<class V>
|
||||||
|
static double pick_view(const std::string& name, V r)
|
||||||
{
|
{
|
||||||
double acc = 0;
|
double acc = 0;
|
||||||
if (name == "twitter" || name == "status")
|
if (name == "twitter" || name == "status")
|
||||||
{
|
{
|
||||||
auto one = [&](json_view s)
|
auto one = [&](V s)
|
||||||
{
|
{
|
||||||
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
acc += static_cast<double>(s["id"].template get<std::uint64_t>());
|
||||||
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
|
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
|
||||||
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
acc += static_cast<double>(s["retweet_count"].template get<std::int64_t>());
|
||||||
};
|
};
|
||||||
if (name == "twitter")
|
if (name == "twitter")
|
||||||
{
|
{
|
||||||
for (const json_view s : r["statuses"])
|
for (const V s : r["statuses"])
|
||||||
{
|
{
|
||||||
one(s);
|
one(s);
|
||||||
}
|
}
|
||||||
@@ -325,20 +344,20 @@ static double pick(const std::string& name, json_view r)
|
|||||||
}
|
}
|
||||||
else if (name == "citm_catalog")
|
else if (name == "citm_catalog")
|
||||||
{
|
{
|
||||||
for (const json_view p : r["performances"])
|
for (const V p : r["performances"])
|
||||||
{
|
{
|
||||||
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
acc += static_cast<double>(p["id"].template get<std::uint64_t>() + p["eventId"].template get<std::uint64_t>() + p["seatCategories"].size());
|
||||||
}
|
}
|
||||||
acc += static_cast<double>(r["events"].size());
|
acc += static_cast<double>(r["events"].size());
|
||||||
}
|
}
|
||||||
else if (name == "canada")
|
else if (name == "canada")
|
||||||
{
|
{
|
||||||
const json_view g = r["features"][0]["geometry"];
|
const V g = r["features"][0]["geometry"];
|
||||||
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
|
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
|
||||||
}
|
}
|
||||||
else if (name == "jeopardy")
|
else if (name == "jeopardy")
|
||||||
{
|
{
|
||||||
for (const json_view q : r)
|
for (const V q : r)
|
||||||
{
|
{
|
||||||
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
|
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
|
||||||
acc += static_cast<double>(q["category"].get_string().size());
|
acc += static_cast<double>(q["category"].get_string().size());
|
||||||
@@ -347,11 +366,21 @@ static double pick(const std::string& name, json_view r)
|
|||||||
else if (name == "rpc")
|
else if (name == "rpc")
|
||||||
{
|
{
|
||||||
acc += static_cast<double>(r["method"].get_string().size());
|
acc += static_cast<double>(r["method"].get_string().size());
|
||||||
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
acc += static_cast<double>(r["params"]["minuend"].template get<std::int64_t>() + r["id"].template get<std::int64_t>());
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, json_view r)
|
||||||
|
{
|
||||||
|
return pick_view<json_view>(name, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, json_editable_view r)
|
||||||
|
{
|
||||||
|
return pick_view<json_editable_view>(name, r);
|
||||||
|
}
|
||||||
|
|
||||||
static double pick(const std::string& name, const json& r)
|
static double pick(const std::string& name, const json& r)
|
||||||
{
|
{
|
||||||
double acc = 0;
|
double acc = 0;
|
||||||
@@ -634,15 +663,35 @@ int main(int argc, char** argv)
|
|||||||
walk(json::parse(dc.text), a);
|
walk(json::parse(dc.text), a);
|
||||||
auto d = json_document::parse(dc.text);
|
auto d = json_document::parse(dc.text);
|
||||||
walk(d.root(), b);
|
walk(d.root(), b);
|
||||||
|
auto ed = json_editable_document::parse(dc.text);
|
||||||
|
stats e;
|
||||||
|
walk(ed.root(), e);
|
||||||
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||||
walk(yyjson_doc_get_root(y), c);
|
walk(yyjson_doc_get_root(y), c);
|
||||||
simdjson::dom::parser p;
|
simdjson::dom::parser p;
|
||||||
walk(p.parse(dc.text).value(), dd);
|
walk(p.parse(dc.text).value(), dd);
|
||||||
const bool ok = a.nodes == b.nodes && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
const bool ok = a.nodes == b.nodes && a.nodes == e.nodes && a.str == e.str && std::fabs(a.num - e.num) <= 1e-9 * std::fabs(a.num) && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
||||||
&& std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num);
|
&& std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num);
|
||||||
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pe = pick(dc.name, ed.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
||||||
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pe && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
||||||
yyjson_doc_free(y);
|
yyjson_doc_free(y);
|
||||||
|
|
||||||
|
// the dumps of the engines may format floats differently, so only: every dump is non-empty, and the
|
||||||
|
// dump of json_view (also editable) and of json::parse parse back to the same number of nodes
|
||||||
|
const std::string dj = json::parse(dc.text).dump();
|
||||||
|
const std::string dv = d.root().dump();
|
||||||
|
const std::string de = ed.root().dump();
|
||||||
|
std::size_t yn = 0;
|
||||||
|
yyjson_doc* y2 = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||||
|
char* yo = yyjson_write(y2, 0, &yn);
|
||||||
|
const bool dumps_ok = !dj.empty() && !dv.empty() && !de.empty() && yo != nullptr && yn > 0 && !simdjson::to_string(p.parse(dc.text).value()).empty();
|
||||||
|
std::free(yo);
|
||||||
|
yyjson_doc_free(y2);
|
||||||
|
stats sj_nodes, sv_nodes, se_nodes;
|
||||||
|
walk(json::parse(dj), sj_nodes);
|
||||||
|
walk(json::parse(dv), sv_nodes);
|
||||||
|
walk(json::parse(de), se_nodes);
|
||||||
|
std::printf("check %-13s dump %s\n", dc.name.c_str(), (dumps_ok && sj_nodes.nodes == a.nodes && sv_nodes.nodes == a.nodes && se_nodes.nodes == a.nodes) ? "OK" : "MISMATCH");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::FILE* csv = std::fopen("bench_view.csv", "w");
|
std::FILE* csv = std::fopen("bench_view.csv", "w");
|
||||||
@@ -669,6 +718,7 @@ int main(int argc, char** argv)
|
|||||||
workloads.push_back({"parse", {
|
workloads.push_back({"parse", {
|
||||||
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
|
||||||
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||||
|
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); g_sink = static_cast<double>(d.root().size()); }},
|
||||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
|
||||||
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
@@ -680,6 +730,7 @@ int main(int argc, char** argv)
|
|||||||
workloads.push_back({"traverse", {
|
workloads.push_back({"traverse", {
|
||||||
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||||
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
|
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
|
||||||
|
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
|
||||||
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
|
||||||
@@ -691,15 +742,31 @@ int main(int argc, char** argv)
|
|||||||
workloads.push_back({"select", {
|
workloads.push_back({"select", {
|
||||||
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||||
{"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }},
|
{"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }},
|
||||||
|
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
|
||||||
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
|
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
|
||||||
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
|
||||||
{"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }},
|
{"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }},
|
||||||
}});
|
}});
|
||||||
|
// pipeline: a fresh document, then a dump of it, in one timed call
|
||||||
|
// (simdjson DOM reuses its parser, as in the parse row); the traverse
|
||||||
|
// workload above is already parse+traverse
|
||||||
|
workloads.push_back({"parse+dump", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); std::size_t n = 0; char* o = yyjson_write(d, 0, &n); g_sink = static_cast<double>(n); std::free(o); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(sj.parse(ps).value_unsafe()); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); std::string o = boost::json::serialize(v); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
#endif
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); std::string o = j.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
}});
|
||||||
{
|
{
|
||||||
// serialization of an already parsed document
|
// serialization of an already parsed document
|
||||||
static json_document vd;
|
static json_document vd;
|
||||||
vd.read(s);
|
vd.read(s);
|
||||||
|
static json_editable_document ed = json_editable_document::parse(s);
|
||||||
|
ed = json_editable_document::parse(s);
|
||||||
static yyjson_doc* yd = nullptr;
|
static yyjson_doc* yd = nullptr;
|
||||||
if (yd)
|
if (yd)
|
||||||
{
|
{
|
||||||
@@ -713,6 +780,7 @@ int main(int argc, char** argv)
|
|||||||
jd = json::parse(s);
|
jd = json::parse(s);
|
||||||
workloads.push_back({"dump", {
|
workloads.push_back({"dump", {
|
||||||
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json_view (editable)", [&] { std::string o = ed.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||||
{"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast<double>(o.size()); }},
|
{"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
@@ -740,7 +808,7 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const double ref = best[0];
|
const double ref = best[0];
|
||||||
std::printf("%-13s %-9s", dc.name.c_str(), wl.first.c_str());
|
std::printf("%-13s %-14s", dc.name.c_str(), wl.first.c_str());
|
||||||
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
{
|
{
|
||||||
const double us = best[k] / 1e3;
|
const double us = best[k] / 1e3;
|
||||||
|
|||||||
@@ -239,6 +239,8 @@ def main():
|
|||||||
ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries')
|
ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries')
|
||||||
ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON')
|
ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON')
|
||||||
ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)')
|
ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)')
|
||||||
|
ap.add_argument('--aligned', action='store_true',
|
||||||
|
help='also build and run with all functions (and loops) aligned, to make code layout artifacts visible')
|
||||||
ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)')
|
ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)')
|
||||||
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
|
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
|
||||||
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
|
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
|
||||||
@@ -282,32 +284,46 @@ def main():
|
|||||||
+ ['-I' + d for d in lib.include])
|
+ ['-I' + d for d in lib.include])
|
||||||
objects.append(obj)
|
objects.append(obj)
|
||||||
|
|
||||||
|
cxx_version = output(cxx + ['--version'])
|
||||||
|
is_clang = 'clang' in cxx_version.lower()
|
||||||
|
# alignment flags: functions and (gcc) loops on 64-byte boundaries
|
||||||
|
align_flags = ['-mllvm', '-align-all-functions=6'] if is_clang else ['-falign-functions=64', '-falign-loops=64']
|
||||||
|
|
||||||
|
# the builds: the default one and, with --aligned, one with aligned code
|
||||||
|
variants = [('default build', args.build_dir, [])]
|
||||||
|
if args.aligned:
|
||||||
|
variants.append(('aligned build', os.path.join(args.build_dir, 'aligned'), align_flags))
|
||||||
|
|
||||||
|
corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus
|
||||||
|
# label -> {benchmark: output}
|
||||||
|
all_outputs = {}
|
||||||
|
for label, build_dir, extra in variants:
|
||||||
|
os.makedirs(build_dir, exist_ok=True)
|
||||||
binaries = {}
|
binaries = {}
|
||||||
for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||||
exe = os.path.join(args.build_dir, bench)
|
exe = os.path.join(build_dir, bench)
|
||||||
run(cxx + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
run(cxx + flags + extra + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
||||||
binaries[bench] = exe
|
binaries[bench] = exe
|
||||||
|
|
||||||
# run: bench_view on its documents, bench_corpus on those and the given files
|
# run: bench_view on its documents, bench_corpus on those and the given files
|
||||||
corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus
|
|
||||||
outputs = {}
|
outputs = {}
|
||||||
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=args.build_dir,
|
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=build_dir,
|
||||||
capture_output=True, text=True).stdout
|
capture_output=True, text=True).stdout
|
||||||
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=args.build_dir,
|
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=build_dir,
|
||||||
capture_output=True, text=True).stdout
|
capture_output=True, text=True).stdout
|
||||||
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
|
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=build_dir,
|
||||||
capture_output=True, text=True).stdout
|
|
||||||
outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
|
|
||||||
capture_output=True, text=True).stdout
|
capture_output=True, text=True).stdout
|
||||||
|
outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))],
|
||||||
|
cwd=build_dir, capture_output=True, text=True).stdout
|
||||||
for name, text in outputs.items():
|
for name, text in outputs.items():
|
||||||
print(text)
|
print(text)
|
||||||
|
all_outputs[label] = outputs
|
||||||
|
|
||||||
# results with their metadata
|
# results with their metadata
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host'
|
host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host'
|
||||||
stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}')
|
stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}')
|
||||||
os.makedirs(os.path.dirname(stem), exist_ok=True)
|
os.makedirs(os.path.dirname(stem), exist_ok=True)
|
||||||
cxx_version = output(cxx + ['--version'])
|
|
||||||
compiler_version = cxx_version.splitlines()[0] if cxx_version else ' '.join(cxx)
|
compiler_version = cxx_version.splitlines()[0] if cxx_version else ' '.join(cxx)
|
||||||
meta = [
|
meta = [
|
||||||
('date', f'{now:%Y-%m-%d %H:%M}'),
|
('date', f'{now:%Y-%m-%d %H:%M}'),
|
||||||
@@ -316,6 +332,7 @@ def main():
|
|||||||
('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'),
|
('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'),
|
||||||
('compiler', compiler_version),
|
('compiler', compiler_version),
|
||||||
('flags', ' '.join(flags)),
|
('flags', ' '.join(flags)),
|
||||||
|
('aligned results', ('included (flags: ' + ' '.join(align_flags) + ')') if args.aligned else 'not included'),
|
||||||
('yyjson', libs['yyjson'].version),
|
('yyjson', libs['yyjson'].version),
|
||||||
('simdjson', libs['simdjson'].version),
|
('simdjson', libs['simdjson'].version),
|
||||||
('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'),
|
('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'),
|
||||||
@@ -328,15 +345,20 @@ def main():
|
|||||||
f.write('| | |\n|---|---|\n')
|
f.write('| | |\n|---|---|\n')
|
||||||
for key, value in meta:
|
for key, value in meta:
|
||||||
f.write(f'| {key} | {value} |\n')
|
f.write(f'| {key} | {value} |\n')
|
||||||
|
for label, outputs in all_outputs.items():
|
||||||
|
# (without --aligned there is only the default build, and no extra heading level)
|
||||||
|
prefix = f'{label}: ' if args.aligned else ''
|
||||||
for name, text in outputs.items():
|
for name, text in outputs.items():
|
||||||
f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n')
|
f.write(f'\n## {prefix}{name}\n\n```\n{text.rstrip()}\n```\n')
|
||||||
with open(stem + '.csv', 'w', encoding='utf-8') as out:
|
with open(stem + '.csv', 'w', encoding='utf-8') as out:
|
||||||
out.write(''.join(f'# {key}: {value}\n' for key, value in meta))
|
out.write(''.join(f'# {key}: {value}\n' for key, value in meta))
|
||||||
|
for label, build_dir, _ in variants:
|
||||||
for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||||
path = os.path.join(args.build_dir, name + '.csv')
|
path = os.path.join(build_dir, name + '.csv')
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
with open(path, encoding='utf-8') as f:
|
with open(path, encoding='utf-8') as f:
|
||||||
out.write(f'# {name}\n' + f.read())
|
# (the default build keeps its plain section names)
|
||||||
|
out.write(f'# {name}' + (f' ({label})' if args.aligned else '') + '\n' + f.read())
|
||||||
print(f'results: {stem}.md, {stem}.csv')
|
print(f'results: {stem}.md, {stem}.csv')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1311,6 +1311,8 @@ TEST_CASE("BSON declared document size bounds the content")
|
|||||||
// records the events that a SAX parser receives
|
// records the events that a SAX parser receives
|
||||||
struct recorder : nlohmann::json_sax<json>
|
struct recorder : nlohmann::json_sax<json>
|
||||||
{
|
{
|
||||||
|
// GCC's -Weffc++ wants the member initialized in the member initialization list
|
||||||
|
// NOLINTNEXTLINE(readability-redundant-member-init)
|
||||||
std::string events {};
|
std::string events {};
|
||||||
|
|
||||||
bool null() override
|
bool null() override
|
||||||
|
|||||||
@@ -749,20 +749,6 @@ std::string exception_of(F f)
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// the exception a call on a basic_json value throws, without the byte range of
|
|
||||||
// the value (JSON_DIAGNOSTIC_POSITIONS), which the exceptions of views lack
|
|
||||||
template<typename F>
|
|
||||||
std::string json_exception_of(F f)
|
|
||||||
{
|
|
||||||
std::string what = exception_of(f);
|
|
||||||
const std::size_t at = what.find("] (bytes ");
|
|
||||||
if (at != std::string::npos)
|
|
||||||
{
|
|
||||||
what.erase(at + 2, what.find(") ", at) - at);
|
|
||||||
}
|
|
||||||
return what;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// compares a view with the ordered_json value materialize() gives for it:
|
// compares a view with the ordered_json value materialize() gives for it:
|
||||||
@@ -971,24 +957,24 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
if (!j.is_object())
|
if (!j.is_object())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v["a"]); }) == json_exception_of([&] { static_cast<void>(j["a"]); }));
|
CHECK(exception_of([&] { static_cast<void>(v["a"]); }) == exception_of([&] { static_cast<void>(j["a"]); }));
|
||||||
}
|
}
|
||||||
if (!j.is_array())
|
if (!j.is_array())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == json_exception_of([&] { static_cast<void>(j[0]); }));
|
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == exception_of([&] { static_cast<void>(j[0]); }));
|
||||||
}
|
}
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at("a")); }) == json_exception_of([&] { static_cast<void>(j.at("a")); }));
|
CHECK(exception_of([&] { static_cast<void>(v.at("a")); }) == exception_of([&] { static_cast<void>(j.at("a")); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at("missing")); }) == json_exception_of([&] { static_cast<void>(j.at("missing")); }));
|
CHECK(exception_of([&] { static_cast<void>(v.at("missing")); }) == exception_of([&] { static_cast<void>(j.at("missing")); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at(0)); }) == json_exception_of([&] { static_cast<void>(j.at(0)); }));
|
CHECK(exception_of([&] { static_cast<void>(v.at(0)); }) == exception_of([&] { static_cast<void>(j.at(0)); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.at(5)); }) == json_exception_of([&] { static_cast<void>(j.at(5)); }));
|
CHECK(exception_of([&] { static_cast<void>(v.at(5)); }) == exception_of([&] { static_cast<void>(j.at(5)); }));
|
||||||
if (!(j.is_object() && j.empty())) // (key() of an end iterator)
|
if (!(j.is_object() && j.empty())) // (key() of an end iterator)
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == json_exception_of([&] { static_cast<void>(j.begin().key()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == exception_of([&] { static_cast<void>(j.begin().key()); }));
|
||||||
}
|
}
|
||||||
if (!j.empty() || j.is_null())
|
if (!j.empty() || j.is_null())
|
||||||
{
|
{
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == json_exception_of([&] { static_cast<void>(j.front()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == exception_of([&] { static_cast<void>(j.front()); }));
|
||||||
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == json_exception_of([&] { static_cast<void>(j.back()); }));
|
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == exception_of([&] { static_cast<void>(j.back()); }));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
CHECK(v.contains("a") == j.contains("a"));
|
CHECK(v.contains("a") == j.contains("a"));
|
||||||
|
|||||||
@@ -326,8 +326,7 @@ TEST_CASE("json_view from_json")
|
|||||||
CHECK(d.root().get<std::unordered_map<std::string, int>>().at("a") == 3);
|
CHECK(d.root().get<std::unordered_map<std::string, int>>().at("a") == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// (the exceptions of views have neither the path nor the byte range of the value)
|
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS
|
||||||
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
SECTION("std types: the same errors as from the materialized value")
|
SECTION("std types: the same errors as from the materialized value")
|
||||||
{
|
{
|
||||||
const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])");
|
const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])");
|
||||||
@@ -393,7 +392,7 @@ TEST_CASE("json_view from_json")
|
|||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
const json j = d.root().materialize();
|
const json j = d.root().materialize();
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\"");
|
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\"");
|
||||||
#if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
#if !JSON_DIAGNOSTICS
|
||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == exception_of_call([&] { static_cast<void>(j[2].get<strict_color>()); }));
|
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == exception_of_call([&] { static_cast<void>(j[2].get<strict_color>()); }));
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@@ -441,11 +440,10 @@ TEST_CASE("json_view from_json")
|
|||||||
CHECK(o.root().get<shape>().center.y == 2);
|
CHECK(o.root().get<shape>().center.y == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS
|
||||||
SECTION("errors are those of the same conversion of a json value")
|
SECTION("errors are those of the same conversion of a json value")
|
||||||
{
|
{
|
||||||
// (with JSON_DIAGNOSTICS, those of json name the path of the value,
|
// (with JSON_DIAGNOSTICS, those of json name the path of the value)
|
||||||
// and with JSON_DIAGNOSTIC_POSITIONS its bytes)
|
|
||||||
const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])");
|
const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])");
|
||||||
const json j = d.root().materialize();
|
const json j = d.root().materialize();
|
||||||
for (std::size_t i = 0; i < 3; ++i)
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
|
|||||||
@@ -1,340 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// source positions of materialize() (JSON_DIAGNOSTIC_POSITIONS, #5864)
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#ifndef JSON_DIAGNOSTICS
|
|
||||||
#define JSON_DIAGNOSTICS 1
|
|
||||||
#endif
|
|
||||||
#define JSON_DIAGNOSTIC_POSITIONS 1
|
|
||||||
#include <nlohmann/json_view.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
using nlohmann::ordered_json;
|
|
||||||
using nlohmann::json_document;
|
|
||||||
using nlohmann::json_editable_document;
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "json_view_test_helpers.hpp"
|
|
||||||
#include <test_data.hpp>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
constexpr std::size_t npos = std::string::npos;
|
|
||||||
|
|
||||||
// whether every value of a has the source positions of the value of b at the
|
|
||||||
// same place (a and b are equal; shallow test documents only)
|
|
||||||
template<typename BasicJsonType>
|
|
||||||
bool same_positions(const BasicJsonType& a, const BasicJsonType& b)
|
|
||||||
{
|
|
||||||
if (a.start_pos() != b.start_pos() || a.end_pos() != b.end_pos() || a.type() != b.type() || a.size() != b.size())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (a.is_object())
|
|
||||||
{
|
|
||||||
for (auto it = a.begin(); it != a.end(); ++it)
|
|
||||||
{
|
|
||||||
if (!same_positions(it.value(), b.at(it.key())))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (a.is_array())
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i < a.size(); ++i)
|
|
||||||
{
|
|
||||||
if (!same_positions(a[i], b[i]))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the materialized root of text has the positions of parse()
|
|
||||||
template<typename BasicJsonType>
|
|
||||||
bool positions_as_parse(const std::string& text, bool comments = false, bool trailing_commas = false)
|
|
||||||
{
|
|
||||||
const auto j = BasicJsonType::parse(text, nullptr, true, comments, trailing_commas);
|
|
||||||
const auto d = nlohmann::basic_json_document<BasicJsonType>::parse(text, true, comments, trailing_commas);
|
|
||||||
const auto e = nlohmann::basic_json_document<BasicJsonType, nlohmann::view_mode::editable>::parse(text, true, comments, trailing_commas);
|
|
||||||
return same_positions(d.root().materialize(), j) && same_positions(e.root().materialize(), j);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string range(const json& j)
|
|
||||||
{
|
|
||||||
return j.start_pos() == npos && j.end_pos() == npos ? "none" : std::to_string(j.start_pos()) + "-" + std::to_string(j.end_pos());
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("json_view materialize() source positions")
|
|
||||||
{
|
|
||||||
SECTION("as parse()")
|
|
||||||
{
|
|
||||||
const std::vector<std::string> texts =
|
|
||||||
{
|
|
||||||
"null", "true", "false", "0", "-12", "3.25e-2", "18446744073709551615", "-9223372036854775809", R"("a")",
|
|
||||||
R"("\n")", R"("\u00e9")", R"("\ud83d\ude00")", "\"\xf0\x9f\x98\x80\"", R"("a\"b\\")", R"("x\\")",
|
|
||||||
" [ ] ", "{}", "[[[]]]", R"({"a":{"b":[1,{"c":[]}]}})",
|
|
||||||
R"({"\u0041": "v\n", "k": "\"", "\t":[ "\/" ]})",
|
|
||||||
"[1, 2.5, true, false, null, \"s\", \"\\u0000\", -0]",
|
|
||||||
"\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n",
|
|
||||||
"\xef\xbb\xbf[1, {\"a\": \"b\"}]", "\xef\xbb\xbf\"\\n\"",
|
|
||||||
};
|
|
||||||
for (const auto& text : texts)
|
|
||||||
{
|
|
||||||
CAPTURE(text)
|
|
||||||
CHECK(positions_as_parse<json>(text));
|
|
||||||
CHECK(positions_as_parse<ordered_json>(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("comments and trailing commas")
|
|
||||||
{
|
|
||||||
const std::vector<std::string> texts =
|
|
||||||
{
|
|
||||||
"[1 /* ] */]", "[1 // ]\n]", "{\"a\": 1 /* } */ , }", "[1, 2, ]", "[[1,],[2 , /**/ ] , ]",
|
|
||||||
"{\"a\": [] // }\n, \"b\": {} /* } ] */ }", "[ /* empty */ ]", "{ // }\n}", "/* a */ [1] // b",
|
|
||||||
"[\"\\n\" /* \" */ , ]",
|
|
||||||
};
|
|
||||||
for (const auto& text : texts)
|
|
||||||
{
|
|
||||||
CAPTURE(text)
|
|
||||||
CHECK(positions_as_parse<json>(text, true, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("generated documents")
|
|
||||||
{
|
|
||||||
json_view_test::generator gen;
|
|
||||||
for (int i = 0; i < 300; ++i)
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
gen.value(text, 0);
|
|
||||||
CAPTURE(text)
|
|
||||||
CHECK(positions_as_parse<json>(text));
|
|
||||||
CHECK(positions_as_parse<ordered_json>(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("test data")
|
|
||||||
{
|
|
||||||
for (const char* name :
|
|
||||||
{
|
|
||||||
"/json_testsuite/sample.json", "/nativejson-benchmark/canada.json", "/nativejson-benchmark/citm_catalog.json",
|
|
||||||
"/nativejson-benchmark/twitter.json", "/json_tests/pass1.json"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(name)
|
|
||||||
const std::ifstream f(std::string(TEST_DATA_DIRECTORY) + name, std::ios::binary);
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << f.rdbuf();
|
|
||||||
CHECK(positions_as_parse<json>(ss.str()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("subtrees")
|
|
||||||
{
|
|
||||||
const std::string text = R"({"a": [1, "\n", {"b": null}], "s": "x\ty", "n": -1.5})";
|
|
||||||
const json j = json::parse(text);
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
CHECK(same_positions(d.root()["a"].materialize(), j["a"]));
|
|
||||||
CHECK(same_positions(d.root()["a"][2].materialize(), j["a"][2]));
|
|
||||||
CHECK(same_positions(d.root()["a"][1].materialize(), j["a"][1]));
|
|
||||||
CHECK(same_positions(d.root()["s"].materialize(), j["s"]));
|
|
||||||
CHECK(same_positions(d.root()["n"].materialize(), j["n"]));
|
|
||||||
CHECK(range(d.root()["a"][1].materialize()) == "10-14");
|
|
||||||
CHECK(range(d.root()["s"].materialize()) == "35-41");
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
SECTION("exception messages")
|
|
||||||
{
|
|
||||||
const std::string text = "{\n \"address\": {\"street\": \"Fake\\tStreet\", \"housenumber\": \"1\"},\n \"list\": [1, [true]]\n}";
|
|
||||||
const json j = json::parse(text);
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const json m = d.root().materialize();
|
|
||||||
const std::string expected = json_view_test::exception_of_call([&]
|
|
||||||
{
|
|
||||||
static_cast<void>(j.at("address").at("housenumber").get<int>());
|
|
||||||
});
|
|
||||||
CHECK(expected.find("(bytes ") != std::string::npos);
|
|
||||||
CHECK(json_view_test::exception_of_call([&]
|
|
||||||
{
|
|
||||||
static_cast<void>(m.at("address").at("housenumber").get<int>());
|
|
||||||
}) == expected);
|
|
||||||
CHECK(json_view_test::exception_of_call([&]
|
|
||||||
{
|
|
||||||
static_cast<void>(m.at("address").at("street").get<int>());
|
|
||||||
}) == json_view_test::exception_of_call([&]
|
|
||||||
{
|
|
||||||
static_cast<void>(j.at("address").at("street").get<int>());
|
|
||||||
}));
|
|
||||||
CHECK(json_view_test::exception_of_call([&]
|
|
||||||
{
|
|
||||||
static_cast<void>(m.at("list").at(1).get<int>());
|
|
||||||
}) == json_view_test::exception_of_call([&]
|
|
||||||
{
|
|
||||||
static_cast<void>(j.at("list").at(1).get<int>());
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("edited documents")
|
|
||||||
{
|
|
||||||
const std::string text = R"({"a": [1, 2], "b": {"c": "\n", "d": true}, "e": [3, [4]], "f": "x"})";
|
|
||||||
const json j = json::parse(text);
|
|
||||||
|
|
||||||
SECTION("new scalars have none")
|
|
||||||
{
|
|
||||||
json_editable_document d = json_editable_document::parse(text);
|
|
||||||
d.set(d.root()["f"], 5);
|
|
||||||
d.set(d.root()["b"]["d"], "s");
|
|
||||||
const json m = d.root().materialize();
|
|
||||||
CHECK(range(m["f"]) == "none");
|
|
||||||
CHECK(range(m["b"]["d"]) == "none");
|
|
||||||
// the object whose last member is new has none; the others keep theirs
|
|
||||||
CHECK(range(m) == "none");
|
|
||||||
CHECK(range(m["b"]) == "none");
|
|
||||||
CHECK(same_positions(m["a"], j["a"]));
|
|
||||||
CHECK(same_positions(m["e"], j["e"]));
|
|
||||||
CHECK(same_positions(m["b"]["c"], j["b"]["c"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("containers whose elements change have none")
|
|
||||||
{
|
|
||||||
json_editable_document d = json_editable_document::parse(text);
|
|
||||||
d.push_back(d.root()["a"], 7);
|
|
||||||
d.erase(d.root()["e"], 0);
|
|
||||||
const json m = d.root().materialize();
|
|
||||||
CHECK(range(m["a"]) == "none");
|
|
||||||
CHECK(range(m["a"][2]) == "none");
|
|
||||||
CHECK(range(m["e"]) == "none");
|
|
||||||
// the elements kept keep theirs
|
|
||||||
CHECK(range(m["a"][0]) == range(j["a"][0]));
|
|
||||||
CHECK(range(m["a"][1]) == range(j["a"][1]));
|
|
||||||
CHECK(same_positions(m["e"][0], j["e"][1]));
|
|
||||||
CHECK(same_positions(m["b"], j["b"]));
|
|
||||||
// the root's elements did not change
|
|
||||||
CHECK(range(m) == range(j));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("new containers have none")
|
|
||||||
{
|
|
||||||
json_editable_document d = json_editable_document::parse(text);
|
|
||||||
d.set(d.root()["b"], json::parse(R"({"x": [1]})"));
|
|
||||||
const json m = d.root().materialize();
|
|
||||||
CHECK(range(m["b"]) == "none");
|
|
||||||
CHECK(range(m["b"]["x"]) == "none");
|
|
||||||
CHECK(range(m["b"]["x"][0]) == "none");
|
|
||||||
CHECK(range(m) == range(j));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("copies of scalars of the document keep theirs")
|
|
||||||
{
|
|
||||||
// (a copy of an array or object is a new one; its scalars are the
|
|
||||||
// parsed ones)
|
|
||||||
json_editable_document d = json_editable_document::parse(text);
|
|
||||||
d.set(d.root()["f"], d.root()["e"]);
|
|
||||||
d.set(d.root()["a"], d.root()["b"]["c"]);
|
|
||||||
const json m = d.root().materialize();
|
|
||||||
CHECK(range(m["a"]) == range(j["b"]["c"]));
|
|
||||||
CHECK(range(m["f"]) == "none");
|
|
||||||
CHECK(range(m["f"][1]) == "none");
|
|
||||||
CHECK(range(m["f"][0]) == range(j["e"][0]));
|
|
||||||
CHECK(range(m["f"][1][0]) == range(j["e"][1][0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
|
||||||
SECTION("images")
|
|
||||||
{
|
|
||||||
const std::string text = R"({"a\n": ["\u00e9", 1.5, {"b": [true, null]}], "c": "d"})";
|
|
||||||
const json j = json::parse(text);
|
|
||||||
|
|
||||||
SECTION("round trip")
|
|
||||||
{
|
|
||||||
const json_document d = json_document::load(json_document::parse(text).save());
|
|
||||||
CHECK(same_positions(d.root().materialize(), j));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("edited")
|
|
||||||
{
|
|
||||||
json_editable_document e = json_editable_document::parse(text);
|
|
||||||
e.set(e.root()["c"], 1);
|
|
||||||
e.push_back(e.root()[std::string("a") + '\n'], false);
|
|
||||||
e.set(e.root()[std::string("a") + '\n'][2]["b"][0], "\"");
|
|
||||||
const json_document d = json_document::load(e.save());
|
|
||||||
const json m = d.root().materialize();
|
|
||||||
CHECK(range(m["c"]) == "none");
|
|
||||||
CHECK(range(m) == "none");
|
|
||||||
const json& a = m[std::string("a") + '\n'];
|
|
||||||
const json& ja = j[std::string("a") + '\n'];
|
|
||||||
CHECK(range(a) == "none");
|
|
||||||
CHECK(range(a[3]) == "none");
|
|
||||||
CHECK(range(a[0]) == range(ja[0]));
|
|
||||||
CHECK(range(a[1]) == range(ja[1]));
|
|
||||||
CHECK(range(a[2]["b"][0]) == "none");
|
|
||||||
CHECK(range(a[2]["b"][1]) == range(ja[2]["b"][1]));
|
|
||||||
// the last element of b is still the parsed one
|
|
||||||
CHECK(range(a[2]["b"]) == range(ja[2]["b"]));
|
|
||||||
CHECK(range(a[2]) == range(ja[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("without positions")
|
|
||||||
{
|
|
||||||
// an image saved without JSON_DIAGNOSTIC_POSITIONS: next is 0 for
|
|
||||||
// the strings with escapes
|
|
||||||
std::vector<std::uint8_t> image = json_document::parse(text).save();
|
|
||||||
std::uint64_t count = 0;
|
|
||||||
std::memcpy(&count, image.data() + 8, sizeof(count));
|
|
||||||
for (std::size_t i = 0; i < count; ++i)
|
|
||||||
{
|
|
||||||
nlohmann::detail::view::node n{};
|
|
||||||
std::memcpy(&n, image.data() + 64 + (i * sizeof(n)), sizeof(n));
|
|
||||||
if (n.kind == nlohmann::detail::view::node_kind::string)
|
|
||||||
{
|
|
||||||
n.next = 0;
|
|
||||||
}
|
|
||||||
std::memcpy(image.data() + 64 + (i * sizeof(n)), &n, sizeof(n));
|
|
||||||
}
|
|
||||||
const json_document d = json_document::load(image);
|
|
||||||
const json m = d.root().materialize();
|
|
||||||
CHECK(range(m[std::string("a") + '\n'][0]) == "none");
|
|
||||||
CHECK(range(m["c"]) == range(j["c"]));
|
|
||||||
CHECK(same_positions(m[std::string("a") + '\n'][2], j[std::string("a") + '\n'][2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
SECTION("a decoded string's position must be within the text")
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> image = json_document::parse(R"(["\n"])").save();
|
|
||||||
nlohmann::detail::view::node n{};
|
|
||||||
std::memcpy(&n, image.data() + 64 + sizeof(n), sizeof(n));
|
|
||||||
REQUIRE(n.next == 2);
|
|
||||||
n.next = 7;
|
|
||||||
std::memcpy(image.data() + 64 + sizeof(n), &n, sizeof(n));
|
|
||||||
CHECK_THROWS_AS(json_document::load(image), json::parse_error&);
|
|
||||||
// without the check, the position is out of bounds and ignored
|
|
||||||
const json_document d = json_document::load(image, json_document::image_check::none);
|
|
||||||
CHECK(range(d.root()[0].materialize()) == "none");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user