mirror of
https://github.com/nlohmann/json.git
synced 2026-10-30 17:35:42 +00:00
* Compare json_view values without a vector per container and a basic_json per number
operator== of views (view/view, view/json, and editable documents) built two
vectors of members for every object it entered, sorted them with stable_sort
for every object, and converted each number to a basic_json value. It now:
- keeps the members of all open objects in one vector (reserved with
geometric growth, so that nested objects do not reallocate), and orders
them by insertion sort for up to 16 members, or not at all when the keys
ascend already;
- compares numbers of one type directly, and builds a basic_json only for
numbers of different types;
- compares short texts (keys, numbers, most strings) with two overlapping
loads (bytes_equal(), in node.hpp);
- first walks both documents in lockstep, in the order of their text, which
finds most pairs of equal values without the work of the comparison. The
walk only enters an object if the comparison would visit its members in the
same order (the object type keeps the order, the keys ascend, or all values
are scalars), so a difference is never found later than before, and it
records the containers it found different, so that the comparison does not
walk them again;
- rejects values of different types and arrays of different sizes before any
of this, and, for an object type that keeps the order, objects whose first
(up to four) members have different keys, which ends a comparison that
differs near the start without preparing the members.
Results are unchanged: numbers compare by value across types, duplicate keys
resolve as parse() resolves them, and objects compare in document order for
ordered_json and by key otherwise.
Instructions of one comparison of a document with an equal copy, macOS arm64
(develop -> now), json_document / ordered_json_document / editable:
twitter 16.1M -> 7.0M 17.7M -> 1.5M 16.6M -> 7.3M
citm 45.0M -> 3.4M 47.0M -> 3.2M 45.7M -> 4.3M
canada 111.4M -> 13.7M 111.4M -> 13.7M 113.3M -> 15.0M
jeopardy 26.2M -> 3.7M 27.3M -> 3.7M 26.6M -> 12.1M
and of a view with a basic_json value:
twitter 11.4M -> 5.9M 11.2M -> 7.3M 11.6M -> 5.9M
citm 33.9M -> 9.7M 32.6M -> 9.1M 34.6M -> 9.8M
canada 92.9M -> 32.1M 92.9M -> 32.1M 93.9M -> 32.8M
and Linux instruction counts (GCC 13 / Clang 18) of the same comparisons:
document with an equal copy json_document ordered_json_document editable
twitter -49 % / -52 % -89 % / -89 % -48 % / -50 %
citm -90 % / -89 % -91 % / -90 % -88 % / -87 %
jeopardy (4,000 questions) -85 % / -85 % -86 % / -86 % -46 % / -49 %
view with a basic_json value
twitter -41 % / -45 % -28 % / -35 % -45 % / -44 %
citm -63 % / -65 % -63 % / -66 % -65 % / -64 %
jeopardy -39 % / -43 % -14 % / -24 % -44 % / -43 %
A sweep that places one difference at about 2,100 positions of eight
documents (a token at an even spacing, the first and last tokens, the first,
middle and last member of the root removed, one appended, the document
wrapped) in five kinds of comparison (view/view of read-only and of editable
documents, view/json, json_document, editable/view) finds the same results as
before and no placement slower on the Mac (Clang); on Linux no placement is
slower than 3 % (Clang and GCC).
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Check the float tokens of a json_document image without a sort and a scan of every character
load(image_check::full) checked every float token of an image by sorting
the token ranges, scanning the token character by character, and comparing
the digit layout with the node's. Now:
- the ranges are sorted only if they are not sorted already (save() writes
them in the order of the nodes, so they are);
- a token of the shape [-]digits.digits whose digit counts are the ones the
node records (nearly all of them) is accepted by checking its digits eight
at a time, without the general scan;
- the general scan skips runs of digits eight at a time.
Damaged tokens are still rejected as before: the check accepts exactly the
tokens that the general scan accepts.
Instructions of load(image_check::full) (develop -> now):
macOS arm64 canada 44.6M -> 30.9M (-31 %), jeopardy 11.3M -> 10.7M,
citm 10.4M -> 10.0M, twitter 5.06M -> 4.94M
Linux GCC 13 canada -50 %, jeopardy -43 %, twitter -31 %, citm -28 %
Linux Clang 18 canada -42 %, jeopardy -48 %, twitter -35 %, citm -32 %
image_check::bounds is unchanged (within 0.4 %).
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Rename names that Flawfinder takes for the file function open()
The comparison cursors' open() is now enter(), and a test's open and
close strings are head and tail.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix the compiler and lint failures of the json_view comparison
- all_digits(): the lambda declares its constant itself; MSVC does not
let a lambda without captures read a constexpr local of the function
- equal(): the frame's "in document order" index is a namespace-scope
constant (document_order); the local constexpr npos in the default
member initializers of the local struct crashed GCC 9 and MinGW GCC
and was not found by MSVC
- keys_differ_early() keeps the first keys in std::arrays (cpplint:
variable-length arrays; clang-tidy: C arrays), and the basic_json
side's first_keys() is not noexcept (key() may throw)
- the numbers of one type are compared in an if chain instead of a
nested conditional operator
- the dump test avoids C arrays and temporary string concatenations
View comparison instructions are unchanged (Apple clang, twitter, citm,
canada: -0.1 % to +0.1 %).
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Keep the byte comparison helpers in compare.hpp
Address review comments: bytes_equal() and bytes_less() move from node.hpp
to compare.hpp, their only user; key_less() no longer splits the comment of
resolve_indices() from its function; and the loop counters of bytes_equal()
and all_digits() are scoped to their loops.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Dispatch on the compile-time conditions of the json_view comparison
MSVC (/W4 /WX) reported C4127 (conditional expression is constant) for
the template parameter EA and the object type's ordering in an if
condition; both are now tag-dispatched.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Remove a nested duplicate guard from the json_view image test
clang-tidy reported the inner #if !defined(JSON_NOEXCEPTION) as
redundant (the test case is in the same #if) and a find() != npos, which
gets the NOLINT the other tests use.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Mark Infer's null dereference in materialize() as a false positive
Infer (ci_infer, also red on develop since #5882) reports a null
dereference in json_sax_dom_parser::null() called from materialize(). The
member pointer the parser stores a value of an object in is set by the
key() before it, so it is not null there.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Move the json_view comparison tests into their own file
With the tests this branch adds, the object file of unit-json_view_dump.cpp
got too large for the MinGW x64 assembler ("file too big"). The test case
"json_view comparison" is now unit-json_view_compare.cpp, unchanged and
also built with C++17.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Build the DOM parser with Visual Studio 2015 again
Since #5882, the three VS 2015 jobs of AppVeyor fail on develop:
- the detection of keys that can be indexed instantiated std::hash for
every string type, which VS 2015 rejects with a static_assert for a type
it does not hash (alt_string): std::hash is now looked at only for
objects in a vector
- the frame stack copied its frames with std::copy to a raw pointer, which
VS 2015 reports as unsafe (C4996, an error with /WX): a loop now
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* json_view: no per-object allocations in ordered_json comparisons
For an object type that keeps the order of its members (ordered_json),
resolve_indices() allocated a vector per compared object, and
resolve_members() two. They now use one working vector owned by equal(),
whose capacity is reused, and resolve_indices() needs none when the keys
are unique. resolve_members() sorts small objects by insertion, as the
other paths do.
Instructions per comparison (callgrind, GCC 13 and clang 18, x86-64):
twitter, ordered_json view == ordered_json: 0.939 / 0.941; 20000 small
objects with a duplicate key: 0.741-0.768; all other rows (json,
citm_catalog, canada, view == view) within 0.988-1.003.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1757 lines
69 KiB
C++
1757 lines
69 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
|
|
|
|
/****************************************************************************\
|
|
* Zero-copy view of a parsed JSON text. *
|
|
* *
|
|
* json_document::parse() builds a flat index of the values of a JSON text *
|
|
* (16 bytes per value) instead of a tree of basic_json values. Strings and *
|
|
* numbers stay in the source text; only strings with escapes are decoded, *
|
|
* into one buffer. json_view is a handle to one value of the document, with *
|
|
* the read-only part of the basic_json interface; materialize() turns a *
|
|
* subtree into the basic_json value that parse() would produce. An editable *
|
|
* document (json_editable_document) also has set(), push_back(), insert(), *
|
|
* and erase(): edits never write to the source text, and views stay valid. *
|
|
* *
|
|
* The source text must outlive a document that borrows it (lvalue byte *
|
|
* containers, C strings); rvalue strings, streams, and other inputs are *
|
|
* owned by the document. *
|
|
\****************************************************************************/
|
|
|
|
#ifndef INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
|
#define INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
|
|
|
#include <algorithm> // all_of, min
|
|
#include <array> // array
|
|
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
|
#include <cstdint> // uint8_t, uint32_t
|
|
#include <cstdio> // FILE
|
|
#include <cstring> // memcpy, strlen
|
|
#include <istream> // istream // IWYU pragma: keep
|
|
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
|
#include <map> // map
|
|
#include <memory> // unique_ptr
|
|
#ifndef JSON_NO_IO
|
|
#include <ostream> // ostream // IWYU pragma: keep
|
|
#endif
|
|
#include <string> // string
|
|
#include <tuple> // tuple_element, tuple_size // IWYU pragma: keep
|
|
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
|
#include <unordered_map> // unordered_map
|
|
#include <utility> // forward, move
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/json.hpp> // IWYU pragma: export
|
|
|
|
// json.hpp provides the library's types and macros used below (it includes
|
|
// the headers that define them); json_view.hpp must not include them again,
|
|
// because the amalgamated json_view.hpp only includes json.hpp
|
|
// IWYU pragma: no_include <version>
|
|
// IWYU pragma: no_include "nlohmann/detail/abi_config.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/abi_macros.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/conversions/from_json.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/input/input_adapters.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/json_pointer.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/meta/cpp_future.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/meta/type_traits.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/string_concat.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/value_t.hpp"
|
|
// IWYU pragma: no_include "nlohmann/json.hpp"
|
|
// IWYU pragma: no_include "nlohmann/json_fwd.hpp"
|
|
|
|
// the view builds on internals of the library: both must be the same version
|
|
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
|
|
#error "json_view.hpp requires json.hpp of the same version (3.12.0)"
|
|
#endif
|
|
|
|
#include <nlohmann/detail/view/builder.hpp>
|
|
#include <nlohmann/detail/view/compare.hpp>
|
|
#include <nlohmann/detail/view/document_data.hpp>
|
|
#include <nlohmann/detail/view/edit.hpp>
|
|
#include <nlohmann/detail/view/errors.hpp>
|
|
#include <nlohmann/detail/view/image.hpp>
|
|
#include <nlohmann/detail/view/input.hpp>
|
|
#include <nlohmann/detail/view/iterator.hpp>
|
|
#include <nlohmann/detail/view/lookup.hpp>
|
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
#include <nlohmann/detail/view/materialize.hpp>
|
|
#include <nlohmann/detail/view/node.hpp>
|
|
#include <nlohmann/detail/view/object_index.hpp>
|
|
#include <nlohmann/detail/view/pointer.hpp>
|
|
#include <nlohmann/detail/view/serializer.hpp>
|
|
#include <nlohmann/detail/view/string_ref.hpp>
|
|
#include <nlohmann/detail/view/value.hpp>
|
|
#include <nlohmann/detail/view/view_mode.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
|
|
/*!
|
|
@brief read-only handle to one value of a basic_json_document
|
|
|
|
Trivially copyable (two pointers). Valid as long as the document is alive and
|
|
has not been re-parsed, and as long as a borrowed source text is alive.
|
|
*/
|
|
template<typename BasicJsonType, view_mode Mode = view_mode::read_only>
|
|
class basic_json_view
|
|
{
|
|
/// whether this is a view of an editable document
|
|
static constexpr bool is_editable = Mode == view_mode::editable;
|
|
|
|
using node = detail::view::node;
|
|
using document_data = detail::view::document_data;
|
|
/// how the index is walked (with edits only for editable documents)
|
|
using navigation = detail::view::navigation<is_editable>;
|
|
|
|
public:
|
|
/// the basic_json type of the document (it also marks views for is_json_view)
|
|
using basic_json_type = BasicJsonType;
|
|
using value_t = detail::value_t;
|
|
using string_t = typename BasicJsonType::string_t;
|
|
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 json_pointer = typename BasicJsonType::json_pointer;
|
|
using size_type = std::size_t;
|
|
/// std::string_view from C++17 on
|
|
using string_view_t = detail::view::string_ref;
|
|
/// forward iterator over elements (arrays) or member values (objects)
|
|
using iterator = detail::view::view_iterator<basic_json_view>;
|
|
using const_iterator = iterator;
|
|
/// a (key, value) item of items()
|
|
using item = detail::view::view_item<basic_json_view>;
|
|
|
|
/// an invalid view (type() == value_t::discarded)
|
|
basic_json_view() noexcept = default;
|
|
|
|
/// a view of a read-only document as a view of an editable one, so that a
|
|
/// function taking an editable view accepts both (a read-only document has
|
|
/// no edits; the view reads it as an editable document before its first
|
|
/// edit, at the cost of one check per access)
|
|
template<view_mode M, detail::enable_if_t<detail::view::is_widening_mode<M, Mode>::value, int> = 0>
|
|
basic_json_view(const basic_json_view<BasicJsonType, M>& other) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
|
: m_doc(other.m_doc), m_node(other.m_node)
|
|
{}
|
|
|
|
//////////
|
|
// type //
|
|
//////////
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE value_t type() const noexcept
|
|
{
|
|
return m_node != nullptr ? static_cast<value_t>(m_node->kind) : value_t::discarded;
|
|
}
|
|
|
|
bool is_null() const noexcept
|
|
{
|
|
return type() == value_t::null;
|
|
}
|
|
|
|
bool is_boolean() const noexcept
|
|
{
|
|
return type() == value_t::boolean;
|
|
}
|
|
|
|
bool is_number() const noexcept
|
|
{
|
|
return is_number_integer() || is_number_float();
|
|
}
|
|
|
|
bool is_number_integer() const noexcept
|
|
{
|
|
return type() == value_t::number_integer || type() == value_t::number_unsigned;
|
|
}
|
|
|
|
bool is_number_unsigned() const noexcept
|
|
{
|
|
return type() == value_t::number_unsigned;
|
|
}
|
|
|
|
bool is_number_float() const noexcept
|
|
{
|
|
return type() == value_t::number_float;
|
|
}
|
|
|
|
bool is_string() const noexcept
|
|
{
|
|
return type() == value_t::string;
|
|
}
|
|
|
|
bool is_array() const noexcept
|
|
{
|
|
return type() == value_t::array;
|
|
}
|
|
|
|
bool is_object() const noexcept
|
|
{
|
|
return type() == value_t::object;
|
|
}
|
|
|
|
/// always false: JSON text has no binary values
|
|
bool is_binary() const noexcept
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool is_primitive() const noexcept
|
|
{
|
|
return is_null() || is_string() || is_boolean() || is_number();
|
|
}
|
|
|
|
bool is_structured() const noexcept
|
|
{
|
|
return is_array() || is_object();
|
|
}
|
|
|
|
/// the root of a failed parse with allow_exceptions == false, or a
|
|
/// default-constructed view
|
|
bool is_discarded() const noexcept
|
|
{
|
|
return type() == value_t::discarded;
|
|
}
|
|
|
|
/// the name of the type, as basic_json::type_name()
|
|
const char* type_name() const noexcept
|
|
{
|
|
return detail::value_type_name(type());
|
|
}
|
|
|
|
//////////////
|
|
// capacity //
|
|
//////////////
|
|
|
|
/// the number of elements (arrays, objects), 0 for null and discarded,
|
|
/// 1 otherwise, as basic_json::size()
|
|
size_type size() const noexcept
|
|
{
|
|
switch (type())
|
|
{
|
|
case value_t::null:
|
|
case value_t::discarded:
|
|
return 0;
|
|
case value_t::array:
|
|
case value_t::object:
|
|
return m_node->len;
|
|
case value_t::string:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::binary:
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/// as basic_json::empty()
|
|
bool empty() const noexcept
|
|
{
|
|
switch (type())
|
|
{
|
|
case value_t::null:
|
|
case value_t::discarded:
|
|
return true;
|
|
case value_t::array:
|
|
case value_t::object:
|
|
return m_node->len == 0;
|
|
case value_t::string:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::binary:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
////////////////////
|
|
// element access //
|
|
////////////////////
|
|
|
|
/// the value of the member with this key (the first one, should the key
|
|
/// occur more than once); a discarded view if there is none, or if this
|
|
/// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
|
|
/// if this is any other value but an object.
|
|
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
|
{
|
|
if (is_discarded())
|
|
{
|
|
return {};
|
|
}
|
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
|
}
|
|
return lookup(key);
|
|
}
|
|
|
|
basic_json_view operator[](const char* key) const
|
|
{
|
|
return operator[](string_view_t(key));
|
|
}
|
|
|
|
basic_json_view operator[](const string_t& key) const
|
|
{
|
|
return operator[](string_view_t(key.data(), key.size()));
|
|
}
|
|
|
|
/// the element at this index; a discarded view if the index is out of
|
|
/// range, or if this is a discarded view. Throws type_error.305 if this is
|
|
/// any other value but an array.
|
|
basic_json_view operator[](size_type idx) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
|
{
|
|
if (is_discarded())
|
|
{
|
|
return {};
|
|
}
|
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
|
}
|
|
return idx < m_node->len ? element(idx) : basic_json_view();
|
|
}
|
|
|
|
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
|
/// single overload for size_type alone would be ambiguous for all of them
|
|
/// and for const char*); negative values are out of range
|
|
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
|
|
basic_json_view operator[](IntegerType idx) const
|
|
{
|
|
return operator[](detail::view::to_index<size_type>(idx));
|
|
}
|
|
|
|
/// the value a JSON pointer refers to; a discarded view if a key is
|
|
/// missing or an index is out of range, or if this is a discarded view.
|
|
/// Other errors throw what const basic_json::operator[] throws.
|
|
basic_json_view operator[](const json_pointer& ptr) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(is_discarded()))
|
|
{
|
|
return {};
|
|
}
|
|
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
|
|
}
|
|
|
|
/// the value of the member with this key (the first one, should the key
|
|
/// occur more than once). Throws type_error.304 if this is not an object,
|
|
/// and out_of_range.403 if there is no such member.
|
|
basic_json_view at(string_view_t key) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
|
}
|
|
const basic_json_view r = lookup(key);
|
|
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
|
{
|
|
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
basic_json_view at(const char* key) const
|
|
{
|
|
return at(string_view_t(key));
|
|
}
|
|
|
|
basic_json_view at(const string_t& key) const
|
|
{
|
|
return at(string_view_t(key.data(), key.size()));
|
|
}
|
|
|
|
/// the element at this index. Throws type_error.304 if this is not an
|
|
/// array, and out_of_range.401 if the index is out of range.
|
|
basic_json_view at(size_type idx) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
|
}
|
|
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
|
{
|
|
detail::view::throw_array_index_out_of_range(idx);
|
|
}
|
|
return element(idx);
|
|
}
|
|
|
|
/// any other integer type, see operator[]; negative values are out of
|
|
/// range
|
|
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
|
|
basic_json_view at(IntegerType idx) const
|
|
{
|
|
return at(detail::view::to_index<size_type>(idx));
|
|
}
|
|
|
|
/// the value a JSON pointer refers to; throws what basic_json::at()
|
|
/// throws if it cannot be resolved
|
|
basic_json_view at(const json_pointer& ptr) const
|
|
{
|
|
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::checked);
|
|
}
|
|
|
|
/// the member with this key converted to T, or the default value if there
|
|
/// is no such member (the first one, should the key occur more than
|
|
/// once). Throws type_error.306 if this is not an object.
|
|
template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
|
|
T value(string_view_t key, const T& default_value) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
|
}
|
|
const basic_json_view r = lookup(key);
|
|
return r.is_discarded() ? default_value : r.template get<T>();
|
|
}
|
|
|
|
string_t value(string_view_t key, const char* default_value) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
|
}
|
|
// (the default is made only if there is no such member)
|
|
const basic_json_view r = lookup(key);
|
|
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
|
}
|
|
|
|
/// the value a JSON pointer refers to converted to T, or the default
|
|
/// value if the pointer cannot be resolved. Throws type_error.306 if this
|
|
/// is neither an object nor an array.
|
|
template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
|
|
T value(const json_pointer& ptr, const T& default_value) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
|
}
|
|
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
|
return r.is_discarded() ? default_value : r.template get<T>();
|
|
}
|
|
|
|
string_t value(const json_pointer& ptr, const char* default_value) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
|
}
|
|
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
|
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
|
}
|
|
|
|
/// the first element or member value; a primitive value itself. Throws
|
|
/// invalid_iterator.214 for null, discarded views, and empty containers.
|
|
basic_json_view front() const
|
|
{
|
|
const iterator it = begin();
|
|
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
|
{
|
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
|
|
}
|
|
return *it;
|
|
}
|
|
|
|
/// the last element or member value (linear in the size); a primitive
|
|
/// value itself. Throws invalid_iterator.214 for null, discarded views,
|
|
/// and empty containers.
|
|
basic_json_view back() const
|
|
{
|
|
if (is_structured() && m_node->len != 0)
|
|
{
|
|
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
|
|
: basic_json_view(m_doc, navigation::value(detail::view::last_child<is_editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
|
}
|
|
return front();
|
|
}
|
|
|
|
////////////
|
|
// lookup //
|
|
////////////
|
|
|
|
/// an iterator to the member with this key (the first one, should the
|
|
/// key occur more than once), or end(); end() also for non-objects
|
|
iterator find(string_view_t key) const
|
|
{
|
|
if (!is_object())
|
|
{
|
|
return end();
|
|
}
|
|
const node* const k = find_key(key);
|
|
return k != nullptr ? iterator(m_doc, k, true) : end();
|
|
}
|
|
|
|
iterator find(const char* key) const
|
|
{
|
|
return find(string_view_t(key));
|
|
}
|
|
|
|
iterator find(const string_t& key) const
|
|
{
|
|
return find(string_view_t(key.data(), key.size()));
|
|
}
|
|
|
|
/// whether this is an object with a member with this key
|
|
bool contains(string_view_t key) const
|
|
{
|
|
return is_object() && find_key(key) != nullptr;
|
|
}
|
|
|
|
bool contains(const char* key) const
|
|
{
|
|
return contains(string_view_t(key));
|
|
}
|
|
|
|
bool contains(const string_t& key) const
|
|
{
|
|
return contains(string_view_t(key.data(), key.size()));
|
|
}
|
|
|
|
/// whether a JSON pointer can be resolved (never throws, as
|
|
/// basic_json::contains())
|
|
bool contains(const json_pointer& ptr) const
|
|
{
|
|
return !detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains).is_discarded();
|
|
}
|
|
|
|
/// the number of members with this key (more than 1 if the key occurs
|
|
/// more than once); 0 if this is not an object
|
|
size_type count(string_view_t key) const
|
|
{
|
|
return is_object() ? detail::view::count_members<is_editable>(*m_doc, m_node, key.data(), key.size()) : 0;
|
|
}
|
|
|
|
size_type count(const char* key) const
|
|
{
|
|
return count(string_view_t(key));
|
|
}
|
|
|
|
size_type count(const string_t& key) const
|
|
{
|
|
return count(string_view_t(key.data(), key.size()));
|
|
}
|
|
|
|
///////////////
|
|
// iteration //
|
|
///////////////
|
|
|
|
/// the first element or member value, in document order; a primitive
|
|
/// value is a range of one element (itself), null an empty range
|
|
NLOHMANN_VIEW_ALWAYS_INLINE iterator begin() const noexcept
|
|
{
|
|
if (NLOHMANN_VIEW_LIKELY(is_structured()))
|
|
{
|
|
return iterator(m_doc, navigation::first(*m_doc, m_node), is_object());
|
|
}
|
|
return iterator(m_doc, m_node, false);
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE iterator end() const noexcept
|
|
{
|
|
if (NLOHMANN_VIEW_LIKELY(is_structured()))
|
|
{
|
|
return iterator(m_doc, navigation::end(*m_doc, m_node), is_object());
|
|
}
|
|
return iterator(m_doc, (is_null() || is_discarded()) ? m_node : m_node + 1, false);
|
|
}
|
|
|
|
iterator cbegin() const noexcept
|
|
{
|
|
return begin();
|
|
}
|
|
|
|
iterator cend() const noexcept
|
|
{
|
|
return end();
|
|
}
|
|
|
|
/// (key, value) items; the key of an array element is its index
|
|
detail::view::view_items<basic_json_view> items() const noexcept
|
|
{
|
|
return detail::view::view_items<basic_json_view>(*this);
|
|
}
|
|
|
|
////////////////
|
|
// conversion //
|
|
////////////////
|
|
|
|
/// the value converted to T, as BasicJsonType::get<T>(): arithmetic types,
|
|
/// strings (string_view_t without a copy), std::nullptr_t, std::vector,
|
|
/// maps with string keys, and views are converted directly; types whose
|
|
/// from_json() accepts views (those of the NLOHMANN_DEFINE_TYPE_* and
|
|
/// NLOHMANN_JSON_SERIALIZE_ENUM macros, say) read the view; other types
|
|
/// go through materialize().get<T>()
|
|
template<typename T>
|
|
NLOHMANN_VIEW_ALWAYS_INLINE T get() const
|
|
{
|
|
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
|
|
}
|
|
|
|
/// as BasicJsonType::get_to(): the types get<T>() converts directly are
|
|
/// assigned, all others are passed to from_json with v as it is (with
|
|
/// the view if from_json() accepts views, else with materialize())
|
|
template<typename T>
|
|
T& get_to(T& v) const
|
|
{
|
|
get_to_impl(v, get_to_kind<T> {});
|
|
return v;
|
|
}
|
|
|
|
/// the string, without a copy; valid as long as the view is. Throws
|
|
/// type_error.302 for other types.
|
|
string_view_t get_string() const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
|
}
|
|
return {m_doc->str(*m_node), m_node->len};
|
|
}
|
|
|
|
/// the text of a number as it appears in the source (e.g. "1.50", "1E2",
|
|
/// or an integer with more digits than any number type holds). Throws
|
|
/// type_error.302 for other types.
|
|
string_view_t number_token() const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
|
}
|
|
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
|
}
|
|
|
|
///////////////////
|
|
// serialization //
|
|
///////////////////
|
|
|
|
/// how dump() writes numbers
|
|
enum class number_format
|
|
{
|
|
/// as basic_json::dump(): integers canonically, floats with the
|
|
/// library's shortest round-trip digits ("1.5", "100.0", "1e+100")
|
|
shortest,
|
|
/// the number text of the source as it is ("1.50", "1E2", "-0", all
|
|
/// digits of a long integer)
|
|
source,
|
|
};
|
|
|
|
/// the text of this value; with number_format::shortest, the output of
|
|
/// ordered_json::parse(text).dump() with the same arguments (members in
|
|
/// document order, all of them should a key occur more than once)
|
|
string_t dump(const int indent = -1, const char indent_char = ' ', const bool ensure_ascii = false,
|
|
const number_format numbers = number_format::shortest) const
|
|
{
|
|
string_t out;
|
|
if (m_node == nullptr)
|
|
{
|
|
out = "<discarded>"; // as basic_json::dump() of a discarded value
|
|
return out;
|
|
}
|
|
detail::view::dump_style style;
|
|
style.pretty = indent >= 0;
|
|
style.indent = indent >= 0 ? static_cast<std::size_t>(indent) : 0;
|
|
style.indent_char = indent_char;
|
|
style.ensure_ascii = ensure_ascii;
|
|
style.source_numbers = numbers == number_format::source;
|
|
// the compact text is about as long as the source text of the value;
|
|
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
|
// the buffer just before the end
|
|
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
|
if (unedited())
|
|
{
|
|
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
|
|
}
|
|
else
|
|
{
|
|
detail::view::view_serializer<BasicJsonType, is_editable>(*m_doc, out, estimate, style).dump(m_node);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
#ifndef JSON_NO_IO
|
|
/// as operator<< of basic_json: a stream width > 0 is the indentation,
|
|
/// the fill character the indentation character
|
|
friend std::ostream& operator<<(std::ostream& o, const basic_json_view& v)
|
|
{
|
|
const bool pretty = o.width() > 0;
|
|
const auto indentation = pretty ? o.width() : 0;
|
|
o.width(0);
|
|
const string_t s = v.dump(pretty ? static_cast<int>(indentation) : -1, o.fill());
|
|
return o.write(s.data(), static_cast<std::streamsize>(s.size()));
|
|
}
|
|
#endif
|
|
|
|
////////////////
|
|
// comparison //
|
|
////////////////
|
|
|
|
/// whether the values parse() would produce for two views are equal, as
|
|
/// by BasicJsonType's operator== (numbers by value, objects by their
|
|
/// members with duplicate keys resolved as parse() resolves them)
|
|
friend bool operator==(const basic_json_view& a, const basic_json_view& b)
|
|
{
|
|
return detail::view::equal<BasicJsonType>(side(a), side(b));
|
|
}
|
|
|
|
friend bool operator!=(const basic_json_view& a, const basic_json_view& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
/// a view of an editable document compares with one of a read-only document
|
|
template<view_mode M, detail::enable_if_t<detail::view::is_other_mode<M, Mode>::value, int> = 0>
|
|
friend bool operator==(const basic_json_view& a, const basic_json_view<BasicJsonType, M>& b)
|
|
{
|
|
return detail::view::equal<BasicJsonType>(side(a), detail::view::view_side<BasicJsonType, basic_json_view<BasicJsonType, M>>(b));
|
|
}
|
|
|
|
template<view_mode M, detail::enable_if_t<detail::view::is_other_mode<M, Mode>::value, int> = 0>
|
|
friend bool operator!=(const basic_json_view& a, const basic_json_view<BasicJsonType, M>& b)
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
/// whether the value parse() would produce for a view equals a value
|
|
friend bool operator==(const basic_json_view& a, const BasicJsonType& j)
|
|
{
|
|
return detail::view::equal<BasicJsonType>(side(a), json_side_t(j));
|
|
}
|
|
|
|
friend bool operator==(const BasicJsonType& j, const basic_json_view& a)
|
|
{
|
|
return a == j;
|
|
}
|
|
|
|
friend bool operator!=(const basic_json_view& a, const BasicJsonType& j)
|
|
{
|
|
return !(a == j);
|
|
}
|
|
|
|
friend bool operator!=(const BasicJsonType& j, const basic_json_view& a)
|
|
{
|
|
return !(a == j);
|
|
}
|
|
|
|
/////////////////
|
|
// materialize //
|
|
/////////////////
|
|
|
|
/// the basic_json value of this subtree, as parse() would produce it
|
|
/// (a discarded value for a discarded view)
|
|
BasicJsonType materialize() const
|
|
{
|
|
if (m_node == nullptr)
|
|
{
|
|
return BasicJsonType(value_t::discarded);
|
|
}
|
|
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, is_editable>(*m_doc, m_node);
|
|
}
|
|
|
|
/// byte offset of this value in the source text (for strings: of the
|
|
/// first byte after the opening quote); static_cast<std::size_t>(-1) for
|
|
/// a discarded view and for strings with escapes, which are decoded
|
|
std::size_t source_offset() const noexcept
|
|
{
|
|
return m_node != nullptr && (m_node->flags & (detail::view::node_flags::storage | detail::view::node_flags::moved | detail::view::node_flags::is_new)) == 0
|
|
? m_node->off : static_cast<std::size_t>(-1);
|
|
}
|
|
|
|
private:
|
|
template<typename, view_mode> friend class basic_json_document;
|
|
template<typename, view_mode> friend class basic_json_view;
|
|
template<typename, typename> friend class detail::view::editor;
|
|
template<typename, typename> friend class detail::view::view_side;
|
|
friend iterator;
|
|
|
|
/// whether the view is one of an editable document
|
|
static constexpr bool is_editable = Editable;
|
|
|
|
basic_json_view(const document_data* d, const node* n) noexcept
|
|
: m_doc(d), m_node(n)
|
|
{}
|
|
|
|
using json_side_t = detail::view::json_side<BasicJsonType, string_view_t>;
|
|
|
|
static detail::view::view_side<BasicJsonType, basic_json_view> side(const basic_json_view& v) noexcept
|
|
{
|
|
return detail::view::view_side<BasicJsonType, basic_json_view>(v);
|
|
}
|
|
|
|
/// the template argument as a runtime condition: testing it directly in
|
|
/// `is_editable && ...` makes the condition constant, which MSVC reports as
|
|
/// C4127
|
|
static bool editable() noexcept
|
|
{
|
|
return is_editable;
|
|
}
|
|
|
|
/// the number of source bytes of this value (estimated for values with
|
|
/// decoded strings); the estimate sizes the output buffer of dump()
|
|
std::size_t source_extent() const noexcept
|
|
{
|
|
if (editable() && m_doc->edits != nullptr)
|
|
{
|
|
// positions of moved and new values are not source offsets; the root
|
|
// (the first node) spans the source text and every edit
|
|
const bool is_root = m_node == m_doc->nodes;
|
|
return is_root ? m_doc->size + m_doc->edits->text_used : 64;
|
|
}
|
|
const node* const end = m_doc->nodes + m_doc->node_count;
|
|
if ((m_node->flags & detail::view::node_flags::storage) != 0)
|
|
{
|
|
return m_node->len;
|
|
}
|
|
// the value ends where the next node in the source begins; nodes
|
|
// with decoded strings (their offset is in the arena) are skipped,
|
|
// but only a few of them, to keep the walk short
|
|
const node* next = document_data::after(m_node);
|
|
for (int skipped = 0; next != end && skipped < 16; ++skipped, ++next)
|
|
{
|
|
if ((next->flags & detail::view::node_flags::storage) == 0)
|
|
{
|
|
return next->off >= m_node->off ? next->off - m_node->off : 0;
|
|
}
|
|
}
|
|
if (next == end)
|
|
{
|
|
return m_doc->size - m_node->off;
|
|
}
|
|
// the end is unknown: assume a few bytes per node, the output buffer
|
|
// grows should the value be larger
|
|
const auto nodes = static_cast<std::size_t>(document_data::after(m_node) - m_node);
|
|
return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + (nodes * 16));
|
|
}
|
|
|
|
/// the value of the first member with this key, or a discarded view
|
|
/// (object required)
|
|
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
|
{
|
|
if (unedited())
|
|
{
|
|
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
|
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
|
}
|
|
const node* const k = detail::view::find_member<is_editable>(*m_doc, m_node, key.data(), key.size());
|
|
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
|
}
|
|
|
|
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
|
|
/// read-only document (nothing is moved or linked), which is cheaper
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
|
|
{
|
|
return !navigation::editable() || m_doc->edits == nullptr;
|
|
}
|
|
|
|
/// the member of this object with @a key (the last one if the key occurs more than once), or nullptr
|
|
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const
|
|
{
|
|
return find_key(key, std::integral_constant<bool, is_editable> {});
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const
|
|
{
|
|
// (an editable document without edits is looked up as one that cannot be edited)
|
|
return unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size())
|
|
: detail::view::find_member<true>(*m_doc, m_node, key.data(), key.size());
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::false_type /*editable*/) const
|
|
{
|
|
return detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
|
}
|
|
|
|
/// the element of an array at an index below its size
|
|
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
|
|
{
|
|
if (unedited())
|
|
{
|
|
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
|
|
}
|
|
return basic_json_view(m_doc, navigation::value(detail::view::element_at<is_editable>(*m_doc, m_node, idx)));
|
|
}
|
|
|
|
// --- get() dispatch ---
|
|
|
|
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
|
}
|
|
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
|
}
|
|
|
|
template<typename T, typename std::enable_if<detail::view::is_non_bool_arithmetic<T>::value, int>::type = 0>
|
|
NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
|
}
|
|
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
|
}
|
|
|
|
std::nullptr_t get_impl(detail::view::value_tag<std::nullptr_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
string_view_t get_impl(detail::view::value_tag<string_view_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
return get_string();
|
|
}
|
|
|
|
template<typename Traits, typename Alloc>
|
|
std::basic_string<char, Traits, Alloc> get_impl(detail::view::value_tag<std::basic_string<char, Traits, Alloc>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
const string_view_t s = get_string();
|
|
return std::basic_string<char, Traits, Alloc>(s.data(), s.size());
|
|
}
|
|
|
|
BasicJsonType get_impl(detail::view::value_tag<BasicJsonType> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
return materialize();
|
|
}
|
|
|
|
basic_json_view get_impl(detail::view::value_tag<basic_json_view> /*unused*/, detail::priority_tag<2> /*unused*/) const noexcept
|
|
{
|
|
return *this;
|
|
}
|
|
|
|
/// vectors of the types converted directly (faster than the library's
|
|
/// from_json()) and containers of views (no from_json() reads them: a view
|
|
/// is no value of BasicJsonType); other containers are read by from_json()
|
|
template<typename U, typename A, detail::enable_if_t<detail::view::is_direct_value<U, BasicJsonType, basic_json_view>::value, int> = 0>
|
|
std::vector<U, A> get_impl(detail::view::value_tag<std::vector<U, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
return detail::view::vector_value<basic_json_view, U, A>(*this);
|
|
}
|
|
|
|
template<typename K, typename C, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
|
|
std::map<K, basic_json_view, C, A> get_impl(detail::view::value_tag<std::map<K, basic_json_view, C, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
return detail::view::map_value<std::map<K, basic_json_view, C, A>>(*this);
|
|
}
|
|
|
|
template<typename K, typename H, typename E, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
|
|
std::unordered_map<K, basic_json_view, H, E, A> get_impl(detail::view::value_tag<std::unordered_map<K, basic_json_view, H, E, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
|
{
|
|
return detail::view::map_value<std::unordered_map<K, basic_json_view, H, E, A>>(*this);
|
|
}
|
|
|
|
/// whether the from_json() of T's serializer reads views (see
|
|
/// detail::has_view_from_json)
|
|
template<typename T>
|
|
using reads_view = detail::has_view_from_json<typename BasicJsonType::template json_serializer<T, void>, basic_json_view, T>;
|
|
|
|
/// how get_to() converts: 2 directly, 1 with a from_json() that reads
|
|
/// views, 0 through materialize()
|
|
template<typename T>
|
|
using get_to_kind = std::integral_constant < int, detail::view::is_direct_value<T, BasicJsonType, basic_json_view>::value
|
|
? 2 : static_cast<int>(reads_view<T>::value) >;
|
|
|
|
/// types whose from_json() reads views, without materialize()
|
|
template<typename T, detail::enable_if_t<detail::conjunction<reads_view<T>, std::is_default_constructible<T>>::value, int> = 0>
|
|
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<1> /*unused*/) const
|
|
{
|
|
auto v = T();
|
|
BasicJsonType::template json_serializer<T, void>::from_json(*this, v);
|
|
return v;
|
|
}
|
|
|
|
/// everything else through the BasicJsonType value (from_json included)
|
|
template<typename T>
|
|
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<0> /*unused*/) const
|
|
{
|
|
return materialize().template get<T>();
|
|
}
|
|
|
|
// --- get_to() dispatch ---
|
|
|
|
template<typename T>
|
|
void get_to_impl(T& v, std::integral_constant<int, 2> /*direct*/) const
|
|
{
|
|
v = get<T>();
|
|
}
|
|
|
|
/// from_json may keep parts of v (members a JSON object lacks, say)
|
|
template<typename T>
|
|
void get_to_impl(T& v, std::integral_constant<int, 1> /*from_json reads views*/) const
|
|
{
|
|
BasicJsonType::template json_serializer<T, void>::from_json(*this, v);
|
|
}
|
|
|
|
template<typename T>
|
|
void get_to_impl(T& v, std::integral_constant<int, 0> /*through materialize()*/) const
|
|
{
|
|
materialize().get_to(v);
|
|
}
|
|
|
|
const document_data* m_doc = nullptr;
|
|
const node* m_node = nullptr;
|
|
};
|
|
|
|
/*!
|
|
@brief a parsed JSON text: owns the node index (and, optionally, the text)
|
|
|
|
Borrowed parses keep a pointer to the caller's text, which must outlive the
|
|
document. Owned parses (parse_copy, rvalue std::string, streams, and inputs
|
|
that are not contiguous byte ranges) keep their own copy.
|
|
*/
|
|
template<typename BasicJsonType, view_mode Mode = view_mode::read_only>
|
|
class basic_json_document
|
|
{
|
|
/// whether the document can be edited
|
|
static constexpr bool is_editable = Mode == view_mode::editable;
|
|
|
|
using document_data = detail::view::document_data;
|
|
|
|
static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8,
|
|
"json_view supports 64-bit integer types only");
|
|
|
|
public:
|
|
using view_type = basic_json_view<BasicJsonType, Mode>;
|
|
using value_t = detail::value_t;
|
|
|
|
/// an empty (discarded) document
|
|
basic_json_document() = default;
|
|
basic_json_document(basic_json_document&&) noexcept = default;
|
|
basic_json_document& operator=(basic_json_document&&) noexcept = default;
|
|
basic_json_document(const basic_json_document&) = delete;
|
|
basic_json_document& operator=(const basic_json_document&) = delete;
|
|
~basic_json_document() = default;
|
|
|
|
/////////////
|
|
// parsing //
|
|
/////////////
|
|
|
|
/// parse a JSON text; contiguous byte inputs are borrowed, everything else
|
|
/// (and rvalue std::string) is owned
|
|
template<typename InputType>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
/// parse(ptr, len) does not compile: len would convert to allow_exceptions
|
|
/// and ptr be read as a C string (as for the overloads of parse_copy,
|
|
/// accept, and read below)
|
|
template<typename InputType, typename IntegerType, typename... Flags>
|
|
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, basic_json_document>::type
|
|
parse(InputType&& input, IntegerType value, Flags&&... flags) = delete;
|
|
|
|
/// parse [first, last)
|
|
template<typename IteratorType, typename std::enable_if<
|
|
std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse(IteratorType first, IteratorType last,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
/// parse a copy of the input; the document does not depend on it afterwards
|
|
template<typename InputType>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse_copy(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
std::string buf;
|
|
append_input(buf, std::forward<InputType>(input));
|
|
d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
template<typename InputType, typename IntegerType, typename... Flags>
|
|
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, basic_json_document>::type
|
|
parse_copy(InputType&& input, IntegerType value, Flags&&... flags) = delete;
|
|
|
|
/// check whether the input is valid JSON (the result of basic_json::accept)
|
|
template<typename InputType>
|
|
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
|
|
return !d.is_discarded();
|
|
}
|
|
|
|
template<typename InputType, typename IntegerType, typename... Flags>
|
|
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, bool>::type
|
|
accept(InputType&& input, IntegerType value, Flags&&... flags) = delete;
|
|
|
|
/// parse into this document, reusing its memory
|
|
template<typename InputType>
|
|
// flawfinder: ignore (a member function, not POSIX read())
|
|
void read(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
read_kind(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas,
|
|
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
|
|
}
|
|
|
|
template<typename InputType, typename IntegerType, typename... Flags>
|
|
typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, void>::type
|
|
// flawfinder: ignore (a member function, not POSIX read())
|
|
read(InputType&& input, IntegerType value, Flags&&... flags) = delete;
|
|
|
|
////////////
|
|
// access //
|
|
////////////
|
|
|
|
/// the root value (discarded if parsing failed without exceptions)
|
|
view_type root() const& noexcept
|
|
{
|
|
if (!m_data || m_data->discarded)
|
|
{
|
|
return view_type();
|
|
}
|
|
return view_type(m_data.get(), m_data->nodes);
|
|
}
|
|
|
|
/// deleted: the view of a temporary document would dangle
|
|
view_type root() const&& = delete;
|
|
|
|
bool is_discarded() const noexcept
|
|
{
|
|
return !m_data || m_data->discarded;
|
|
}
|
|
|
|
/// the parsed text
|
|
typename view_type::string_view_t source() const noexcept
|
|
{
|
|
return m_data ? typename view_type::string_view_t(m_data->src, m_data->size) : typename view_type::string_view_t();
|
|
}
|
|
|
|
/// whether the document holds its own copy of the text
|
|
bool owns_source() const noexcept
|
|
{
|
|
return m_data && ((!m_data->owned.empty() && m_data->src == m_data->owned.data()) || !m_data->owned_image.empty());
|
|
}
|
|
|
|
/// number of index nodes (values plus object keys)
|
|
std::size_t node_count() const noexcept
|
|
{
|
|
return m_data ? m_data->node_count : 0;
|
|
}
|
|
|
|
/// bytes held by the document (index, decoded strings, and owned text or image)
|
|
std::size_t memory_usage() const noexcept
|
|
{
|
|
if (!m_data)
|
|
{
|
|
return 0;
|
|
}
|
|
return sizeof(document_data) + (m_data->inline_capacity * sizeof(detail::view::node))
|
|
+ (m_data->nodes != m_data->inline_nodes ? m_data->node_capacity * sizeof(detail::view::node) : 0)
|
|
+ m_data->arena.capacity() + m_data->owned.capacity() + m_data->owned_image.capacity()
|
|
+ (m_data->indexes.capacity() * sizeof(document_data::object_index)) + (m_data->index_slots.capacity() * sizeof(std::uint32_t))
|
|
+ (m_data->large_objects.capacity() * sizeof(std::uint32_t))
|
|
+ (m_data->edits != nullptr ? m_data->edits->bytes : 0);
|
|
}
|
|
|
|
/// release unused capacity of the index and the decoded strings; like
|
|
/// std::vector::shrink_to_fit, this invalidates the views of the document
|
|
/// (take new ones from root())
|
|
void shrink_to_fit()
|
|
{
|
|
if (!m_data)
|
|
{
|
|
return;
|
|
}
|
|
using detail::view::node;
|
|
document_data& d = *m_data;
|
|
|
|
// allocate everything first, so that an exception leaves the document
|
|
// unchanged
|
|
// (the decoded strings of a loaded image stay in the image)
|
|
const bool arena_in_use = d.base[1] == d.arena.data();
|
|
const bool shrink_arena = d.arena.capacity() > d.arena.size();
|
|
std::vector<char> arena(shrink_arena && arena_in_use ? d.arena : std::vector<char>());
|
|
// (edits link to the nodes of the index, which then stays in place)
|
|
const bool shrink_nodes = d.nodes != d.inline_nodes && d.node_count != d.node_capacity && d.edits == nullptr;
|
|
const bool into_header = d.node_count <= d.inline_capacity;
|
|
std::vector<document_data::object_index> indexes(d.indexes.capacity() > d.indexes.size() ? d.indexes : std::vector<document_data::object_index>());
|
|
std::vector<std::uint32_t> index_slots(d.index_slots.capacity() > d.index_slots.size() ? d.index_slots : std::vector<std::uint32_t>());
|
|
node* fresh = (shrink_nodes && !into_header) ? static_cast<node*>(::operator new (d.node_count * sizeof(node))) : d.inline_nodes;
|
|
|
|
if (shrink_nodes)
|
|
{
|
|
if (d.node_count != 0)
|
|
{
|
|
std::memcpy(fresh, d.nodes, d.node_count * sizeof(node));
|
|
}
|
|
::operator delete (d.nodes);
|
|
d.nodes = fresh;
|
|
d.node_capacity = into_header ? d.inline_capacity : d.node_count;
|
|
}
|
|
if (shrink_arena)
|
|
{
|
|
d.arena.swap(arena);
|
|
if (arena_in_use)
|
|
{
|
|
d.base[1] = d.arena.data();
|
|
}
|
|
}
|
|
if (d.indexes.capacity() > d.indexes.size())
|
|
{
|
|
d.indexes.swap(indexes);
|
|
}
|
|
if (d.index_slots.capacity() > d.index_slots.size())
|
|
{
|
|
d.index_slots.swap(index_slots);
|
|
}
|
|
}
|
|
|
|
////////////
|
|
// images //
|
|
////////////
|
|
|
|
/// how load() checks an image (full, bounds, or none)
|
|
using image_check = detail::view::image_check;
|
|
|
|
#if NLOHMANN_VIEW_LITTLE_ENDIAN
|
|
// (the image format is little-endian: big-endian targets have no save()
|
|
// and load(), so that a call fails to compile instead of always throwing)
|
|
|
|
/// The document as an image that load() reads without parsing: the node
|
|
/// index, the text, and the decoded strings. An edited document is
|
|
/// written in its current state (floats that are not finite become null,
|
|
/// as in dump()).
|
|
std::vector<std::uint8_t> save() const
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
|
{
|
|
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document");
|
|
}
|
|
return detail::view::save_image(*m_data);
|
|
}
|
|
|
|
/// Read an image written by save(). The image is borrowed: it must stay
|
|
/// alive and unchanged while the document is used.
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document load(const std::uint8_t* image, std::size_t size, const image_check check = image_check::full)
|
|
{
|
|
basic_json_document d;
|
|
d.ensure_data(nullptr, 0);
|
|
detail::view::load_image(*d.m_data, image, size, check);
|
|
return d;
|
|
}
|
|
|
|
/// read an image (borrowed)
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document load(const std::vector<std::uint8_t>& image, const image_check check = image_check::full)
|
|
{
|
|
return load(image.data(), image.size(), check);
|
|
}
|
|
|
|
/// read an image and keep it (no copy)
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document load(std::vector<std::uint8_t>&& image, const image_check check = image_check::full)
|
|
{
|
|
basic_json_document d;
|
|
d.ensure_data(nullptr, 0);
|
|
d.m_data->owned_image = std::move(image);
|
|
detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check);
|
|
return d;
|
|
}
|
|
#endif
|
|
|
|
///////////
|
|
// edits //
|
|
///////////
|
|
|
|
// The source text is never written; new values go to storage owned by
|
|
// the document. A view keeps referring to the same value: after an
|
|
// assignment it sees the new value, and edits elsewhere do not affect it.
|
|
// A view of an erased value keeps its last value. An edit of an
|
|
// array/object invalidates the iterators over it. Values are accepted as
|
|
// views (of any document), BasicJsonType values, and everything
|
|
// BasicJsonType can be constructed from.
|
|
|
|
using string_view_t = typename view_type::string_view_t;
|
|
using json_pointer = typename BasicJsonType::json_pointer;
|
|
|
|
/// replace a value (a view of this document); returns a view of it
|
|
template<typename V>
|
|
view_type set(view_type target, V&& value)
|
|
{
|
|
return editor().set(target, std::forward<V>(value));
|
|
}
|
|
|
|
/// set a member (added if missing; a null value becomes an object);
|
|
/// returns a view of the member value
|
|
template<typename V>
|
|
view_type set(view_type object, string_view_t key, V&& value)
|
|
{
|
|
return editor().set(object, key, std::forward<V>(value));
|
|
}
|
|
|
|
/// assign an existing array element; returns a view of it
|
|
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
|
view_type set(view_type array, I idx, V && value)
|
|
{
|
|
return editor().set(array, index(idx), std::forward<V>(value));
|
|
}
|
|
|
|
/// set the value at a JSON pointer: its parent must exist; an object
|
|
/// member is set (added if missing), an array element assigned, and "-"
|
|
/// or the size of the array appends. A null parent becomes what
|
|
/// basic_json's operator[](json_pointer) makes of it: an array for "-"
|
|
/// and for array indices (padded with nulls up to the index), an object otherwise.
|
|
template<typename V>
|
|
view_type set(const json_pointer& ptr, V&& value)
|
|
{
|
|
if (ptr.empty())
|
|
{
|
|
return set(root(), std::forward<V>(value));
|
|
}
|
|
const view_type parent = root().at(ptr.parent_pointer());
|
|
const auto& token = ptr.back();
|
|
if (parent.is_null())
|
|
{
|
|
if (token == "-")
|
|
{
|
|
return push_back(parent, std::forward<V>(value));
|
|
}
|
|
// (a token that cannot be an array index, such as "01" or "", names an
|
|
// object member, as in basic_json)
|
|
const bool digits = !token.empty()
|
|
&& (token.size() == 1 || token[0] != '0')
|
|
&& std::all_of(token.begin(), token.end(), [](const char c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
});
|
|
if (digits)
|
|
{
|
|
// (an invalid index is an error before the parent changes)
|
|
const std::size_t idx = pointer_index(token);
|
|
if (idx >= 0xFFFFFFFFu)
|
|
{
|
|
detail::view::throw_array_index_out_of_range(idx);
|
|
}
|
|
for (std::size_t i = 0; i < idx; ++i)
|
|
{
|
|
push_back(parent, nullptr);
|
|
}
|
|
return push_back(parent, std::forward<V>(value));
|
|
}
|
|
}
|
|
if (parent.is_array())
|
|
{
|
|
const std::size_t idx = token == "-" ? parent.size() : pointer_index(token);
|
|
if (idx == parent.size())
|
|
{
|
|
return push_back(parent, std::forward<V>(value));
|
|
}
|
|
return set(parent, idx, std::forward<V>(value));
|
|
}
|
|
return set(parent, string_view_t(token.data(), token.size()), std::forward<V>(value));
|
|
}
|
|
|
|
/// append to an array (a null value becomes an array); returns a view of
|
|
/// the new element
|
|
template<typename V>
|
|
view_type push_back(view_type array, V&& value)
|
|
{
|
|
return editor().push_back(array, std::forward<V>(value));
|
|
}
|
|
|
|
/// insert into an array before position idx (idx <= size()); returns a
|
|
/// view of the new element
|
|
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
|
view_type insert(view_type array, I idx, V && value)
|
|
{
|
|
return editor().insert(array, index(idx), std::forward<V>(value));
|
|
}
|
|
|
|
/// remove all members with this key; returns their number
|
|
std::size_t erase(view_type object, string_view_t key)
|
|
{
|
|
return editor().erase(object, key);
|
|
}
|
|
|
|
/// remove an array element
|
|
template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
|
void erase(view_type array, I idx)
|
|
{
|
|
editor().erase(array, index(idx));
|
|
}
|
|
|
|
/// remove the value at a JSON pointer; returns the number of removed
|
|
/// values
|
|
std::size_t erase(const json_pointer& ptr)
|
|
{
|
|
if (ptr.empty())
|
|
{
|
|
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent");
|
|
}
|
|
const view_type parent = root().at(ptr.parent_pointer());
|
|
const auto& token = ptr.back();
|
|
if (parent.is_array())
|
|
{
|
|
erase(parent, pointer_index(token));
|
|
return 1;
|
|
}
|
|
return erase(parent, string_view_t(token.data(), token.size()));
|
|
}
|
|
|
|
private:
|
|
using input_kind = detail::view::input_kind;
|
|
|
|
detail::view::editor<BasicJsonType, view_type> editor()
|
|
{
|
|
static_assert(is_editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, view_mode::editable> (json_editable_document)");
|
|
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
|
{
|
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
|
|
}
|
|
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
|
}
|
|
|
|
/// an index (out_of_range.401 if negative)
|
|
template<typename I>
|
|
static std::size_t index(I idx)
|
|
{
|
|
return index(idx, std::is_signed<I> {});
|
|
}
|
|
|
|
template<typename I>
|
|
static std::size_t index(I idx, std::true_type /*signed*/)
|
|
{
|
|
if (idx < 0)
|
|
{
|
|
detail::view::throw_array_index_out_of_range(idx);
|
|
}
|
|
return static_cast<std::size_t>(idx);
|
|
}
|
|
|
|
template<typename I>
|
|
static std::size_t index(I idx, std::false_type /*unsigned*/)
|
|
{
|
|
return static_cast<std::size_t>(idx);
|
|
}
|
|
|
|
/// the array index of a JSON pointer token (json_pointer's rules)
|
|
template<typename StringType>
|
|
static std::size_t pointer_index(const StringType& token)
|
|
{
|
|
std::size_t idx = 0;
|
|
const detail::view::index_status status = detail::view::array_index(token, idx);
|
|
if (status != detail::view::index_status::ok)
|
|
{
|
|
detail::view::throw_array_index_error(status, token);
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
/// create the storage (sized for the input) on first use
|
|
void ensure_data(const char* src, std::size_t size)
|
|
{
|
|
if (!m_data)
|
|
{
|
|
m_data.reset(document_data::create(detail::view::estimate_nodes(src, size)));
|
|
}
|
|
}
|
|
|
|
/// parse a buffer the document takes ownership of (it replaces the
|
|
/// previous owned copy)
|
|
void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas)
|
|
{
|
|
ensure_data(buf.data(), buf.size());
|
|
m_data->owned = std::move(buf);
|
|
build(m_data->owned.data(), m_data->owned.size(), allow_exceptions, comments, trailing_commas, true, true);
|
|
}
|
|
|
|
/// parse a copy of the input; the copy is appended to the cleared owned
|
|
/// buffer, so that its capacity is reused
|
|
template<typename T>
|
|
void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas)
|
|
{
|
|
if (!m_data)
|
|
{
|
|
std::string buf;
|
|
append_input(buf, std::forward<T>(input));
|
|
build_owned(std::move(buf), allow_exceptions, comments, trailing_commas);
|
|
return;
|
|
}
|
|
document_data& d = *m_data;
|
|
// (the previous text is gone from here on: should reading the input
|
|
// throw, the document is discarded)
|
|
d.discarded = true;
|
|
d.src = "";
|
|
d.size = 0;
|
|
d.owned.clear();
|
|
append_input(d.owned, std::forward<T>(input));
|
|
build(d.owned.data(), d.owned.size(), allow_exceptions, comments, trailing_commas, true, true);
|
|
}
|
|
|
|
/// sentinel: src[size] is readable and 0 (std::string, C strings)
|
|
void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
|
|
{
|
|
ensure_data(src, size);
|
|
document_data& d = *m_data;
|
|
if (!owned)
|
|
{
|
|
d.owned.clear();
|
|
}
|
|
d.owned_image.clear();
|
|
d.src = src;
|
|
d.size = size;
|
|
d.node_count = 0;
|
|
d.edits.reset(); // (views of the previous text end here anyway)
|
|
d.base[2] = nullptr;
|
|
d.arena.clear();
|
|
d.indexes.clear();
|
|
d.index_slots.clear();
|
|
d.large_objects.clear();
|
|
d.discarded = true;
|
|
detail::view::parse_failure failure;
|
|
bool ok = false;
|
|
if (NLOHMANN_VIEW_UNLIKELY(size > detail::view::max_input_size()))
|
|
{
|
|
failure.code = detail::view::error_code::input_too_large;
|
|
}
|
|
else
|
|
{
|
|
ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
|
|
}
|
|
if (NLOHMANN_VIEW_LIKELY(ok))
|
|
{
|
|
d.base[0] = d.src;
|
|
d.base[1] = d.arena.data();
|
|
d.arena_size = d.arena.size();
|
|
detail::view::build_object_indexes(d);
|
|
std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while parsing)
|
|
d.discarded = false;
|
|
return;
|
|
}
|
|
std::vector<std::uint32_t>().swap(d.large_objects);
|
|
if (allow_exceptions)
|
|
{
|
|
detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas);
|
|
}
|
|
}
|
|
|
|
// --- input dispatch (see detail::view::input_kind) ---
|
|
|
|
void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
|
|
{
|
|
build_owned(std::move(s), ae, c, tc);
|
|
}
|
|
|
|
template<typename CharT>
|
|
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
|
|
{
|
|
if (s == nullptr)
|
|
{
|
|
build("", 0, ae, c, tc, false, true);
|
|
return;
|
|
}
|
|
const char* cs = reinterpret_cast<const char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
// C strings are null-terminated, as for json::parse(const char*)
|
|
// flawfinder: ignore
|
|
build(cs, std::strlen(cs), ae, c, tc, false, true);
|
|
}
|
|
|
|
template<typename Array>
|
|
void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
|
|
{
|
|
using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type;
|
|
const std::size_t n = std::extent<Array>::value;
|
|
// a trailing NUL (string literals) is not part of the text, as for
|
|
// parse(), and serves as sentinel
|
|
const bool terminated = n > 0 && a[n - 1] == 0 && (!detail::abi_config::strict_nul_handling || std::is_same<CharT, char>::value);
|
|
build(reinterpret_cast<const char*>(&a[0]), terminated ? n - 1 : n, ae, c, tc, false, terminated); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
|
|
{
|
|
// std::basic_string guarantees data()[size()] == 0: use it as sentinel
|
|
const auto size = static_cast<std::size_t>(s.size());
|
|
build(size == 0 ? "" : reinterpret_cast<const char*>(s.data()), size, ae, c, tc, false, // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
detail::view::is_std_string<T>::value || size == 0);
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
|
|
{
|
|
build_copy(s, ae, c, tc);
|
|
}
|
|
|
|
template<typename CharT>
|
|
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
|
|
{
|
|
if (s == nullptr)
|
|
{
|
|
build("", 0, ae, c, tc, false, true);
|
|
return;
|
|
}
|
|
// NUL-terminated, as for the single-byte C strings
|
|
build_copy(detail::view::make_adapter_input(detail::input_adapter(s, s + std::char_traits<typename std::remove_cv<CharT>::type>::length(s))), ae, c, tc);
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
|
|
{
|
|
build_copy(std::forward<T>(input), ae, c, tc);
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc)
|
|
{
|
|
using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type;
|
|
// borrow the range where the library's input adapter scans it as one
|
|
// block: pointers and, in C++20, contiguous iterators (std::vector,
|
|
// std::string, ...) over single bytes
|
|
read_range_impl(first, last, ae, c, tc, std::integral_constant < bool, std::is_integral<value_type>::value
|
|
&& detail::iterator_input_adapter<IteratorType>::supports_bulk_scan > {});
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
|
|
{
|
|
const auto size = static_cast<std::size_t>(std::distance(first, last));
|
|
build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/)
|
|
{
|
|
build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc);
|
|
}
|
|
|
|
/// append the text of an input to buf: a contiguous byte input as one
|
|
/// block, a stream in blocks, an input adapter (made here for any other
|
|
/// input) character by character
|
|
template<typename T>
|
|
static void append_input(std::string& buf, T&& input)
|
|
{
|
|
using D = typename std::decay<T>::type;
|
|
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
|
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
|
|
: std::is_base_of<std::istream, D>::value ? 1
|
|
: detail::view::is_adapter_input<D>::value ? 2
|
|
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
|
|
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
|
}
|
|
|
|
template<typename T>
|
|
static void append_input_impl(std::string& buf, const T& s, std::integral_constant<int, 0> /*contiguous bytes*/)
|
|
{
|
|
buf.append(reinterpret_cast<const char*>(s.data()), static_cast<std::size_t>(s.size())); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename Stream>
|
|
static void append_input_impl(std::string& buf, Stream&& is, std::integral_constant<int, 1> /*stream*/) // NOLINT(cppcoreguidelines-missing-std-forward): read in place, as by the library's stream adapter
|
|
{
|
|
detail::view::append_stream(buf, is);
|
|
}
|
|
|
|
template<typename Adapter>
|
|
static void append_input_impl(std::string& buf, detail::view::adapter_input<Adapter>&& input, std::integral_constant<int, 2> /*input adapter*/)
|
|
{
|
|
detail::view::append_adapter(buf, std::move(input.ia));
|
|
}
|
|
|
|
#ifndef JSON_NO_IO
|
|
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
|
|
{
|
|
if (file == nullptr)
|
|
{
|
|
// (as before: the library's adapter decides what a null stream yields)
|
|
detail::view::append_adapter(buf, detail::input_adapter(file));
|
|
return;
|
|
}
|
|
detail::view::append_file(buf, file);
|
|
}
|
|
#endif
|
|
|
|
template<typename T>
|
|
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
|
{
|
|
detail::view::append_adapter(buf, detail::input_adapter(std::forward<T>(input)));
|
|
}
|
|
|
|
std::unique_ptr<document_data, document_data::deleter> m_data{}; // NOLINT(readability-redundant-member-init)
|
|
};
|
|
|
|
/*!
|
|
@brief whether T is a basic_json_view (of any BasicJsonType and view_mode)
|
|
|
|
For constraining templates that accept views, e.g. a from_json() written for
|
|
both basic_json values and views.
|
|
*/
|
|
template<typename T>
|
|
struct is_json_view : detail::is_json_view<T> {};
|
|
|
|
/// a parsed JSON text for json
|
|
using json_document = basic_json_document<json, view_mode::read_only>;
|
|
/// a value of a json_document
|
|
using json_view = basic_json_view<json, view_mode::read_only>;
|
|
/// a parsed JSON text for ordered_json
|
|
using ordered_json_document = basic_json_document<ordered_json, view_mode::read_only>;
|
|
/// a value of an ordered_json_document
|
|
using ordered_json_view = basic_json_view<ordered_json, view_mode::read_only>;
|
|
/// an editable parsed JSON text for json
|
|
using json_editable_document = basic_json_document<json, view_mode::editable>;
|
|
/// a value of a json_editable_document
|
|
using json_editable_view = basic_json_view<json, view_mode::editable>;
|
|
/// an editable parsed JSON text for ordered_json
|
|
using ordered_json_editable_document = basic_json_document<ordered_json, view_mode::editable>;
|
|
/// a value of an ordered_json_editable_document
|
|
using ordered_json_editable_view = basic_json_view<ordered_json, view_mode::editable>;
|
|
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
// tuple protocol for the items of basic_json_view::items() (structured bindings)
|
|
namespace std // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification)
|
|
{
|
|
|
|
#if defined(__clang__)
|
|
// Fix: https://github.com/nlohmann/json/issues/1401
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wmismatched-tags"
|
|
#endif
|
|
template<typename View>
|
|
class tuple_size<::nlohmann::detail::view::view_item<View>> // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification)
|
|
: public std::integral_constant<std::size_t, 2> {};
|
|
|
|
template<std::size_t N, typename View>
|
|
class tuple_element<N, ::nlohmann::detail::view::view_item<View>> // NOLINT(cert-dcl58-cpp,bugprone-std-namespace-modification)
|
|
{
|
|
public:
|
|
using type = decltype(std::declval<::nlohmann::detail::view::view_item<View>>().template get<N>());
|
|
};
|
|
#if defined(__clang__)
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
} // namespace std
|
|
|
|
#include <nlohmann/detail/view/macro_unscope.hpp> // IWYU pragma: keep
|
|
|
|
#endif // INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|