mirror of
https://github.com/nlohmann/json.git
synced 2026-10-27 16:28:57 +00:00
With JSON_DIAGNOSTIC_POSITIONS, materialize() now gives every value the start_pos()/end_pos() that basic_json::parse() would give it, so exceptions about materialized values name the same bytes (#5864). - json_sax_dom_parser takes the lexer type as a defaulted template parameter; materialize() passes position_source, which reports the positions it derives from the index. diagnostic_positions treats npos as "no position" and keeps ranges all-or-nothing. - Strings with escapes keep the offset of their opening quote plus one in `next` (only with the macro); materialize() scans from there to the closing quote. 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. - In editable documents, values written by edits, arrays and objects whose elements changed, and containers whose last element has no position get none. Images keep the flag is_new, and load() checks that `next` of a string is within the text. Nothing changes on the parse path without the macro. The view's own exceptions still carry no byte range, so the tests that compare them with those of materialized values skip it under the macro, as they skip the path under JSON_DIAGNOSTICS. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
154 lines
7.2 KiB
C++
154 lines
7.2 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <cstddef> // size_t
|
|
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
|
#include <cstring> // memcpy
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
/// The largest input a document accepts, in bytes. Offsets and node counts are
|
|
/// 32 bits wide; the limit keeps 16 bytes (the width of the scanner's steps)
|
|
/// below 2^32, so that a position one step past the end of the text fits.
|
|
constexpr std::size_t max_input_size() noexcept
|
|
{
|
|
return 0xFFFFFFEFu;
|
|
}
|
|
|
|
/// node flags
|
|
struct node_flags
|
|
{
|
|
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
|
|
static constexpr std::uint8_t edited = 2; ///< string or number token lives in the edit arena (editable documents)
|
|
static constexpr std::uint8_t storage = escaped | edited; ///< mask: where a string or number token lives (index into document_data::base)
|
|
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
|
static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents)
|
|
static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position
|
|
static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters
|
|
};
|
|
|
|
/// the kind of a node: the value_t of its value, or link
|
|
enum class node_kind : std::uint8_t
|
|
{
|
|
null = static_cast<std::uint8_t>(value_t::null),
|
|
object = static_cast<std::uint8_t>(value_t::object),
|
|
array = static_cast<std::uint8_t>(value_t::array),
|
|
string = static_cast<std::uint8_t>(value_t::string), // NOLINT(build/include_what_you_use): an enumerator, not std::string
|
|
boolean = static_cast<std::uint8_t>(value_t::boolean),
|
|
number_integer = static_cast<std::uint8_t>(value_t::number_integer),
|
|
number_unsigned = static_cast<std::uint8_t>(value_t::number_unsigned),
|
|
number_float = static_cast<std::uint8_t>(value_t::number_float),
|
|
/// an entry of an edited sequence that stands for a value stored elsewhere
|
|
/// (the address of the value's node is kept in the len/next bytes); placed
|
|
/// after value_t::discarded, which no node ever has
|
|
link = static_cast<std::uint8_t>(value_t::discarded) + 1,
|
|
};
|
|
|
|
/// One entry of the flat index, in document order. An object's members are
|
|
/// stored as key node followed by the value's subtree. Integers keep their
|
|
/// converted 64-bit value in the len/next bytes (the node after a scalar is
|
|
/// always the next one, and the token length follows from `extra`).
|
|
struct node
|
|
{
|
|
node_kind kind; ///< the kind of the value, or link
|
|
std::uint8_t flags; ///< node_flags
|
|
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); 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");
|
|
|
|
// images store the node kinds as bytes, and the test below (object and array
|
|
// are 1 and 2) and those of the number kinds depend on this numbering
|
|
static_assert(static_cast<std::uint8_t>(node_kind::null) == 0 && static_cast<std::uint8_t>(node_kind::object) == 1
|
|
&& static_cast<std::uint8_t>(node_kind::array) == 2 && static_cast<std::uint8_t>(node_kind::string) == 3
|
|
&& static_cast<std::uint8_t>(node_kind::boolean) == 4 && static_cast<std::uint8_t>(node_kind::number_integer) == 5
|
|
&& static_cast<std::uint8_t>(node_kind::number_unsigned) == 6 && static_cast<std::uint8_t>(node_kind::number_float) == 7,
|
|
"the node format depends on the numbering of node_kind");
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept
|
|
{
|
|
return static_cast<unsigned>(n.kind) - 1u <= 1u;
|
|
}
|
|
|
|
/// the value a link node stands for
|
|
NLOHMANN_VIEW_ALWAYS_INLINE const node* link_target(const node& n) noexcept
|
|
{
|
|
const node* t = nullptr;
|
|
std::memcpy(static_cast<void*>(&t), reinterpret_cast<const unsigned char*>(&n) + 8, sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
return t;
|
|
}
|
|
|
|
/// let the entry n stand for the value at target (and mark the value)
|
|
inline void make_link(node& n, node* target) noexcept
|
|
{
|
|
target->flags = static_cast<std::uint8_t>(target->flags | node_flags::linked);
|
|
n = node{};
|
|
n.kind = node_kind::link;
|
|
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, static_cast<const void*>(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
/// the converted value of an integer node: len is its low half, next its high
|
|
/// half (on little-endian targets the two words are the value in memory)
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept
|
|
{
|
|
#if NLOHMANN_VIEW_LITTLE_ENDIAN
|
|
std::uint64_t v = 0;
|
|
std::memcpy(&v, reinterpret_cast<const unsigned char*>(&n) + 8, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
return v;
|
|
#else
|
|
return static_cast<std::uint64_t>(n.len) | (static_cast<std::uint64_t>(n.next) << 32);
|
|
#endif
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noexcept
|
|
{
|
|
#if NLOHMANN_VIEW_LITTLE_ENDIAN
|
|
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, &v, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
#else
|
|
n.len = static_cast<std::uint32_t>(v);
|
|
n.next = static_cast<std::uint32_t>(v >> 32);
|
|
#endif
|
|
}
|
|
|
|
/// token length of a number node
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept
|
|
{
|
|
return n.kind == node_kind::number_float ? n.len
|
|
: (n.extra & 0xFFu) + (n.kind == node_kind::number_integer ? 1u : 0u);
|
|
}
|
|
|
|
/// estimated number of nodes for an input of `size` bytes (one node per ~12
|
|
/// bytes covers typical documents without regrowth)
|
|
inline std::size_t estimate_nodes(std::size_t size) noexcept
|
|
{
|
|
return (size / 12) + 16;
|
|
}
|
|
|
|
/// estimated number of nodes for the input [src, src + size): pretty-printed
|
|
/// input (whitespace after the first byte) needs about a node per 12 bytes,
|
|
/// minified input up to one per 4 (yyjson tells the two apart the same way)
|
|
inline std::size_t estimate_nodes(const char* src, std::size_t size) noexcept
|
|
{
|
|
return size >= 2 && (src[1] == ' ' || src[1] == '\n' || src[1] == '\r' || src[1] == '\t') ? estimate_nodes(size) : (size / 4) + 16;
|
|
}
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|