* Add shared detail helpers for the #5891 cleanup Add detail::is_primitive(value_t), detail::hex4_lower(), detail::utf8_shape(), detail::input_format_name(), detail::empty_input_message(), the exception ids 116, 319, 320 and 416 to detail::exception_id, and make detail::is_ascii_copyable() constexpr. Later commits switch the duplicated code over to them. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.4: BSON writers share one write-pass UTF-8 sanitizer (#5891) write_bson_entry_header and write_bson_string re-derived the error-handler policy differently. Both now use sanitize_utf8_for_bson_write, which treats strict like keep (the size pass already validated) and sanitizes replace/ignore into the bytes the size pass measured. No context plumbing is needed. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.1: one MessagePack container prefix helper (#5891) Array and object prefixes differed only in the fix marker and the 16/32-bit markers; both now delegate to write_msgpack_container_prefix. Output is byte-identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.2: one iterative walker for CBOR and MessagePack (#5891) write_cbor_iterative and write_msgpack_iterative (and their *_value_or_push helpers) were identical but for the head, scalar, and key writers. They are now one template over a small policy struct, so everything inlines. BON8 keeps its own walker (open string run, closing marker), noted in a comment. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.3, 2.1.8: one UBJSON container start helper, shared frame base (#5891) write_ubjson_start_array/_object are now write_ubjson_start_container<IsArray>, keeping the array-only begin()+1 scan and excessive_valueless rule. ubjson_frame derives from binary_container_frame instead of duplicating it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.4: write_bytes helper for the byte-string write sites (#5891) The 13 oa.write_characters(reinterpret_cast<const CharType*>(x.data()), n) sites now call write_bytes(const void*, size_t); copy_characters sites and the buffer-based writes are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.5: shared object-key writer for CBOR and MessagePack (#5891) write_cbor_key and write_msgpack_key were the same SFINAE pair apart from the string/scalar writers; both now delegate to write_object_key<Walk>. UBJSON key and to_format_length stay as they are. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.10: writer-side format names from input_format_name (#5891) The CBOR/MessagePack/UBJSON/BSON/BJData/BON8 literals in throw_on_discarded and throw_integer_out_of_range calls now come from detail::input_format_name; the texts are identical, and UBJSON vs BJData still follows use_bjdata. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891) 1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.6: use string_t-aware to_string/concat in meta() (#5891) Replaces the four std::to_string sites; rendered text unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: use detail::is_primitive in json.hpp switches (#5891) has_no_children, set_parents, max_size and the two diff walkers. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.1: factor insert()/iterator throw helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.2: share swap() overloads via swap_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.4: factor primitive reset and range check in erase()/range ctor (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.6: share push_back array bodies via push_back_array_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.7: route parse/accept/sax_parse through *_impl helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.8: share the vector-returning to_* bodies via to_binary_vector (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.10: merge destroy_string/destroy_binary into destroy_pointer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.1/1.7: exceptions: share what_impl, flatten diagnostics switch (#5891) The four create() bodies build their message through one protected static helper; the diagnostics parent walk handles array/object and ignores all other types. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.2/2.4.3/1.7: json_pointer: share slash and escape checks, drop primitive case lists (#5891) The leading-slash and invalid '~' escape checks are shared helpers; the reference-token walks test object and array first and treat everything else as primitive. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7/2.4.9: iter_impl, iteration_proxy: drop primitive case lists, share compare preamble (#5891) Object and array stay the first branches; everything else is the primitive case. operator== and operator< share the different-container check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5/2.4.6: from_json: share get_number_value and reserve_if_possible (#5891) The three number cases of get_arithmetic_value and the arithmetic from_json share one helper (boolean stays accepted only by the latter); the array and object reserve overload pairs become one. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.8: ordered_map: share erase_impl between the key erase overloads (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: json_pointer: flatten walk drops the primitive case list (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.2/1.3/1.5/2.5.1/2.5.2/2.5.5: share helpers in the view code (#5891) - detail::exception_id at every numeric id; throw_* helpers take it - hex_byte, hex4_lower, utf8_shape replace local hex/table code - throw_array_index_out_of_range/_past_end, throw_key_not_found - has_native_float_format replaces the hand-written IEEE check No change of messages, ids or output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: use utf8_shape in get_bon8_string, add exhaustive UTF-8 validator test (#5891) get_bon8_string takes the valid range of the second byte from utf8_shape() (0xF5..0xF7 stay errors, unget behavior unchanged). validate_one_utf8 keeps its if-chain (hot bulk path); the new test checks decode, validate_one_utf8, utf8_shape, json::accept and the BON8 reader against each other for every lead and second byte. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.1: get_cbor_sized_bytes for the CBOR chunk getters (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.2: get_string_with_length for the plain-order string sites (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.3, 2.3.4, 2.3.10: unsupported_key_type, empty_input_message, input_format_name in the readers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.7, 2.3.9: read_object_key and leave_container_and_check for CBOR/MessagePack/BON8 (#5891) UBJSON keys (different getter signature) and its container tail stay as is. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.5: json_sax_dom_error_state base for the DOM SAX parsers (#5891) Holds errored, allow_exceptions, parse_error() and is_errored(). The scalar handlers stay in each class (parse hot path). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.6: table of control-character messages in the lexer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.8: finish_input helper for the strict-EOF block in the parser (#5891) sax_parse keeps its if (result) guard and return value. Also adds the clang-tidy NOLINT for the 2.3.6 message table. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: share parse_array_index between json_pointer and json_view (#5891) Move json_pointer::parse_array_index() and its status enum to namespace detail (json_pointer keeps a forwarding member), and let the json_view pointer resolution use it instead of its own copy of the RFC 6901 index grammar. Both accepted the same tokens with the same outcome. Also update the comment on detail::decode(), which listed four of the five UTF-8 validators and now points to the test that keeps them in sync. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5: keep the inlined number cases in from_json(ArithmeticType&) (#5891) Calling get_number_value() from from_json(ArithmeticType&) made GetArray<std::array<int, 1000>> 2.4x slower (it changes the inlining of the std::array conversion), so that overload keeps its own switch, with a comment. get_arithmetic_value() still uses the shared helper. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: force-inline detail::parse_array_index (#5891) Moving parse_array_index out of json_pointer stopped clang from inlining it into the pointer resolution (JsonPointerAt/citm_catalog +17%, JsonPointerAt/twitter +8%); JSON_HEDLEY_ALWAYS_INLINE restores the baseline. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: keep get_bon8_string's own UTF-8 ranges (#5891) Taking the second-byte range from utf8_shape() made BON8 decoding 2-3% slower (FromBinaryShape/scalars, FromBinaryBuffer, alignment-pinned builds), so the byte path keeps its hand-written ranges. The exhaustive validator test still checks that it accepts the same sequences as the other validators. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI failures: constexpr, NRVO, cppcheck, IWYU (#5891) - json_sax_dom_error_state::is_errored() cannot be constexpr: the class is not a literal type (GCC 4.8, GCC -Wpedantic, Clang 3.5). - to_binary_vector: destroy the vector writer before returning, as the output_vector_sink sets the size of the vector in its destructor. Without NRVO (MSVC Debug in C++14 mode), the vector was moved out before it was resized. - parser::sax_parse: return false instead of result after the early exit (cppcheck identicalConditionAfterEarlyExit). - json.hpp: export detail/exceptions.hpp for IWYU, so json_view.hpp can name exception_id without including a detail header. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: throw helpers take input_format_t, simpler type checks - throw_on_discarded() and throw_integer_out_of_range() take an input_format_t and call input_format_name() themselves - has_no_children(), set_parents() and max_size() check array, then object, instead of calling is_primitive() first - parse_impl() chains like accept_impl(); sax_parse_impl() uses if/else - std::array for the control character messages; negation<> in write_object_key(); clarify the is_primitive() comment Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_view: use exception_id in the value() overloads Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_sax: assert the invariant of place_container for the static analyzer Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
862 lines
29 KiB
C++
862 lines
29 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 <array> // array
|
|
#include <cmath> // isinf, isnan
|
|
#include <cstddef> // size_t
|
|
#include <cstdint> // int64_t, uint8_t, uint32_t, uint64_t
|
|
#include <cstring> // memmove
|
|
#include <limits> // numeric_limits
|
|
#include <string> // string, to_string
|
|
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_convertible, is_floating_point, is_same, is_signed
|
|
#include <utility> // forward
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <nlohmann/detail/view/document_data.hpp>
|
|
#include <nlohmann/detail/view/edit_storage.hpp>
|
|
#include <nlohmann/detail/view/errors.hpp>
|
|
#include <nlohmann/detail/view/lookup.hpp>
|
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
#include <nlohmann/detail/view/node.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
|
|
template<typename BasicJsonType, bool Editable>
|
|
class basic_json_view;
|
|
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
/// the index of the first true condition (the number of conditions if none is)
|
|
template<bool... Conditions>
|
|
struct first_true : std::integral_constant<int, 0> {};
|
|
|
|
template<bool... Conditions>
|
|
struct first_true<false, Conditions...> : std::integral_constant < int, 1 + first_true<Conditions...>::value > {};
|
|
|
|
/// Checks a string the way basic_json's serializer does when it writes it
|
|
/// (type_error.316 with the same message), so that an editable document
|
|
/// only holds valid UTF-8: the error is at the first byte that no
|
|
/// well-formed sequence can continue with (Unicode, Table 3-7).
|
|
inline void check_utf8(const char* s, std::size_t n)
|
|
{
|
|
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
for (std::size_t i = 0; i < n;)
|
|
{
|
|
const unsigned char c = p[i];
|
|
if (c < 0x80)
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
const utf8_sequence_shape shape = utf8_shape(c);
|
|
const std::size_t len = shape.length;
|
|
if (len == 0)
|
|
{
|
|
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c)));
|
|
}
|
|
for (std::size_t k = 1; k < len; ++k)
|
|
{
|
|
if (i + k == n)
|
|
{
|
|
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1])));
|
|
}
|
|
const unsigned char b = p[i + k];
|
|
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF))
|
|
{
|
|
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b)));
|
|
}
|
|
}
|
|
i += len;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@brief the edits of an editable basic_json_document
|
|
|
|
Values are accepted as views (of any document), BasicJsonType values, and
|
|
everything BasicJsonType can be constructed from. The source text is never
|
|
written: new values go to storage owned by the document (see
|
|
edit_storage.hpp).
|
|
*/
|
|
template<typename BasicJsonType, typename View>
|
|
class editor
|
|
{
|
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
|
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
|
using number_float_t = typename BasicJsonType::number_float_t;
|
|
using string_t = typename BasicJsonType::string_t;
|
|
using string_view_t = typename View::string_view_t;
|
|
using nav = navigation<true>;
|
|
|
|
public:
|
|
explicit editor(document_data& d) noexcept
|
|
: m_doc(d)
|
|
{}
|
|
|
|
/// replace a value; returns its view
|
|
template<typename V>
|
|
View set(const View& target, V&& value)
|
|
{
|
|
node* const slot = own(target);
|
|
const encoded e = encode(std::forward<V>(value));
|
|
assign(slot, e, nullptr, false);
|
|
return View(&m_doc, slot);
|
|
}
|
|
|
|
/// set a member (appended if missing; a null value becomes an object);
|
|
/// returns a view of the member value
|
|
template<typename V>
|
|
View set(const View& object, string_view_t key, V&& value)
|
|
{
|
|
node* const o = own(object);
|
|
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
|
{
|
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
|
}
|
|
check_utf8(key.data(), key.size());
|
|
const encoded e = encode(std::forward<V>(value));
|
|
if (o->kind == static_cast<std::uint8_t>(value_t::null))
|
|
{
|
|
become_empty(o, value_t::object);
|
|
}
|
|
// an existing member: assign it (and drop later duplicates, so that
|
|
// lookups, iteration, and materialize() agree)
|
|
node* slot = nullptr;
|
|
bool duplicates = false;
|
|
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
|
{
|
|
if (key_equals(*k, key))
|
|
{
|
|
if (slot != nullptr)
|
|
{
|
|
duplicates = true;
|
|
break;
|
|
}
|
|
slot = const_cast<node*>(nav::value(k + 1)); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
|
}
|
|
}
|
|
if (slot != nullptr)
|
|
{
|
|
if (duplicates)
|
|
{
|
|
erase_members(o, key, true);
|
|
}
|
|
assign(slot, e, o, true);
|
|
return View(&m_doc, slot);
|
|
}
|
|
const node k = string_node(key.data(), key.size());
|
|
slot = new_slot(e);
|
|
node* const h = block_of(m_doc, o, 2);
|
|
h[h->next] = k;
|
|
make_link(h[h->next + 1], slot);
|
|
h->next += 2;
|
|
++h->len;
|
|
++o->len;
|
|
return View(&m_doc, slot);
|
|
}
|
|
|
|
/// assign an existing array element; returns a view of it
|
|
template<typename V>
|
|
View set(const View& array, std::size_t idx, V&& value)
|
|
{
|
|
node* const a = own(array);
|
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
|
{
|
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
|
}
|
|
check_index(idx, a->len);
|
|
const encoded e = encode(std::forward<V>(value));
|
|
node* const slot = const_cast<node*>(nav::value(element_at<true>(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast)
|
|
assign(slot, e, a, true);
|
|
return View(&m_doc, slot);
|
|
}
|
|
|
|
/// append to an array (a null value becomes an array); returns a view of
|
|
/// the new element
|
|
template<typename V>
|
|
View push_back(const View& array, V&& value)
|
|
{
|
|
node* const a = own(array);
|
|
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
|
{
|
|
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
|
}
|
|
const encoded e = encode(std::forward<V>(value));
|
|
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
|
{
|
|
become_empty(a, value_t::array);
|
|
}
|
|
node* const slot = new_slot(e);
|
|
node* const h = block_of(m_doc, a, 1);
|
|
make_link(h[h->next], slot);
|
|
++h->next;
|
|
++h->len;
|
|
++a->len;
|
|
return View(&m_doc, slot);
|
|
}
|
|
|
|
/// insert into an array before position idx (idx <= size()); returns a
|
|
/// view of the new element
|
|
template<typename V>
|
|
View insert(const View& array, std::size_t idx, V&& value)
|
|
{
|
|
node* const a = own(array);
|
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
|
{
|
|
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
|
}
|
|
check_index(idx, a->len + 1);
|
|
const encoded e = encode(std::forward<V>(value));
|
|
node* const slot = new_slot(e);
|
|
node* const h = block_of(m_doc, a, 1);
|
|
std::memmove(h + 2 + idx, h + 1 + idx, (h->next - 1 - idx) * sizeof(node));
|
|
make_link(h[1 + idx], slot);
|
|
++h->next;
|
|
++h->len;
|
|
++a->len;
|
|
return View(&m_doc, slot);
|
|
}
|
|
|
|
/// remove all members with this key; returns their number
|
|
std::size_t erase(const View& object, string_view_t key)
|
|
{
|
|
node* const o = own(object);
|
|
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
|
{
|
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
|
}
|
|
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
|
{
|
|
if (key_equals(*k, key))
|
|
{
|
|
return erase_members(o, key, false);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// remove an array element
|
|
void erase(const View& array, std::size_t idx)
|
|
{
|
|
node* const a = own(array);
|
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
|
{
|
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
|
}
|
|
check_index(idx, a->len);
|
|
node* const h = block_of(m_doc, a, 0);
|
|
std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node));
|
|
--h->next;
|
|
--h->len;
|
|
--a->len;
|
|
}
|
|
|
|
private:
|
|
/// an encoded value: a scalar node, or the root of a new array/object
|
|
struct encoded
|
|
{
|
|
node scalar{};
|
|
node* region = nullptr;
|
|
};
|
|
|
|
/// the node of a view of this document
|
|
node* own(const View& v)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
|
{
|
|
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document");
|
|
}
|
|
edit_state_of(m_doc);
|
|
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
|
}
|
|
|
|
static void check_index(std::size_t idx, std::size_t limit)
|
|
{
|
|
if (idx >= limit)
|
|
{
|
|
throw_array_index_out_of_range(idx);
|
|
}
|
|
}
|
|
|
|
bool key_equals(const node& k, string_view_t key) const noexcept
|
|
{
|
|
return m_doc.key_equals(k, key.data(), key.size());
|
|
}
|
|
|
|
/// remove the members with this key (all, or all but the first) from an object
|
|
std::size_t erase_members(node* o, string_view_t key, bool keep_first)
|
|
{
|
|
node* const h = block_of(m_doc, o, 0);
|
|
node* w = h + 1;
|
|
std::size_t erased = 0;
|
|
bool kept = false;
|
|
for (const node* r = h + 1, *end = h + h->next; r != end; r += 2)
|
|
{
|
|
const bool match = key_equals(*r, key);
|
|
if (match && (kept || !keep_first))
|
|
{
|
|
++erased;
|
|
continue;
|
|
}
|
|
kept = kept || match;
|
|
if (w != r)
|
|
{
|
|
w[0] = r[0];
|
|
w[1] = r[1];
|
|
}
|
|
w += 2;
|
|
}
|
|
h->next = static_cast<std::uint32_t>(w - h);
|
|
h->len -= static_cast<std::uint32_t>(erased);
|
|
o->len -= static_cast<std::uint32_t>(erased);
|
|
return erased;
|
|
}
|
|
|
|
/// turn a null into an empty array/object in place
|
|
static void become_empty(node* n, value_t k) noexcept
|
|
{
|
|
const std::uint8_t linked = n->flags & node_flags::linked;
|
|
*n = node{};
|
|
n->kind = static_cast<std::uint8_t>(k);
|
|
n->flags = static_cast<std::uint8_t>(node_flags::is_new | linked);
|
|
n->next = 1;
|
|
}
|
|
|
|
/// Replace the value at slot; `parent` is the container whose elements
|
|
/// include slot (if known).
|
|
void assign(node* slot, const encoded& e, node* parent, bool parent_known)
|
|
{
|
|
// an entry of a moved sequence links to the slot: it can take any extent
|
|
const std::uint8_t linked = slot->flags & node_flags::linked;
|
|
if (e.region == nullptr)
|
|
{
|
|
if (is_container(*slot) && slot->next > 1 && slot != m_doc.nodes && linked == 0)
|
|
{
|
|
// The slot spans its old elements in the enclosing sequence, but
|
|
// a scalar is one node: the enclosing container first switches to
|
|
// links (then the extent of the slot no longer matters). Looking
|
|
// for the container is linear in the size of the document, so
|
|
// links (which are marked in the slot) avoid it.
|
|
node* const p = parent_known ? parent : find_parent(m_doc, slot);
|
|
if (p != nullptr && ((p->flags & node_flags::moved) == 0 || moved_capacity(m_doc, p) == 0))
|
|
{
|
|
block_of(m_doc, p, 0);
|
|
}
|
|
}
|
|
*slot = e.scalar;
|
|
slot->flags = static_cast<std::uint8_t>(slot->flags | linked);
|
|
return;
|
|
}
|
|
// an array/object: the slot keeps its extent (so that the enclosing
|
|
// sequence still steps over it), and the elements come from the new
|
|
// sequence
|
|
const node* const r = e.region;
|
|
const std::uint32_t extent = is_container(*slot) ? slot->next : 1;
|
|
const bool was_moved = (slot->flags & node_flags::moved) != 0;
|
|
// Everything that can throw happens before the slot changes: a slot
|
|
// that is a container without the moved flag would show its old
|
|
// elements. reserve_moved() makes the set_moved() below, which sets
|
|
// the flag, safe; the entry of `regions` exists already (encode()
|
|
// added it), so that the assignment at the end does not allocate.
|
|
if (!was_moved)
|
|
{
|
|
reserve_moved(m_doc);
|
|
}
|
|
slot->kind = r->kind;
|
|
slot->extra = 0;
|
|
slot->len = r->len;
|
|
slot->next = extent;
|
|
// (set_moved() adds the moved flag to a slot that does not have it yet)
|
|
slot->flags = static_cast<std::uint8_t>((was_moved ? node_flags::moved | node_flags::is_new : 0) | linked);
|
|
set_moved(m_doc, slot, e.region, 0);
|
|
edit_state_of(m_doc).regions[e.region] = slot;
|
|
}
|
|
|
|
/// a node for a new element (links point to it; it never moves)
|
|
node* new_slot(const encoded& e)
|
|
{
|
|
if (e.region != nullptr)
|
|
{
|
|
return e.region;
|
|
}
|
|
node* const s = alloc_nodes(m_doc, 1);
|
|
*s = e.scalar;
|
|
return s;
|
|
}
|
|
|
|
//////////////
|
|
// encoding //
|
|
//////////////
|
|
|
|
template<int N>
|
|
using encode_tag = std::integral_constant<int, N>;
|
|
|
|
template<typename T>
|
|
struct is_view : std::false_type {};
|
|
|
|
template<typename J, bool E>
|
|
struct is_view<basic_json_view<J, E>> : std::true_type {};
|
|
|
|
template<typename V>
|
|
encoded encode(V&& v)
|
|
{
|
|
using D = typename std::decay<V>::type;
|
|
return encode_impl(std::forward<V>(v), encode_tag<first_true<is_view<D>::value,
|
|
std::is_same<D, BasicJsonType>::value,
|
|
std::is_same<D, std::nullptr_t>::value,
|
|
std::is_same<D, bool>::value,
|
|
std::is_arithmetic<D>::value,
|
|
std::is_convertible<const D&, string_view_t>::value>::value> {});
|
|
}
|
|
|
|
/// a view of any document (copied; nothing is shared with it)
|
|
template<typename J, bool E>
|
|
encoded encode_impl(const basic_json_view<J, E>& v, encode_tag<0> /*view*/)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
|
{
|
|
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
|
}
|
|
encoded r;
|
|
if (!is_container(*v.m_node))
|
|
{
|
|
r.scalar = copy_scalar(*v.m_doc, *v.m_node);
|
|
return r;
|
|
}
|
|
r.region = alloc_nodes(m_doc, count_nodes<E>(*v.m_doc, v.m_node));
|
|
fill_nodes<E>(*v.m_doc, v.m_node, r.region);
|
|
edit_state_of(m_doc).regions.emplace(r.region, nullptr);
|
|
return r;
|
|
}
|
|
|
|
encoded encode_impl(const BasicJsonType& j, encode_tag<1> /*json*/)
|
|
{
|
|
encoded r;
|
|
if (!j.is_structured())
|
|
{
|
|
r.scalar = json_scalar(j);
|
|
return r;
|
|
}
|
|
r.region = alloc_nodes(m_doc, count_nodes(j));
|
|
fill_nodes(j, r.region);
|
|
edit_state_of(m_doc).regions.emplace(r.region, nullptr);
|
|
return r;
|
|
}
|
|
|
|
encoded encode_impl(std::nullptr_t /*unused*/, encode_tag<2> /*null*/)
|
|
{
|
|
encoded r;
|
|
r.scalar = plain_node(value_t::null);
|
|
return r;
|
|
}
|
|
|
|
encoded encode_impl(bool b, encode_tag<3> /*boolean*/)
|
|
{
|
|
encoded r;
|
|
r.scalar = plain_node(value_t::boolean);
|
|
r.scalar.flags = static_cast<std::uint8_t>(r.scalar.flags | (b ? node_flags::is_true : 0));
|
|
return r;
|
|
}
|
|
|
|
template<typename T>
|
|
encoded encode_impl(T x, encode_tag<4> /*number*/)
|
|
{
|
|
encoded r;
|
|
r.scalar = number_node(x, std::integral_constant<int, first_true<std::is_floating_point<T>::value, std::is_signed<T>::value>::value> {});
|
|
return r;
|
|
}
|
|
|
|
template<typename T>
|
|
encoded encode_impl(const T& s, encode_tag<5> /*string*/)
|
|
{
|
|
const string_view_t sv(s);
|
|
check_utf8(sv.data(), sv.size());
|
|
encoded r;
|
|
r.scalar = string_node(sv.data(), sv.size());
|
|
return r;
|
|
}
|
|
|
|
template<typename T>
|
|
encoded encode_impl(T&& x, encode_tag<6> /*other*/)
|
|
{
|
|
return encode_impl(BasicJsonType(std::forward<T>(x)), encode_tag<1> {});
|
|
}
|
|
|
|
static node plain_node(value_t k) noexcept
|
|
{
|
|
node n{};
|
|
n.kind = static_cast<std::uint8_t>(k);
|
|
n.flags = node_flags::is_new;
|
|
return n;
|
|
}
|
|
|
|
template<typename T>
|
|
node number_node(T x, std::integral_constant<int, 0> /*floating-point*/)
|
|
{
|
|
return float_node(static_cast<number_float_t>(x));
|
|
}
|
|
|
|
template<typename T>
|
|
node number_node(T x, std::integral_constant<int, 1> /*signed*/)
|
|
{
|
|
return integer_node(static_cast<std::uint64_t>(static_cast<std::int64_t>(x)), value_t::number_integer);
|
|
}
|
|
|
|
template<typename T>
|
|
node number_node(T x, std::integral_constant<int, 2> /*unsigned*/)
|
|
{
|
|
return integer_node(static_cast<std::uint64_t>(x), value_t::number_unsigned);
|
|
}
|
|
|
|
/// an integer with its canonical token in the edit arena
|
|
node integer_node(std::uint64_t bits, value_t k)
|
|
{
|
|
const bool negative = k == value_t::number_integer && static_cast<std::int64_t>(bits) < 0;
|
|
std::uint64_t magnitude = negative ? 0 - bits : bits;
|
|
std::array<char, 24> buf{};
|
|
char* p = buf.data() + buf.size(); // NOLINT(misc-const-correctness): digits are written through p
|
|
do
|
|
{
|
|
*--p = static_cast<char>('0' + (magnitude % 10));
|
|
magnitude /= 10;
|
|
}
|
|
while (magnitude != 0);
|
|
if (negative)
|
|
{
|
|
*--p = '-';
|
|
}
|
|
const auto len = static_cast<std::size_t>(buf.data() + buf.size() - p);
|
|
node n = plain_node(k);
|
|
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
|
n.off = append_text(m_doc, p, len);
|
|
// number_length() adds one for the sign of number_integer nodes
|
|
n.extra = static_cast<std::uint16_t>(k == value_t::number_integer ? len - 1 : len);
|
|
set_integer_bits(n, bits);
|
|
return n;
|
|
}
|
|
|
|
/// a float with its shortest round-trip token (as basic_json::dump()
|
|
/// writes it), or nan, inf, -inf, in the edit arena
|
|
node float_node(number_float_t x)
|
|
{
|
|
string_t text;
|
|
if (std::isnan(x))
|
|
{
|
|
text = "nan";
|
|
}
|
|
else if (std::isinf(x))
|
|
{
|
|
text = x > 0 ? "inf" : "-inf";
|
|
}
|
|
else
|
|
{
|
|
text = BasicJsonType(x).dump();
|
|
}
|
|
node n = plain_node(value_t::number_float);
|
|
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
|
n.extra = 0xFFFFu; // (the digit layout is not recorded)
|
|
n.off = append_text(m_doc, text.data(), text.size());
|
|
n.len = static_cast<std::uint32_t>(text.size());
|
|
return n;
|
|
}
|
|
|
|
/// a string (or key) in the edit arena
|
|
node string_node(const char* s, std::size_t len)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
|
{
|
|
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
|
}
|
|
node n = plain_node(value_t::string);
|
|
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
|
n.off = append_text(m_doc, s, len);
|
|
n.len = static_cast<std::uint32_t>(len);
|
|
return n;
|
|
}
|
|
|
|
/// a scalar of a view (of any document) as a node of this document
|
|
node copy_scalar(const document_data& from, const node& n)
|
|
{
|
|
if (&from == &m_doc)
|
|
{
|
|
return n; // the same storage
|
|
}
|
|
switch (static_cast<value_t>(n.kind))
|
|
{
|
|
case value_t::string:
|
|
// (an editable document only holds valid UTF-8, whatever the check of the other document was)
|
|
check_utf8(from.str(n), n.len);
|
|
return string_node(from.str(n), n.len);
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
return integer_node(integer_bits(n), static_cast<value_t>(n.kind));
|
|
case value_t::number_float:
|
|
{
|
|
node r = plain_node(value_t::number_float);
|
|
r.flags = static_cast<std::uint8_t>(r.flags | node_flags::edited);
|
|
r.off = append_text(m_doc, from.str(n), n.len);
|
|
r.len = n.len;
|
|
r.extra = n.extra;
|
|
return r;
|
|
}
|
|
case value_t::boolean:
|
|
{
|
|
node r = plain_node(value_t::boolean);
|
|
r.flags = static_cast<std::uint8_t>(r.flags | (n.flags & node_flags::is_true));
|
|
return r;
|
|
}
|
|
case value_t::null:
|
|
case value_t::object:
|
|
case value_t::array:
|
|
case value_t::binary:
|
|
case value_t::discarded:
|
|
default:
|
|
return plain_node(value_t::null);
|
|
}
|
|
}
|
|
|
|
node json_scalar(const BasicJsonType& j)
|
|
{
|
|
switch (j.type())
|
|
{
|
|
case value_t::null:
|
|
return plain_node(value_t::null);
|
|
case value_t::boolean:
|
|
{
|
|
node r = plain_node(value_t::boolean);
|
|
r.flags = static_cast<std::uint8_t>(r.flags | (j.template get<bool>() ? node_flags::is_true : 0));
|
|
return r;
|
|
}
|
|
case value_t::number_integer:
|
|
return integer_node(static_cast<std::uint64_t>(static_cast<std::int64_t>(j.template get<number_integer_t>())), value_t::number_integer);
|
|
case value_t::number_unsigned:
|
|
return integer_node(static_cast<std::uint64_t>(j.template get<number_unsigned_t>()), value_t::number_unsigned);
|
|
case value_t::number_float:
|
|
return float_node(j.template get<number_float_t>());
|
|
case value_t::string:
|
|
{
|
|
const auto& s = j.template get_ref<const string_t&>();
|
|
check_utf8(s.data(), s.size());
|
|
return string_node(s.data(), s.size());
|
|
}
|
|
case value_t::binary:
|
|
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", "");
|
|
case value_t::discarded:
|
|
case value_t::object:
|
|
case value_t::array:
|
|
default:
|
|
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
|
}
|
|
}
|
|
|
|
// The subtrees are walked with an explicit stack (as materialize() does):
|
|
// the nesting depth is limited by memory only, not by the call stack.
|
|
|
|
/// number of nodes of a subtree (containers, keys, scalars)
|
|
template<bool E>
|
|
static std::size_t count_nodes(const document_data& d, const node* n)
|
|
{
|
|
using walk = navigation<E>;
|
|
struct frame
|
|
{
|
|
const node* pos; ///< next element, or key of the next member
|
|
const node* end;
|
|
bool object;
|
|
};
|
|
std::vector<frame> open;
|
|
std::size_t r = 0;
|
|
for (;;)
|
|
{
|
|
++r;
|
|
if (is_container(*n))
|
|
{
|
|
open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast<std::uint8_t>(value_t::object)});
|
|
}
|
|
// the next value: close finished containers, then step over the key
|
|
for (;;)
|
|
{
|
|
if (open.empty())
|
|
{
|
|
return r;
|
|
}
|
|
frame& f = open.back();
|
|
if (f.pos == f.end)
|
|
{
|
|
open.pop_back();
|
|
continue;
|
|
}
|
|
const node* v = f.pos;
|
|
if (f.object)
|
|
{
|
|
++r; // the key
|
|
++v;
|
|
}
|
|
f.pos = document_data::after(v);
|
|
n = walk::value(v);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// copy a subtree (of any document) as a contiguous sequence of
|
|
/// count_nodes() nodes
|
|
template<bool E>
|
|
void fill_nodes(const document_data& d, const node* n, node* out)
|
|
{
|
|
using walk = navigation<E>;
|
|
struct frame
|
|
{
|
|
const node* pos; ///< next element, or key of the next member
|
|
const node* end;
|
|
bool object;
|
|
node* self; ///< the container in the copy
|
|
};
|
|
std::vector<frame> open;
|
|
for (;;)
|
|
{
|
|
if (is_container(*n))
|
|
{
|
|
node* const self = out++;
|
|
*self = plain_node(static_cast<value_t>(n->kind));
|
|
self->len = n->len;
|
|
open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast<std::uint8_t>(value_t::object), self});
|
|
}
|
|
else
|
|
{
|
|
*out++ = copy_scalar(d, *n);
|
|
}
|
|
// the next value: close finished containers, then copy the key
|
|
for (;;)
|
|
{
|
|
if (open.empty())
|
|
{
|
|
return;
|
|
}
|
|
frame& f = open.back();
|
|
if (f.pos == f.end)
|
|
{
|
|
f.self->next = static_cast<std::uint32_t>(out - f.self);
|
|
open.pop_back();
|
|
continue;
|
|
}
|
|
const node* v = f.pos;
|
|
if (f.object)
|
|
{
|
|
*out++ = copy_scalar(d, *v);
|
|
++v;
|
|
}
|
|
f.pos = document_data::after(v);
|
|
n = walk::value(v);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::size_t count_nodes(const BasicJsonType& j)
|
|
{
|
|
using iterator = typename BasicJsonType::const_iterator;
|
|
struct frame
|
|
{
|
|
iterator pos;
|
|
iterator end;
|
|
bool object;
|
|
};
|
|
std::vector<frame> open;
|
|
const BasicJsonType* n = &j;
|
|
std::size_t r = 0;
|
|
for (;;)
|
|
{
|
|
++r;
|
|
if (n->is_structured())
|
|
{
|
|
open.push_back(frame{n->cbegin(), n->cend(), n->is_object()});
|
|
}
|
|
for (;;)
|
|
{
|
|
if (open.empty())
|
|
{
|
|
return r;
|
|
}
|
|
frame& f = open.back();
|
|
if (f.pos == f.end)
|
|
{
|
|
open.pop_back();
|
|
continue;
|
|
}
|
|
r += f.object ? 1 : 0; // the key
|
|
n = &*f.pos;
|
|
++f.pos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void fill_nodes(const BasicJsonType& j, node* out)
|
|
{
|
|
using iterator = typename BasicJsonType::const_iterator;
|
|
struct frame
|
|
{
|
|
iterator pos;
|
|
iterator end;
|
|
bool object;
|
|
node* self; ///< the container in the copy
|
|
};
|
|
std::vector<frame> open;
|
|
const BasicJsonType* n = &j;
|
|
for (;;)
|
|
{
|
|
if (n->is_structured())
|
|
{
|
|
node* const self = out++;
|
|
*self = plain_node(n->type());
|
|
self->len = static_cast<std::uint32_t>(n->size());
|
|
open.push_back(frame{n->cbegin(), n->cend(), n->is_object(), self});
|
|
}
|
|
else
|
|
{
|
|
*out++ = json_scalar(*n);
|
|
}
|
|
for (;;)
|
|
{
|
|
if (open.empty())
|
|
{
|
|
return;
|
|
}
|
|
frame& f = open.back();
|
|
if (f.pos == f.end)
|
|
{
|
|
f.self->next = static_cast<std::uint32_t>(out - f.self);
|
|
open.pop_back();
|
|
continue;
|
|
}
|
|
if (f.object)
|
|
{
|
|
const auto& key = f.pos.key();
|
|
check_utf8(key.data(), key.size());
|
|
*out++ = string_node(key.data(), key.size());
|
|
}
|
|
n = &*f.pos;
|
|
++f.pos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
document_data& m_doc;
|
|
};
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|