mirror of
https://github.com/nlohmann/json.git
synced 2026-10-27 16:28:57 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c130c33d9 |
No files matched your search
@@ -32,9 +32,20 @@ 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
|
||||
would be; the JSON nesting depth is limited only by available memory, as for `BasicJsonType::parse()` itself.
|
||||
|
||||
Unlike parsing with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values
|
||||
produced by `materialize()` do not carry source positions. The index has the start offset of every value, but not the
|
||||
end of arrays, objects, and escaped strings, which the positions also need.
|
||||
With [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values get the source
|
||||
positions [`BasicJsonType::parse()`](../basic_json/parse.md) would give them, also for the subtree of a view (the
|
||||
positions are those in the document's whole source text), and exceptions about them name the same bytes. For values of
|
||||
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
|
||||
never caches the result.
|
||||
@@ -62,8 +73,7 @@ never caches the result.
|
||||
|
||||
- [`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
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values (not
|
||||
produced by `materialize()`)
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values
|
||||
- [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
|
||||
|
||||
@@ -113,6 +113,7 @@ 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
|
||||
- [`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
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -149,10 +149,12 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
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
|
||||
is read to its end into a buffer the document owns, and parsing starts after that.
|
||||
- **No source positions on `materialize()`.** Even with
|
||||
- **Fewer source positions after edits.** With
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled,
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) does not set them. The index has the start offset of every
|
||||
value, but not the end of arrays, objects, and escaped strings, which the positions also need.
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`.
|
||||
In an [editable document](../api/json_editable_document.md), a value written by an edit has none, and neither has
|
||||
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)/
|
||||
[`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
|
||||
|
||||
@@ -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 |
|
||||
| 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 |
|
||||
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself |
|
||||
| 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)) |
|
||||
|
||||
<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,6 +218,11 @@ packet-beta
|
||||
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
|
||||
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
|
||||
([`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
|
||||
@@ -277,7 +282,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
|
||||
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`
|
||||
rebuilds the indexes. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
||||
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`,
|
||||
and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them.
|
||||
|
||||
## Input adapters
|
||||
|
||||
@@ -230,7 +230,10 @@ struct diagnostic_positions
|
||||
{
|
||||
// Lexer has read the first character of the container, so
|
||||
// subtract 1 from the position to get the correct start position.
|
||||
v.start_position = lexer->get_chars_read_total() - 1;
|
||||
// (npos: a value without a source position, see materialize() of
|
||||
// json_view)
|
||||
const std::size_t read = lexer->get_chars_read_total();
|
||||
v.start_position = read == std::string::npos ? read : read - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +248,12 @@ struct diagnostic_positions
|
||||
{
|
||||
// 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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +270,12 @@ struct diagnostic_positions
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
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())
|
||||
{
|
||||
@@ -473,8 +488,12 @@ read up to it.
|
||||
@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
|
||||
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>
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false, typename LexerType = lexer<BasicJsonType, InputAdapterType>>
|
||||
class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
@@ -483,7 +502,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using binary_t = typename BasicJsonType::binary_t;
|
||||
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
||||
using lexer_t = LexerType;
|
||||
|
||||
/*!
|
||||
@param[in,out] r reference to a JSON value that is manipulated while
|
||||
|
||||
@@ -56,8 +56,8 @@ using parser_callback_t =
|
||||
template<typename SAX>
|
||||
struct sax_ignores_number_text : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect, LexerType>> : std::true_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||
|
||||
@@ -1193,7 +1193,13 @@ indent_done:
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,7 +80,8 @@ constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
|
||||
/// 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
|
||||
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
||||
/// are not finite become null, as dump() writes them.
|
||||
/// are not finite become null, as dump() writes them. Nodes keep the flag
|
||||
/// 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)
|
||||
{
|
||||
struct frame
|
||||
@@ -103,16 +104,18 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
arena_tail.append(d.str(s), s.len);
|
||||
r.flags = node_flags::escaped;
|
||||
}
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | (s.flags & node_flags::is_new));
|
||||
return r;
|
||||
};
|
||||
const auto copy_node = [&](const node * 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))
|
||||
{
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
r.extra = 0;
|
||||
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
||||
r.len = 0; // counted below
|
||||
@@ -129,7 +132,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());
|
||||
text_tail.append(d.str(*v), number_length(*v));
|
||||
}
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
case value_t::number_float:
|
||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||
@@ -139,22 +142,23 @@ 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.kind = node_kind::null;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
}
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(t, v->len);
|
||||
r.extra = 0xFFFFu; // the digit layout is not recorded
|
||||
}
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & (node_flags::is_true | node_flags::is_new));
|
||||
break;
|
||||
case value_t::null:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
}
|
||||
out.push_back(r);
|
||||
@@ -515,13 +519,15 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
std::vector<float_range> floats;
|
||||
const auto check_string = [&](const node & n) -> bool
|
||||
{
|
||||
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
|
||||
if ((n.flags & ~(node_flags::escaped | node_flags::is_new)) != 0 || n.extra != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
||||
const std::size_t limit = decoded ? arena_size : text_size;
|
||||
if (n.off > limit || n.len > limit - n.off)
|
||||
// (next: the source offset of a decoded string's opening quote plus
|
||||
// one, or 0; see node)
|
||||
if (n.off > limit || n.len > limit - n.off || n.next > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -593,13 +599,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
{
|
||||
case value_t::null:
|
||||
// (the offset of a literal is read to size the output of dump())
|
||||
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::boolean:
|
||||
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
|
||||
if ((n.flags & ~(node_flags::is_true | node_flags::is_new)) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -613,7 +619,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
if (n.flags != 0 || !number_in_bounds(n))
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || !number_in_bounds(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -635,7 +641,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
case value_t::object:
|
||||
{
|
||||
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
||||
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,176 @@ namespace detail
|
||||
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
|
||||
|
||||
@@ -33,14 +203,22 @@ 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
|
||||
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().
|
||||
Without a lexer the handler records no source positions
|
||||
(JSON_DIAGNOSTIC_POSITIONS).
|
||||
|
||||
With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with
|
||||
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>
|
||||
BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
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>;
|
||||
#endif
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
// the open containers: their next element (or key of the next member),
|
||||
@@ -52,9 +230,15 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
const node* pos;
|
||||
const node* end;
|
||||
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
|
||||
// (with positions, it goes through the handler, which sets them)
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
{
|
||||
case value_t::string:
|
||||
@@ -76,10 +260,16 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
BasicJsonType result;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
position_source positions;
|
||||
sax_t sax(result, true, &positions);
|
||||
#else
|
||||
sax_t sax(result, true);
|
||||
const string_t no_token{};
|
||||
#endif
|
||||
const string_t no_token {};
|
||||
// (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::vector<frame> deep{};
|
||||
@@ -94,6 +284,16 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
std::size_t depth = 0;
|
||||
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
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
@@ -102,6 +302,12 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
case value_t::array:
|
||||
{
|
||||
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)
|
||||
{
|
||||
sax.start_object(n->len);
|
||||
@@ -118,8 +324,14 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
open = deep.data();
|
||||
capacity = deep.size();
|
||||
}
|
||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// (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;
|
||||
#else
|
||||
open[depth++] = frame {nav::first(d, n), nav::end(d, n), object};
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
case value_t::string:
|
||||
{
|
||||
@@ -159,6 +371,15 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
frame& f = open[depth - 1];
|
||||
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)
|
||||
{
|
||||
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::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 next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||
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)
|
||||
};
|
||||
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;
|
||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||
friend class ::nlohmann::detail::binary_reader;
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
||||
|
||||
Generated
+25
-6
@@ -15629,7 +15629,10 @@ struct diagnostic_positions
|
||||
{
|
||||
// Lexer has read the first character of the container, so
|
||||
// subtract 1 from the position to get the correct start position.
|
||||
v.start_position = lexer->get_chars_read_total() - 1;
|
||||
// (npos: a value without a source position, see materialize() of
|
||||
// json_view)
|
||||
const std::size_t read = lexer->get_chars_read_total();
|
||||
v.start_position = read == std::string::npos ? read : read - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15644,6 +15647,12 @@ struct diagnostic_positions
|
||||
{
|
||||
// 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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15660,6 +15669,12 @@ struct diagnostic_positions
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
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())
|
||||
{
|
||||
@@ -15872,8 +15887,12 @@ read up to it.
|
||||
@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
|
||||
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>
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false, typename LexerType = lexer<BasicJsonType, InputAdapterType>>
|
||||
class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
@@ -15882,7 +15901,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using binary_t = typename BasicJsonType::binary_t;
|
||||
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
||||
using lexer_t = LexerType;
|
||||
|
||||
/*!
|
||||
@param[in,out] r reference to a JSON value that is manipulated while
|
||||
@@ -22515,8 +22534,8 @@ using parser_callback_t =
|
||||
template<typename SAX>
|
||||
struct sax_ignores_number_text : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect, LexerType>> : std::true_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||
@@ -36454,7 +36473,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
friend class ::nlohmann::detail::binary_writer;
|
||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||
friend class ::nlohmann::detail::binary_reader;
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
||||
|
||||
Generated
+250
-17
@@ -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::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 next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||
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)
|
||||
};
|
||||
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
||||
|
||||
@@ -2402,7 +2402,13 @@ indent_done:
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -4943,7 +4949,8 @@ constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
|
||||
/// 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
|
||||
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
||||
/// are not finite become null, as dump() writes them.
|
||||
/// are not finite become null, as dump() writes them. Nodes keep the flag
|
||||
/// 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)
|
||||
{
|
||||
struct frame
|
||||
@@ -4966,16 +4973,18 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
arena_tail.append(d.str(s), s.len);
|
||||
r.flags = node_flags::escaped;
|
||||
}
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | (s.flags & node_flags::is_new));
|
||||
return r;
|
||||
};
|
||||
const auto copy_node = [&](const node * 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))
|
||||
{
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
r.extra = 0;
|
||||
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
||||
r.len = 0; // counted below
|
||||
@@ -4992,7 +5001,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());
|
||||
text_tail.append(d.str(*v), number_length(*v));
|
||||
}
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
case value_t::number_float:
|
||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||
@@ -5002,22 +5011,23 @@ 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.kind = node_kind::null;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
}
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(t, v->len);
|
||||
r.extra = 0xFFFFu; // the digit layout is not recorded
|
||||
}
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & (node_flags::is_true | node_flags::is_new));
|
||||
break;
|
||||
case value_t::null:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
}
|
||||
out.push_back(r);
|
||||
@@ -5378,13 +5388,15 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
std::vector<float_range> floats;
|
||||
const auto check_string = [&](const node & n) -> bool
|
||||
{
|
||||
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
|
||||
if ((n.flags & ~(node_flags::escaped | node_flags::is_new)) != 0 || n.extra != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
||||
const std::size_t limit = decoded ? arena_size : text_size;
|
||||
if (n.off > limit || n.len > limit - n.off)
|
||||
// (next: the source offset of a decoded string's opening quote plus
|
||||
// one, or 0; see node)
|
||||
if (n.off > limit || n.len > limit - n.off || n.next > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -5456,13 +5468,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
{
|
||||
case value_t::null:
|
||||
// (the offset of a literal is read to size the output of dump())
|
||||
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::boolean:
|
||||
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
|
||||
if ((n.flags & ~(node_flags::is_true | node_flags::is_new)) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -5476,7 +5488,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
if (n.flags != 0 || !number_in_bounds(n))
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || !number_in_bounds(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -5498,7 +5510,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
case value_t::object:
|
||||
{
|
||||
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
||||
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -6108,6 +6120,176 @@ namespace detail
|
||||
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
|
||||
|
||||
@@ -6115,14 +6297,22 @@ 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
|
||||
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().
|
||||
Without a lexer the handler records no source positions
|
||||
(JSON_DIAGNOSTIC_POSITIONS).
|
||||
|
||||
With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with
|
||||
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>
|
||||
BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
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>;
|
||||
#endif
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
// the open containers: their next element (or key of the next member),
|
||||
@@ -6134,9 +6324,15 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
const node* pos;
|
||||
const node* end;
|
||||
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
|
||||
// (with positions, it goes through the handler, which sets them)
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
{
|
||||
case value_t::string:
|
||||
@@ -6158,10 +6354,16 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
BasicJsonType result;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
position_source positions;
|
||||
sax_t sax(result, true, &positions);
|
||||
#else
|
||||
sax_t sax(result, true);
|
||||
const string_t no_token{};
|
||||
#endif
|
||||
const string_t no_token {};
|
||||
// (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::vector<frame> deep{};
|
||||
@@ -6176,6 +6378,16 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
std::size_t depth = 0;
|
||||
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
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
@@ -6184,6 +6396,12 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
case value_t::array:
|
||||
{
|
||||
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)
|
||||
{
|
||||
sax.start_object(n->len);
|
||||
@@ -6200,8 +6418,14 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
open = deep.data();
|
||||
capacity = deep.size();
|
||||
}
|
||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// (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;
|
||||
#else
|
||||
open[depth++] = frame {nav::first(d, n), nav::end(d, n), object};
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
case value_t::string:
|
||||
{
|
||||
@@ -6241,6 +6465,15 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
frame& f = open[depth - 1];
|
||||
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)
|
||||
{
|
||||
sax.end_object();
|
||||
|
||||
@@ -749,6 +749,20 @@ std::string exception_of(F f)
|
||||
}
|
||||
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
|
||||
|
||||
// compares a view with the ordered_json value materialize() gives for it:
|
||||
@@ -957,24 +971,24 @@ TEST_CASE("json_view element access and iteration")
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
if (!j.is_object())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v["a"]); }) == exception_of([&] { static_cast<void>(j["a"]); }));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["a"]); }) == json_exception_of([&] { static_cast<void>(j["a"]); }));
|
||||
}
|
||||
if (!j.is_array())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == exception_of([&] { static_cast<void>(j[0]); }));
|
||||
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == json_exception_of([&] { static_cast<void>(j[0]); }));
|
||||
}
|
||||
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")); }) == exception_of([&] { static_cast<void>(j.at("missing")); }));
|
||||
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)); }) == exception_of([&] { static_cast<void>(j.at(5)); }));
|
||||
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("missing")); }) == json_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(5)); }) == json_exception_of([&] { static_cast<void>(j.at(5)); }));
|
||||
if (!(j.is_object() && j.empty())) // (key() of an end iterator)
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == exception_of([&] { static_cast<void>(j.begin().key()); }));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == json_exception_of([&] { static_cast<void>(j.begin().key()); }));
|
||||
}
|
||||
if (!j.empty() || j.is_null())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == exception_of([&] { static_cast<void>(j.front()); }));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == exception_of([&] { static_cast<void>(j.back()); }));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == json_exception_of([&] { static_cast<void>(j.front()); }));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == json_exception_of([&] { static_cast<void>(j.back()); }));
|
||||
}
|
||||
#endif
|
||||
CHECK(v.contains("a") == j.contains("a"));
|
||||
|
||||
@@ -326,7 +326,8 @@ TEST_CASE("json_view from_json")
|
||||
CHECK(d.root().get<std::unordered_map<std::string, int>>().at("a") == 3);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS
|
||||
// (the exceptions of views have neither the path nor the byte range of the value)
|
||||
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
||||
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])");
|
||||
@@ -392,7 +393,7 @@ TEST_CASE("json_view from_json")
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
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\"");
|
||||
#if !JSON_DIAGNOSTICS
|
||||
#if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
||||
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
|
||||
@@ -440,10 +441,11 @@ TEST_CASE("json_view from_json")
|
||||
CHECK(o.root().get<shape>().center.y == 2);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS
|
||||
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
|
||||
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 j = d.root().materialize();
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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