mirror of
https://github.com/nlohmann/json.git
synced 2026-10-27 16:28:57 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95519dbbcb | ||
|
|
359c889ed2 | ||
|
|
16793d6884 | ||
|
|
9edff5292d | ||
|
|
8dd8f5ce8e | ||
|
|
0babaa1a4e |
No files matched your search
@@ -69,6 +69,19 @@ Duplicate member names inside an operation object are resolved by the parser bef
|
|||||||
is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a
|
is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a
|
||||||
`remove` operation.
|
`remove` operation.
|
||||||
|
|
||||||
|
The JSON patch may be the JSON value itself (`#!cpp j.patch_inplace(j)`): the operations are then read from a copy of
|
||||||
|
the patch made before the first operation is applied, so the result is the same as that of
|
||||||
|
`#!cpp j = j.patch(j)`, and the exception safety is the same as above.
|
||||||
|
|
||||||
|
If [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) is enabled, the same holds for a JSON patch stored inside the
|
||||||
|
JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`): it is detected via the parent pointers.
|
||||||
|
|
||||||
|
!!! danger "Undefined behavior"
|
||||||
|
|
||||||
|
Without [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md), the JSON patch must not be a value stored inside the
|
||||||
|
JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`), as the operations may change or destroy it while it is
|
||||||
|
read. Pass a copy (`#!cpp j.patch_inplace(json(j["patch"]))`) or use [`patch`](patch.md) instead.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
??? example "Example: apply a JSON patch in place"
|
??? example "Example: apply a JSON patch in place"
|
||||||
@@ -122,3 +135,5 @@ is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op":
|
|||||||
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of
|
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of
|
||||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is
|
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is
|
||||||
not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones.
|
not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones.
|
||||||
|
- Applying a JSON value as patch to itself (or, with `JSON_DIAGNOSTICS`, to a value stored inside it) no longer results
|
||||||
|
in undefined behavior in version 3.13.0.
|
||||||
@@ -32,20 +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).
|
|
||||||
|
|
||||||
A `basic_json` value, in contrast, keeps its positions when it is changed. Documents [loaded](../basic_json_document/load.md)
|
|
||||||
from an image have the positions of the saved document, except that its strings with escapes have none if it was saved
|
|
||||||
without `JSON_DIAGNOSTIC_POSITIONS`.
|
|
||||||
|
|
||||||
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.
|
||||||
@@ -73,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
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ json_pointer& operator/=(std::size_t array_idx)
|
|||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`ptr` (in)
|
`ptr` (in)
|
||||||
: JSON pointer to append
|
: JSON pointer to append; may be the JSON pointer itself (`#!cpp p /= p`), which then appends a copy of its
|
||||||
|
reference tokens
|
||||||
|
|
||||||
`token` (in)
|
`token` (in)
|
||||||
: reference token to append
|
: reference token to append
|
||||||
@@ -69,6 +70,7 @@ json_pointer& operator/=(std::size_t array_idx)
|
|||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
1. Added in version 3.6.0.
|
1. Added in version 3.6.0. Appending a JSON pointer to itself no longer results in undefined behavior in version
|
||||||
|
3.13.0.
|
||||||
2. Added in version 3.6.0. Changed type of `token` to `string_t` in version 3.11.0.
|
2. Added in version 3.6.0. Changed type of `token` to `string_t` in version 3.11.0.
|
||||||
3. Added in version 3.6.0.
|
3. Added in version 3.6.0.
|
||||||
@@ -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, or 0 (only with [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md)) |
|
| 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,11 +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 nothing while parsing: 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
|
||||||
@@ -282,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 ? read : 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 = v.end_position;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 = v.end_position;
|
|
||||||
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 {};
|
||||||
|
|||||||
@@ -243,6 +243,13 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
||||||
json_pointer& operator/=(const json_pointer& ptr)
|
json_pointer& operator/=(const json_pointer& ptr)
|
||||||
{
|
{
|
||||||
|
// insert() must not be given iterators into the vector it inserts
|
||||||
|
// into: append a copy when a pointer is appended to itself
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(&ptr == this))
|
||||||
|
{
|
||||||
|
return *this /= json_pointer(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
reference_tokens.insert(reference_tokens.end(),
|
reference_tokens.insert(reference_tokens.end(),
|
||||||
ptr.reference_tokens.begin(),
|
ptr.reference_tokens.begin(),
|
||||||
ptr.reference_tokens.end());
|
ptr.reference_tokens.end());
|
||||||
|
|||||||
@@ -1193,13 +1193,7 @@ indent_done:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
p = r.p;
|
p = r.p;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
// the decoded string is in the arena: next keeps the source offset
|
|
||||||
// of the opening quote plus one (0: unknown) for materialize()
|
|
||||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, static_cast<std::uint64_t>(r.len) | (static_cast<std::uint64_t>(s - b) << 32));
|
|
||||||
#else
|
|
||||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len);
|
push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len);
|
||||||
#endif
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,176 +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.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
{
|
|
||||||
const bool known = first != std::string::npos && last != std::string::npos;
|
|
||||||
start = known ? first : std::string::npos;
|
|
||||||
end = known ? last : 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
|
|
||||||
{
|
|
||||||
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 += 2;
|
|
||||||
while (pos < size && src[pos] != '\n' && src[pos] != '\r')
|
|
||||||
{
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pos + 1 < size && src[pos + 1] == '*')
|
|
||||||
{
|
|
||||||
pos += 2;
|
|
||||||
while (pos < size && !(src[pos] == '*' && pos + 1 < size && src[pos + 1] == '/'))
|
|
||||||
{
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
pos += 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
|
|
||||||
{
|
|
||||||
const std::size_t size = d.size;
|
|
||||||
const std::size_t off = n.off;
|
|
||||||
std::size_t first = std::string::npos;
|
|
||||||
std::size_t last = std::string::npos;
|
|
||||||
if ((n.flags & node_flags::is_new) == 0)
|
|
||||||
{
|
|
||||||
switch (static_cast<value_t>(n.kind))
|
|
||||||
{
|
|
||||||
case value_t::string:
|
|
||||||
if ((n.flags & node_flags::storage) == 0)
|
|
||||||
{
|
|
||||||
// off is the first byte after the opening quote
|
|
||||||
if (off != 0)
|
|
||||||
{
|
|
||||||
first = off - 1;
|
|
||||||
last = 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], size, first);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
if ((n.flags & node_flags::storage) == 0)
|
|
||||||
{
|
|
||||||
first = off;
|
|
||||||
last = off + number_length(n);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::boolean:
|
|
||||||
first = off;
|
|
||||||
last = off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u);
|
|
||||||
break;
|
|
||||||
case value_t::null:
|
|
||||||
first = off;
|
|
||||||
last = off + 4u;
|
|
||||||
break;
|
|
||||||
case value_t::object:
|
|
||||||
case value_t::array:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// (a crafted image can point past the text)
|
|
||||||
set(first, last != std::string::npos && last <= size ? last : std::string::npos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the basic_json value of the subtree at n
|
@brief the basic_json value of the subtree at n
|
||||||
|
|
||||||
@@ -203,22 +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;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true, position_source>;
|
|
||||||
#else
|
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
||||||
#endif
|
|
||||||
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),
|
||||||
@@ -230,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:
|
||||||
@@ -260,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{};
|
||||||
@@ -284,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))
|
||||||
@@ -302,12 +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)
|
|
||||||
const std::size_t start = (n->flags & node_flags::is_new) == 0 && n->off < d.size ? std::size_t{n->off} :
|
|
||||||
std::string::npos;
|
|
||||||
positions.set(start, start + 1);
|
|
||||||
#endif
|
|
||||||
if (object)
|
if (object)
|
||||||
{
|
{
|
||||||
sax.start_object(n->len);
|
sax.start_object(n->len);
|
||||||
@@ -324,14 +118,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
open = deep.data();
|
open = deep.data();
|
||||||
capacity = deep.size();
|
capacity = deep.size();
|
||||||
}
|
}
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||||
// (until an element is read, the container's own bracket is the last thing read)
|
|
||||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object, start, positions.end};
|
|
||||||
break;
|
break;
|
||||||
#else
|
|
||||||
open[depth++] = frame {nav::first(d, n), nav::end(d, n), object};
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
@@ -371,15 +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
|
|
||||||
const std::size_t end = f.start == std::string::npos || f.last_end == std::string::npos ? std::string::npos : position_source::container_end(d.base[0], d.size, f.last_end);
|
|
||||||
// (an end of npos clears the start, see diagnostic_positions)
|
|
||||||
positions.set(end == std::string::npos ? end : end - 1, end);
|
|
||||||
if (depth > 1)
|
|
||||||
{
|
|
||||||
open[depth - 2].last_end = 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");
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -6950,6 +6950,24 @@ public:
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
||||||
void patch_inplace(const basic_json& json_patch)
|
void patch_inplace(const basic_json& json_patch)
|
||||||
{
|
{
|
||||||
|
// the operations below change *this while iterating json_patch: apply
|
||||||
|
// a copy when a value is applied as patch to itself or, with
|
||||||
|
// JSON_DIAGNOSTICS, to a value stored inside it
|
||||||
|
bool patch_aliases_this = (&json_patch == this);
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
for (const basic_json* p = json_patch.m_parent; !patch_aliases_this && p != nullptr; p = p->m_parent)
|
||||||
|
{
|
||||||
|
patch_aliases_this = (p == this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(patch_aliases_this))
|
||||||
|
{
|
||||||
|
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): *this is modified below
|
||||||
|
const basic_json json_patch_copy = json_patch;
|
||||||
|
patch_inplace(json_patch_copy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
basic_json& result = *this;
|
basic_json& result = *this;
|
||||||
// the valid JSON Patch operations
|
// the valid JSON Patch operations
|
||||||
enum class patch_operations {add, remove, replace, move, copy, test, invalid};
|
enum class patch_operations {add, remove, replace, move, copy, test, invalid};
|
||||||
|
|||||||
Generated
+31
-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 ? read : 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 = v.end_position;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 = v.end_position;
|
|
||||||
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 {};
|
||||||
@@ -24326,6 +24307,13 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
||||||
json_pointer& operator/=(const json_pointer& ptr)
|
json_pointer& operator/=(const json_pointer& ptr)
|
||||||
{
|
{
|
||||||
|
// insert() must not be given iterators into the vector it inserts
|
||||||
|
// into: append a copy when a pointer is appended to itself
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(&ptr == this))
|
||||||
|
{
|
||||||
|
return *this /= json_pointer(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
reference_tokens.insert(reference_tokens.end(),
|
reference_tokens.insert(reference_tokens.end(),
|
||||||
ptr.reference_tokens.begin(),
|
ptr.reference_tokens.begin(),
|
||||||
ptr.reference_tokens.end());
|
ptr.reference_tokens.end());
|
||||||
@@ -36473,7 +36461,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;
|
||||||
@@ -43264,6 +43252,24 @@ public:
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
||||||
void patch_inplace(const basic_json& json_patch)
|
void patch_inplace(const basic_json& json_patch)
|
||||||
{
|
{
|
||||||
|
// the operations below change *this while iterating json_patch: apply
|
||||||
|
// a copy when a value is applied as patch to itself or, with
|
||||||
|
// JSON_DIAGNOSTICS, to a value stored inside it
|
||||||
|
bool patch_aliases_this = (&json_patch == this);
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
for (const basic_json* p = json_patch.m_parent; !patch_aliases_this && p != nullptr; p = p->m_parent)
|
||||||
|
{
|
||||||
|
patch_aliases_this = (p == this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(patch_aliases_this))
|
||||||
|
{
|
||||||
|
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): *this is modified below
|
||||||
|
const basic_json json_patch_copy = json_patch;
|
||||||
|
patch_inplace(json_patch_copy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
basic_json& result = *this;
|
basic_json& result = *this;
|
||||||
// the valid JSON Patch operations
|
// the valid JSON Patch operations
|
||||||
enum class patch_operations {add, remove, replace, move, copy, test, invalid};
|
enum class patch_operations {add, remove, replace, move, copy, test, invalid};
|
||||||
|
|||||||
Generated
+17
-250
@@ -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");
|
||||||
|
|
||||||
@@ -2402,13 +2402,7 @@ indent_done:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
p = r.p;
|
p = r.p;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
// the decoded string is in the arena: next keeps the source offset
|
|
||||||
// of the opening quote plus one (0: unknown) for materialize()
|
|
||||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, static_cast<std::uint64_t>(r.len) | (static_cast<std::uint64_t>(s - b) << 32));
|
|
||||||
#else
|
|
||||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len);
|
push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len);
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -4949,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
|
||||||
@@ -4973,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
|
||||||
@@ -5001,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)
|
||||||
@@ -5011,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);
|
||||||
@@ -5388,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;
|
||||||
}
|
}
|
||||||
@@ -5468,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;
|
||||||
}
|
}
|
||||||
@@ -5488,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;
|
||||||
}
|
}
|
||||||
@@ -5510,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;
|
||||||
}
|
}
|
||||||
@@ -6120,176 +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.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
{
|
|
||||||
const bool known = first != std::string::npos && last != std::string::npos;
|
|
||||||
start = known ? first : std::string::npos;
|
|
||||||
end = known ? last : 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
|
|
||||||
{
|
|
||||||
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 += 2;
|
|
||||||
while (pos < size && src[pos] != '\n' && src[pos] != '\r')
|
|
||||||
{
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pos + 1 < size && src[pos + 1] == '*')
|
|
||||||
{
|
|
||||||
pos += 2;
|
|
||||||
while (pos < size && !(src[pos] == '*' && pos + 1 < size && src[pos + 1] == '/'))
|
|
||||||
{
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
pos += 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
|
|
||||||
{
|
|
||||||
const std::size_t size = d.size;
|
|
||||||
const std::size_t off = n.off;
|
|
||||||
std::size_t first = std::string::npos;
|
|
||||||
std::size_t last = std::string::npos;
|
|
||||||
if ((n.flags & node_flags::is_new) == 0)
|
|
||||||
{
|
|
||||||
switch (static_cast<value_t>(n.kind))
|
|
||||||
{
|
|
||||||
case value_t::string:
|
|
||||||
if ((n.flags & node_flags::storage) == 0)
|
|
||||||
{
|
|
||||||
// off is the first byte after the opening quote
|
|
||||||
if (off != 0)
|
|
||||||
{
|
|
||||||
first = off - 1;
|
|
||||||
last = 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], size, first);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
if ((n.flags & node_flags::storage) == 0)
|
|
||||||
{
|
|
||||||
first = off;
|
|
||||||
last = off + number_length(n);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::boolean:
|
|
||||||
first = off;
|
|
||||||
last = off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u);
|
|
||||||
break;
|
|
||||||
case value_t::null:
|
|
||||||
first = off;
|
|
||||||
last = off + 4u;
|
|
||||||
break;
|
|
||||||
case value_t::object:
|
|
||||||
case value_t::array:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// (a crafted image can point past the text)
|
|
||||||
set(first, last != std::string::npos && last <= size ? last : std::string::npos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the basic_json value of the subtree at n
|
@brief the basic_json value of the subtree at n
|
||||||
|
|
||||||
@@ -6297,22 +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;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true, position_source>;
|
|
||||||
#else
|
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
||||||
#endif
|
|
||||||
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),
|
||||||
@@ -6324,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:
|
||||||
@@ -6354,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{};
|
||||||
@@ -6378,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))
|
||||||
@@ -6396,12 +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)
|
|
||||||
const std::size_t start = (n->flags & node_flags::is_new) == 0 && n->off < d.size ? std::size_t{n->off} :
|
|
||||||
std::string::npos;
|
|
||||||
positions.set(start, start + 1);
|
|
||||||
#endif
|
|
||||||
if (object)
|
if (object)
|
||||||
{
|
{
|
||||||
sax.start_object(n->len);
|
sax.start_object(n->len);
|
||||||
@@ -6418,14 +6200,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
open = deep.data();
|
open = deep.data();
|
||||||
capacity = deep.size();
|
capacity = deep.size();
|
||||||
}
|
}
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||||
// (until an element is read, the container's own bracket is the last thing read)
|
|
||||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object, start, positions.end};
|
|
||||||
break;
|
break;
|
||||||
#else
|
|
||||||
open[depth++] = frame {nav::first(d, n), nav::end(d, n), object};
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
@@ -6465,15 +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
|
|
||||||
const std::size_t end = f.start == std::string::npos || f.last_end == std::string::npos ? std::string::npos : position_source::container_end(d.base[0], d.size, f.last_end);
|
|
||||||
// (an end of npos clears the start, see diagnostic_positions)
|
|
||||||
positions.set(end == std::string::npos ? end : end - 1, end);
|
|
||||||
if (depth > 1)
|
|
||||||
{
|
|
||||||
open[depth - 2].last_end = end;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (f.object)
|
if (f.object)
|
||||||
{
|
{
|
||||||
sax.end_object();
|
sax.end_object();
|
||||||
|
|||||||
@@ -826,6 +826,129 @@ TEST_CASE("JSON patch")
|
|||||||
CHECK(doc.at("foo") == "bar");
|
CHECK(doc.at("foo") == "bar");
|
||||||
}
|
}
|
||||||
#endif // !defined(JSON_NOEXCEPTION)
|
#endif // !defined(JSON_NOEXCEPTION)
|
||||||
|
|
||||||
|
SECTION("applying a value as patch to itself")
|
||||||
|
{
|
||||||
|
// a JSON patch is an array, so an array of operations can be
|
||||||
|
// applied to itself; the operations are read from the patch as
|
||||||
|
// it was before the first operation, as patch() does it
|
||||||
|
const json copy_op = {{"op", "copy"}, {"from", "/0"}, {"path", "/-"}};
|
||||||
|
json copy_ops = json::array();
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
copy_ops.push_back(copy_op);
|
||||||
|
}
|
||||||
|
|
||||||
|
json expected_copies = copy_ops;
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
expected_copies.push_back(copy_op);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("copy operations without spare capacity")
|
||||||
|
{
|
||||||
|
// a copy has exactly as much capacity as elements
|
||||||
|
json doc = copy_ops;
|
||||||
|
CHECK(doc.patch(doc) == expected_copies);
|
||||||
|
|
||||||
|
doc.patch_inplace(doc);
|
||||||
|
CHECK(doc == expected_copies);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
SECTION("patch stored inside the patched value")
|
||||||
|
{
|
||||||
|
// the parent pointers of JSON_DIAGNOSTICS make the aliasing detectable
|
||||||
|
const json op = {{"op", "replace"}, {"path", "/patch"}, {"value", 1}};
|
||||||
|
json doc = {{"a", 1}, {"patch", json::array({op})}};
|
||||||
|
const json expected = doc.patch(doc["patch"]);
|
||||||
|
|
||||||
|
doc.patch_inplace(doc["patch"]);
|
||||||
|
CHECK(doc == expected);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("copy operations with spare capacity")
|
||||||
|
{
|
||||||
|
// reserve while the array is empty: reallocating non-empty
|
||||||
|
// elements behind the library's back leaves stale parent
|
||||||
|
// pointers with JSON_DIAGNOSTICS
|
||||||
|
json doc = json::array();
|
||||||
|
doc.get_ref<json::array_t&>().reserve(100);
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
doc.push_back(copy_op);
|
||||||
|
}
|
||||||
|
CHECK(doc.patch(doc) == expected_copies);
|
||||||
|
|
||||||
|
doc.patch_inplace(doc);
|
||||||
|
CHECK(doc == expected_copies);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("remove operations")
|
||||||
|
{
|
||||||
|
json doc = R"([
|
||||||
|
{"op": "remove", "path": "/0"},
|
||||||
|
{"op": "remove", "path": "/0"},
|
||||||
|
{"op": "remove", "path": "/0"}
|
||||||
|
])"_json;
|
||||||
|
CHECK(doc.patch(doc) == json::array());
|
||||||
|
|
||||||
|
doc.patch_inplace(doc);
|
||||||
|
CHECK(doc == json::array());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("operations that read a changed element")
|
||||||
|
{
|
||||||
|
// the second operation is still a "test" operation, although
|
||||||
|
// the first one replaced it in the document
|
||||||
|
json doc = R"([
|
||||||
|
{"op": "replace", "path": "/1", "value": 1},
|
||||||
|
{"op": "test", "path": "/1", "value": 1},
|
||||||
|
{"op": "add", "path": "/-", "value": 2}
|
||||||
|
])"_json;
|
||||||
|
const json expected = R"([
|
||||||
|
{"op": "replace", "path": "/1", "value": 1},
|
||||||
|
1,
|
||||||
|
{"op": "add", "path": "/-", "value": 2},
|
||||||
|
2
|
||||||
|
])"_json;
|
||||||
|
CHECK(doc.patch(doc) == expected);
|
||||||
|
|
||||||
|
doc.patch_inplace(doc);
|
||||||
|
CHECK(doc == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("replacing the root")
|
||||||
|
{
|
||||||
|
json doc = R"([
|
||||||
|
{"op": "add", "path": "", "value": [1, 2]},
|
||||||
|
{"op": "add", "path": "/-", "value": 3},
|
||||||
|
{"op": "replace", "path": "", "value": {"a": 4}}
|
||||||
|
])"_json;
|
||||||
|
const json expected = {{"a", 4}};
|
||||||
|
CHECK(doc.patch(doc) == expected);
|
||||||
|
|
||||||
|
doc.patch_inplace(doc);
|
||||||
|
CHECK(doc == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("failing operation")
|
||||||
|
{
|
||||||
|
// the operations before the failing one remain applied
|
||||||
|
json doc = R"([
|
||||||
|
{"op": "add", "path": "/-", "value": 1},
|
||||||
|
{"op": "test", "path": "/0/op", "value": "remove"}
|
||||||
|
])"_json;
|
||||||
|
CHECK_THROWS_AS(doc.patch(doc), json::other_error&);
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(doc.patch_inplace(doc), json::other_error&);
|
||||||
|
CHECK(doc.size() == 3);
|
||||||
|
CHECK(doc.back() == 1);
|
||||||
|
}
|
||||||
|
#endif // !defined(JSON_NOEXCEPTION)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("errors")
|
SECTION("errors")
|
||||||
|
|||||||
@@ -845,6 +845,44 @@ TEST_CASE("JSON pointers")
|
|||||||
CHECK(ptr.to_string() == "/object/~1");
|
CHECK(ptr.to_string() == "/object/~1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("appending a pointer to itself")
|
||||||
|
{
|
||||||
|
// operator/= must not pass iterators into its own token vector to
|
||||||
|
// insert(); each pointer is appended to itself and must yield the
|
||||||
|
// pointer whose string representation is the original one twice
|
||||||
|
const std::vector<std::string> pointers =
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
"/",
|
||||||
|
"/a",
|
||||||
|
"/a/b",
|
||||||
|
"/foo/0/-",
|
||||||
|
"/a~1b/~0c/~01",
|
||||||
|
"/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& s : pointers)
|
||||||
|
{
|
||||||
|
CAPTURE(s)
|
||||||
|
json::json_pointer ptr(s);
|
||||||
|
const json::json_pointer expected(s + s);
|
||||||
|
|
||||||
|
json::json_pointer& ret = (ptr /= ptr);
|
||||||
|
CHECK(&ret == &ptr);
|
||||||
|
CHECK(ptr == expected);
|
||||||
|
CHECK(ptr.to_string() == s + s);
|
||||||
|
|
||||||
|
// appending to itself again doubles the pointer once more
|
||||||
|
ptr /= ptr;
|
||||||
|
CHECK(ptr.to_string() == s + s + s + s);
|
||||||
|
|
||||||
|
// operator/ appends to a copy and leaves the operand unchanged
|
||||||
|
const json::json_pointer original(s);
|
||||||
|
CHECK((original / original) == expected);
|
||||||
|
CHECK(original.to_string() == s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("equality comparison")
|
SECTION("equality comparison")
|
||||||
{
|
{
|
||||||
std::string ptr_string{"/foo/bar"};
|
std::string ptr_string{"/foo/bar"};
|
||||||
|
|||||||
@@ -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