Compare commits

..
Author SHA1 Message Date
Niels Lohmann 9cb9f7fbad Add images of json_documents: save() and load()
An image is a document stored so that loading it needs no
parsing: save() writes the node index, the text and the decoded
strings; the static load() reads an image written by save().

load() takes a pointer and size, a borrowed vector, or an owned
rvalue vector; the nodes are copied so they are aligned and can
be edited, while the text and decoded strings stay in the image.

image_check controls how much load() trusts the input: full
checks structure, bounds, strings and numbers, the parser's own
guarantees; bounds checks structure and bounds only; none skips
all checks, for images from a trusted source.

Layout is little-endian only ("NJVI" header, nodes, text, decoded
strings), following the idea of zero-copy formats such as
FlatBuffers and YaFF; the check follows FlatBuffers' Verifier.

New errors: parse_error.116 for a malformed image or a failed
check, type_error.320 for a discarded document or a big-endian
target.

A dedicated fuzzer and 6,000 seeded corruptions, checked under
ASan/UBSan, found and fixed two gaps: unchecked reserved header
fields, and unbounded null/boolean offsets that could make
dump() throw std::length_error.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 10:48:07 +02:00
Niels Lohmann a0b98325cf Add editable json_documents: set, push_back, insert, and erase
basic_json_document gets a second template parameter, Editable
(false by default), plus the aliases json_editable_document,
json_editable_view, ordered_json_editable_document and
ordered_json_editable_view.

Editable documents can change values and structure without
rewriting the source text: set()/push_back() on values, keys,
array indices and JSON pointers; insert() before an array
element; erase() of an object key, array index or JSON pointer.

New values and element sequences go into edit storage that the
document owns and never moves, so views keep referring to their
value across edits and a parsed node never moves. Read-only
documents walk the plain node array and are unaffected.

Strings are checked for UTF-8 on entry, so dump() of an editable
document never throws type_error.316. Binary values cannot be
stored (type_error.319).

A seeded differential test applies random edits to an editable
document and to the equivalent ordered_json and compares both
after every step.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 10:48:07 +02:00
Niels Lohmann 6ee5e89803 Scan json_view strings with SIMD and index large objects
Speed up json_view's parser with SIMD scanning and a hash table
for large objects.

Long runs of string bytes are scanned 16 bytes at a time with NEON
(AArch64, GCC and Clang) and SSE2 (x86-64), both baseline
instruction sets. Keys keep 16 table checks before the vector
loop, because their lengths repeat from record to record; string
values get 8, because their lengths vary more. Non-ASCII text is
validated 16 bytes at a time with simdjson's "lookup4" check
(Keiser and Lemire, 2021), with NEON on AArch64 and, on x86-64,
with SSSE3. SSSE3 is not part of baseline x86-64, so the check is
compiled for SSSE3 with a function attribute and used only where
CPUID reports it, which all x86-64 CPUs since about 2011 do; the
answer is cached in a statically initialized atomic, so there is
no guard of a local static and no global constructor. The same
input is accepted either way. JSON_VIEW_NO_SIMD selects the
portable code.

On x86-64, string runs are now checked vector-first: one SSE2
compare from the first byte finds the end of most keys and short
values, instead of a branch per byte for the first 8-16 bytes.
AArch64 keeps the byte-wise steps, where a NEON mask costs more and
the branches predict well. Entering an object or array no longer
stalls: open() stores the parent's frame field by field instead of
building it on the stack and reading it back with wider loads,
which waited for the narrower stores to retire.

Objects with 128 members or more get an open-addressing hash table
built when the object closes, so operator[], at(), find(),
contains(), count(), value(), and JSON pointers take constant time
on average in such objects; of duplicate keys, the first is kept,
as for the linear search. The idea comes from Boost.JSON.

simdjson is credited in simd.hpp's SPDX block, the README, and
license.md.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 10:48:07 +02:00
Niels Lohmann d26cbef976 Add dump() and comparisons to json_view
Add basic_json_view::dump() and the comparison operators, and read
floats from the parser's digit layout instead of rescanning the
token.

dump(indent, indent_char, ensure_ascii, number_format) writes a
value the way ordered_json::parse(text).dump() writes it for the
same arguments: members in document order, all of them should a
key occur more than once; strings escaped by the same rules, using
the library's scanning kernels; floats written with the library's
to_chars conversion, so the output equals basic_json's byte for
byte; integers copied from the source, where they are already
canonical, except -0, which parse() reads as 0. There is no
error_handler argument, because the view only holds valid UTF-8.
number_format::source copies numbers exactly as they appear in the
source (e.g. "1.50", "1E2", "-0"), which basic_json cannot provide.
operator<< takes the indentation from the stream width, as for
basic_json. The writer walks iteratively, so nesting depth is
limited by memory only.

operator== and operator!= compare two views, or a view and a
basic_json value in either order, by the rules basic_json's
operator== uses: numbers compare by value across their types,
objects compare by their members with duplicate keys resolved as
parse() resolves them, member order matters only where the object
type keeps one, and discarded views compare as discarded basic_json
values do, including under JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON.
Nothing is materialized except single scalars.

While parsing, the view now records where the integer digits, the
fraction digits, and the exponent of a float token are, so floats
and doubles with at most 19 digits are read from that layout with
the library's decimal_to_float() instead of rescanning the token.
Both round correctly, so the values are those of parse(). get<double>(),
materialize(), dump(), and the comparisons all use it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 10:48:06 +02:00
Niels Lohmann 0adb7783a4 Add element access, iteration, values, and JSON pointers to json_view
Give basic_json_view the read-only access functions of basic_json:
operator[] and at() with keys and indices, front()/back(), find(),
contains(), count(), begin()/end() and cbegin()/cend(), items()
with structured bindings from C++17 on, and type_name().

Exceptions have the ids and messages of the const functions of
basic_json. Where basic_json has undefined behavior the view
answers safely: operator[] with a missing key or an out-of-range
index returns a discarded view, and front()/back() of an empty
container throw invalid_iterator.214. Objects are iterated in
document order, and all members are visited; duplicate-key lookups
find the first member (as yyjson and simdjson do), while parse(),
materialize(), and the map conversions keep the last value, as
parse() does. Keys of up to 16 bytes are compared with two
overlapping loads.

Add value conversions: get<T>()/get_to() for arithmetic types,
bool, nullptr_t, strings (std::basic_string copied,
string_view_t without a copy), BasicJsonType, views, std::vector,
and maps with string keys; get_string() for the string without a
copy; number_token() for the number exactly as written in the
source; value() with keys and JSON pointers; and operator[]/at()/
contains() with JSON pointers. Everything else, including types
with from_json(), goes through materialize() of that subtree.
get<T>() of arithmetic types is inlined down to the conversion, so
reading an integer needs no call.

detail::json_pointer_access exposes a pointer's reference tokens
to code outside basic_json.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 10:48:06 +02:00
Niels Lohmann 4303ba674d Add json_document and json_view: node index, parser, and document
Add json_document and json_view, a read-only, zero-copy index of a
JSON text, as the first public slice of the zero-copy view (#5295).

A parse produces a flat array of 16-byte nodes in document order,
one per value and one per object key. Strings stay in the source
text; escaped strings are decoded into an arena. Integers are
converted while their digits are in the cache; floats keep only
their digit layout and are converted on read. Containers store the
size of their subtree, so a reader can step over one in constant
time. A document makes a handful of allocations, however many
values it has.

The parser accepts exactly what json::parse accepts, with every
combination of ignore_comments and ignore_trailing_commas, with and
without a trailing NUL, and under JSON_STRICT_NUL_HANDLING. It is
portable C++11 and does not depend on byte order.

basic_json_document adds parse, parse_copy, accept, read (reuses a
document's memory), root, is_discarded, source, owns_source,
node_count, memory_usage, and shrink_to_fit. basic_json_view adds
type, the is_* queries, operator bool, size, empty, materialize,
and source_offset. A parse error throws the same exception
basic_json::parse would throw for the same input, message and
position included.

detail::abi_config keeps JSON_STRICT_NUL_HANDLING and
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON readable after json.hpp
undefines them, in the ABI namespace so they always match the
basic_json in use.

A NUL byte that ends a // comment is the end of the input, as in
parse() since #5696.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 10:48:06 +02:00
Niels Lohmann 17842694f6 Write doubles with the shortest digits (Żmij), digits in registers
Write doubles with the conversion of Zmij by Victor Zverovich (MIT),
ported to C++11 (detail/conversions/zmij.hpp). It finds the
shortest decimal that reads back as the same double, and the
closest one if there are several. Grisu2, used until now, is fast
but not always shortest: it sometimes writes a 17th digit where 16
suffice, or a last digit that is not the closest. The layout is
unchanged (1.5, 100.0, 1e+100, -0.0); float keeps Grisu2.

Digits are converted eight at a time with the BCD conversion of
Xiang JunBo, as in Zmij, and written with one byte swap per eight
digits and fixed-size moves instead of per-digit loops. Leading and
trailing zeros are counted from those bytes. to_chars() uses a
local buffer when the caller's is shorter than the 41 bytes this
may write. The powers of ten come from the number-parsing table,
adjusted where it holds values rounded up, and extended with Zmij's
compressed tables beyond 10^308.

write_shortest() converts its 16 digits in one vector register
(SSE2 on x86-64, NEON on 64-bit Arm, both baseline) and inserts the
decimal point inside the register, avoiding a store-forwarding
stall that cost about 25% of the time to write a double. dump()
writes floats and integers straight into the serializer's write
buffer instead of copying them from a member buffer, and small
integers eight digits at a time. read_eight_bytes() and
parse_eight_digits() are marked always-inline, which GCC had been
calling out of line in the number-parsing loops.

Of one million random doubles, about 0.14% are now written with
different digits, always to a value that still reads back as the
same double.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 10:48:06 +02:00
Niels Lohmann 953d74ddcd Speed up the lexer: own float parser, string scan, and \u table
Give the library its own correctly rounded float converter for
binary32 and binary64 (IEEE 754), and speed up the lexer's string
and escape scanning.

The converter splits a number token into sign, significand, and
decimal exponent, then tries Clinger's fast path, then a templated
Eisel-Lemire step, and falls back to an exact big-integer digit
comparison for tokens with more than 19 significant digits whose two
candidate values round differently. This replaces std::from_chars
and strtod/strtof for both formats, so parsed values no longer
depend on the C/C++ library or the current locale. The strtold
fallback kept for other long double formats (x87, binary128) now
also copies a multi-byte decimal point correctly, fixing #5660.
eisel_lemire() and decimal_to_float() are always inlined so callers
keep the whole conversion in their hot loop.

The string-scanning kernels in string_scan.hpp find a stop byte with
the trailing-zero count of the SWAR mask instead of a byte loop, and
scalar_string_bulk_run() validates a run of multi-byte UTF-8
sequences one after another instead of re-searching after each one.

get_codepoint() decodes a contiguous \uXXXX escape with one table
lookup per byte instead of four range-checked get() calls; the
streaming path and all error positions are unchanged.

Adds 508 generated hard float-parsing cases with expected binary32
and binary64 bits, and kernel-comparison tests for the string scans
and the escape table against byte-by-byte references.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 10:48:06 +02:00
Michiel van SlobbeandMichiel van Slobbe 5ecb704f6b Speedup; check for the expected separator before the lexer's token switch (#5592)
* Check for the expected separator before the lexer's token switch

After a key the parser expects ':', after a value usually ','. Test for
that character first instead of going through scan()'s switch, which
compiles to an indirect jump. Any other character takes the old path,
so tokens and error messages are unchanged.

Parsing 6.3% faster with GCC 15.2 and 2.7% with Clang 22.1 (geomean of
the ParseString, ParseFile and ParseIndented benchmarks).

Signed-off-by: Michiel van Slobbe <michiel.van.slobbe@gmail.com>

* Improvement: address PR comments

Signed-off-by: Michiel van Slobbe <michiel.van.slobbe@gmail.com>

* fix: address comments

Signed-off-by: Michiel van Slobbe <michiel.van.slobbe@gmail.com>

* Fix clang-tidy bugprone-signed-char-misuse in scan_expecting

Convert the expected separator through unsigned char before storing it as
char_int_type. The generated code is unchanged.

Signed-off-by: Michiel van Slobbe <michiel.van.slobbe@gmail.com>

* Use raw string literals in the separator comment tests

Signed-off-by: Michiel van Slobbe <michiel.van.slobbe@gmail.com>

---------

Signed-off-by: Michiel van Slobbe <michiel.van.slobbe@gmail.com>
Co-authored-by: Michiel van Slobbe <michiel.van.slobbe@gmail.com>
2026-10-06 08:36:40 +02:00
Niels Lohmann 0490778fc3 Replace retired macOS 14 runner and test all available Xcode versions (#5757)
* Replace retired macOS 14 runner and test all available Xcode versions

GitHub retires the macos-14 image on 2026-11-02 (brownouts from
2026-10-05). Xcode 15 is not available on any remaining hosted
runner, so drop the macos-14 job and its documented compilers.

Also test the Xcode versions that the images provide but CI did not
use (26.1.1-26.3 on macos-15, 26.4.1-26.6 on a new macos-26 job), and
pin GCC 16 explicitly next to gcc:latest.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Document new Xcode and GCC versions in the supported compilers table

Versions taken from the CI logs of this PR (Xcode 26.1.1-26.6) and
from the gcc:16 image (same digest as gcc:16.2.0 and gcc:latest).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 07:46:24 +02:00
0a365865f9 Make basic_json destruction allocation-free and non-recursive (#5762)
* Use the provided allocator in destroy() (#4842)

Uses the provided allocator to allocate the stack used to avoid
recursion in the destroy() implementation used by ~basic_json.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Test that the destructor uses the provided allocator

Adds a regression test for #4842: destroying a nested array or object must allocate its temporary stack through the basic_json allocator, not std::allocator.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix allocation failure during JSON destruction

Signed-off-by: Michael Sam <michaelsam94@users.noreply.github.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make json_value::destroy() non-recursive and allocation-free

destroy() used to flatten a nested array/object into a heap-allocated
std::vector to avoid recursing per nesting level. That vector could
itself throw bad_alloc under memory pressure, and since it now used the
basic_json's own allocator (#4842), a failing allocator supplied by the
caller made this more likely, not less. An exception thrown from inside
~basic_json(), which is noexcept, terminates the program (#5135).

Replace the vector-based stack with a pointer-reversal walk that visits
the tree without recursing per level and without allocating anything:
cur is the array/object currently being emptied, prev is its parent
(or null at the top). A parent's last child slot doubles as storage for
that parent's own parent link while we are below it, so no extra memory
is needed. A child is only ever removed once it is a scalar or an empty
array/object, which neither allocates nor recurses more than one level
deep. take() moves m_data between these locals directly, bypassing
set_parents()/assert_invariant() (the former is O(#children) per call
under JSON_DIAGNOSTICS, which would make the walk quadratic otherwise).

This also removes the std::vector<basic_json, allocator_type> stack
added by #4842, so the extra allocations it introduced disappear along
with it.

Co-authored-by: Michael Sam <9461037+michaelsam94@users.noreply.github.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Test that destroy() performs no allocation, even under memory pressure

Update the #4842 regression test: it used to check that destroying a
nested array/object made at least one allocation through the provided
allocator (the old flattening stack). Now that destroy() does not
allocate at all, assert the opposite: zero allocations, deallocations
only.

Rework the #5135 regression test to use a dedicated failing/counting
allocator instead of overriding the process-wide ::operator new and
::operator delete, which affected every allocation in the whole
unit-regression2 binary rather than just the values under test. Keep
the original small repro as one case, and add deep (100000 levels) and
wide-and-deep nested array/object/ordered_json cases, all destroyed
while every further allocation is made to fail: the destructor must
complete without allocating, without throwing, and without leaking.

Co-authored-by: Michael Sam <9461037+michaelsam94@users.noreply.github.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Refactor destroy() for readability and add edge-case tests

Apply review feedback from Greg Marr on the json_value::destroy()
non-recursive, allocation-free destruction walk (#5135):

- last_child() now uses object->rbegin()->second instead of
  std::prev(object->end())->second; pop_last_child() keeps
  std::prev(end()) since erase() needs a forward iterator.
- is_empty_container() becomes has_no_children(), a switch that
  returns true for every non-container type as well as empty
  array/object, simplifying the "scalar or already-empty child"
  check at the call site. The local variable `last` is renamed to
  `cur_last_ref` for clarity.
- free_container() asserts the array/object is already empty before
  freeing it, and the object branches assert the expected type.
- destroy(value_t t) is now a thin dispatcher to destroy_string(),
  destroy_binary(), and destroy_container(t), each handling its own
  "not initialized" check and sharing the simple cases first in the
  switch.
- destroy_container() moves the top-level container into the local
  stand-in via a plain swap of the json_value union, instead of a
  manual copy plus clearing array/object by hand.
- The "cur has no children and there is no parent" case now frees
  cur and returns immediately, so the main loop is a plain
  while (true) with no trailing code after it.

Also adds edge-case tests for both json and ordered_json (mixes of
empty/non-empty arrays and objects, container children in first/last
position, single-element chains, top-level empty containers, and
destruction via erase()/assignment), plus a mixed-tree case in the
"destructor performs no allocation" test.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make the destroy() walk helpers private

They modify basic_json internals without maintaining its invariants and
are only meant for destroy_container(), so they no longer need to be
reachable from the rest of basic_json.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Michael Sam <michaelsam94@users.noreply.github.com>
Co-authored-by: Vesko Karaganev <vesko.karaganev@gmail.com>
Co-authored-by: Michael Sam <michaelsam94@users.noreply.github.com>
Co-authored-by: Michael Sam <9461037+michaelsam94@users.noreply.github.com>
2026-10-06 07:40:39 +02:00
5379e04ce4 Throw type_error.321 when serializing discarded values to binary formats (#5761)
* Throw type_error.321 when serializing discarded values to binary formats

The CBOR, MessagePack, UBJSON, BJData, and BSON writers silently
skipped the payload of a value_t::discarded value nested in an array
or object, while still writing its slot in the element/member count
(and, for BSON, its entry header), producing a binary document whose
declared size does not match what was actually written.

Throw type_error.321 instead, for a discarded value anywhere in the
tree, including at the top level.

Rewritten from the original PR against the current (non-recursive
option aside) binary_writer.hpp, which has changed substantially since
this was first proposed; the out_of_range.412 MessagePack size check
and unrelated test reformatting from that PR are dropped as out of
scope here.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Document and test type_error.321 for discarded binary values

Add docs for the new exception (home/exceptions.md and the Exceptions
sections of to_cbor/to_msgpack/to_ubjson/to_bjdata/to_bson) and test
coverage for a discarded value nested in an array or object, nested
deeper, and (for UBJSON/BJData) inside an optimized same-type array,
for each of CBOR, MessagePack, UBJSON, BJData, and BSON. Adjust the
three pre-existing "discarded" tests that asserted the old silent
behavior (empty/short output) to expect type_error.321 instead.

Co-authored-by: ameliabarnabyhub <312084480+ameliabarnabyhub@users.noreply.github.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: ameliabarnabyhub <ameliabarnabyhub@users.noreply.github.com>
Co-authored-by: ameliabarnabyhub <312084480+ameliabarnabyhub@users.noreply.github.com>
2026-10-06 07:33:34 +02:00
Niels Lohmann 8a142bc436 Fix CI on develop after #5600, #5607, and #5755 (#5764)
* Fix CI on develop after #5600, #5607, and #5755

- binary_reader: cast the result of -1 - number back to number_integer_t,
  because a number_integer_t narrower than int is promoted to int, which
  GCC's -Warith-conversion rejects (ci_test_gcc, ci_test_standards_gcc)
- JSON_DELETE_DEPRECATED_FUNCTIONS: declare the deleted stream operators
  as function templates at namespace scope, because GCC < 5 rejects deleted
  friend functions ("can't initialize friend function") and Clang 7-9 report
  a redefinition when a class template has a deleted friend function
- docs: give the examples of JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS "Example:"
  titles and add the page to the docset (style_check)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Ignore libstdc++'s <format> sign change in the sanitizer job

libstdc++ 14's <format> initializes a size_t parameter with -1 (GCC bug
119429), so every std::format call fails ci_test_clang_sanitizer under
-fsanitize=integer (test-std-format_cpp20). Exclude only the
implicit-integer-sign-change check and only that header via
-fsanitize-ignorelist.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the AppVeyor (MSVC 2015-2019) build

- binary_reader: emit_signed/emit_unsigned pass integers that do not fit
  the number types to emit_float as long double, because MSVC's <cmath>
  has no integer overloads of std::isfinite (C2668 'fpclassify'), from #5607
- scalar comparisons: the friend operators take the JSON type for their
  noexcept from their parameter, because MSVC 2015/2017 take basic_json as
  the class template there (C3203) and MSVC 2019 16.0 does not see member
  types or template parameters, from #5751
- unit-conversions2: skip the !is_nothrow_constructible static_assert for
  std::optional on MSVC 2017, which evaluates the conditional noexcept as
  true (C2607), from #5754

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Re-amalgamate

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Avoid MSVC 2015's C4800 for enums with underlying type bool

MSVC 2015 warns about any conversion to bool (C4800), even with an
explicit cast, so the enum conversions from #5754 (#5671) failed the
AppVeyor build with /WX. Convert to bool by comparing with zero via the
new detail::bool_aware_static_cast, and keep doctest from printing the
enum in the test.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Skip the #5650 allocator test on MSVC 2015 debug builds

MSVC 2015's debug STL constructs the containers' debug proxies through
the allocator in noexcept constructors, so countdown_allocator's failing
construction crashes test-allocator (SIGSEGV) instead of throwing
std::bad_alloc. Use the guard #5585 uses for the same reason.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Skip deleted-function detection checks on MSVC 2015

MSVC 2015 does not treat selecting a deleted function in decltype as a
substitution failure, so the detection traits in
unit-delete_deprecated_functions (#5755) and the integral-key checks in
unit-element_access2 (#5657) report deleted overloads as callable there.
Calling them still fails to compile. Skip those checks for
_MSC_VER < 1910, and use the stream operators for real in the runtime
section, so MSVC 2015 still compiles them with the macro set.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Split the Visual Studio 2017 AppVeyor jobs in two

The VS 2017 jobs hit AppVeyor's 60-minute limit per job while still
compiling the tests (77 of about 108 test targets after 58 minutes).
They pass /std:c++17 for everything anyway, so build only the C++17
variant of each test (JSON_TestStandards=17), and split the unit test
files across two jobs each with the new JSON_TestShard=<index>/<count>
option, which keeps every <count>-th test file starting at <index>.
The extra variants of single test files are built in shard 0 only.

CMAKE_OPTIONS is no longer quoted in appveyor.yml, so that it can hold
more than one option.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix std::terminate when converting std::optional with MSVC 2017

MSVC 2017 evaluates std::is_nothrow_assignable<json&, const T&> as true
even if T's to_json throws, so to_json(json&, const std::optional<T>&)
was noexcept there and the exception from #5642's test called
std::terminate instead of propagating. Make that conversion never
noexcept on MSVC 2017; all other compilers keep the exact condition.
The static_asserts on the condition are skipped for MSVC 2017; the
runtime check that the exception propagates still runs there.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-06 07:22:55 +02:00
Niels Lohmann 23d3b373e1 Add JSON_DELETE_DEPRECATED_FUNCTIONS to delete the deprecated functions (#5755)
* Add JSON_DELETE_DEPRECATED_FUNCTIONS to delete the deprecated functions

Defining JSON_DELETE_DEPRECATED_FUNCTIONS to 1 (or the CMake option
JSON_DeleteDeprecatedFunctions) declares every deprecated function as
deleted instead of deprecated, so that code that is not ready for 4.0.0
no longer compiles. A deleted function still takes part in overload
resolution, so from_*(ptr, len) cannot silently bind len to the strict
parameter of from_*(InputType&&, bool); the roadmap now plans to keep
these overloads deleted in 4.0.0 instead of removing them.

The legacy discarded-value comparison is left to its own macro.

Also update the 4.0 roadmap: add JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
and JSON_DELETE_DEPRECATED_FUNCTIONS to the macro table, add the
from_bjdata/from_bon8 (ptr, len) overloads to the deprecated functions,
document the macro in the migration guide, and fix the docs style check
findings (example titles, missing docset entry for JSON_STRICT_BINARY_UTF8).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Declare each deprecated function once and guard only its body

Instead of repeating every deprecated declaration in an
#if JSON_DELETE_DEPRECATED_FUNCTIONS branch, keep one declaration
(with its deprecation attribute) and switch only between "= delete;"
and the function body. Suggested by @gregmarr in the review.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 22:11:34 +02:00
Niels Lohmann c5a7a4b46d Add deprecated from_bon8/from_bjdata(ptr, len) overloads (#5688)
from_bon8(ptr, len) and from_bjdata(ptr, len) had no overload for a
pointer and a length, unlike from_cbor/from_msgpack/from_ubjson/
from_bson. The call instead bound to from_*(InputType&&, bool strict),
which read ptr as a NUL-terminated C string via strlen and silently
converted len to the strict flag. Data containing a 0x00 byte was cut
off there; data without one was read past the end of the buffer.

Add a deprecated (ptr, len, strict, allow_exceptions) overload for
each function that forwards to (ptr, ptr + len, ...), matching the
existing deprecated overloads of the other four binary readers. Since
neither function ever had this overload, the deprecation is declared
as of version 3.13.0, the next unreleased version, rather than the
version each function was originally added in.

Fixes #5648.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 22:11:34 +02:00
Niels Lohmann 4f69be80d6 Clarify wording in diff documentation (#5759)
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 18:20:20 +02:00
Niels LohmannandMuhammad Amir bin Mohamad Ghazaly 8c1f60a45e Store maps with enum keys as objects (opt-in) (#5600)
* Store maps with enum keys as objects (opt-in)

Maps with enum keys, such as std::map<E, T>, are stored as arrays of
[key, value] pairs, because enums are not convertible to the string type
of object keys - even if NLOHMANN_JSON_SERIALIZE_ENUM maps them to
strings (#4378).

The new JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS macro stores them as objects
instead, converting each key with the enum's to_json. It applies to any
map-like type with enum keys (std::map with any comparator,
std::unordered_map, ...). A key that does not convert to a string throws
type_error.302, and two keys converting to the same string throw the new
type_error.318, rather than losing an entry. The macro changes the output
of inline functions, so it is part of the ABI tag (_ekmo).

Reading needs no macro: std::map and std::unordered_map with enum keys
are now also read from objects, converting each key with the enum's
from_json. That input was rejected before, and arrays of pairs are still
read, so data written either way can be read.

This supersedes #4531, which first proposed storing these maps as
objects.

Co-authored-by: Muhammad Amir bin Mohamad Ghazaly <amirghaz@umich.edu>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Keep multimaps with enum keys as arrays of pairs

With JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, is_enum_keyed_map also matched
std::multimap and std::unordered_multimap. Storing them as objects throws
type_error.318 as soon as a key occurs twice, which is the normal case for
a multimap, so such values could no longer be serialized at all once the
macro was enabled, although they are stored losslessly as arrays of
[key, value] pairs without it.

Exclude maps with non-unique keys from is_enum_keyed_map. They are
detected by insert(value_type) returning an iterator rather than a
pair<iterator, bool>. Map-like types without such an insert() are still
treated as before.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Move the default enum-keyed map tests out of unit-conversions.cpp

The Windows clang 20.1.8 job (MinGW, Debug) failed to link
test-conversions_cpp17 with "relocation truncated to fit:
IMAGE_REL_AMD64_REL32 against .rdata": the object file of
unit-conversions.cpp was already close to the limit, and the new
"maps with enum keys" test case pushed it over. windows.yml asks to keep
these objects small by splitting test files.

Move the test case unchanged into unit-enum_keyed_maps_default.cpp,
with the three enums it needs. It still honors a -D flag for
JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, as before. unit-conversions.cpp
is back to its state on develop.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Build the enum-keyed map test object instead of parsing it

ci_test_diagnostic_positions failed in unit-enum_keyed_maps_default.cpp:
with JSON_DIAGNOSTIC_POSITIONS, a parsed value adds its byte range to
the exception message ("(bytes 0-7) type must be array, but is
object"), so the exact-message checks did not match. Build the object
in memory, like unit-custom-array-type.cpp does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Muhammad Amir bin Mohamad Ghazaly <amirghaz@umich.edu>
2026-10-04 17:54:05 +02:00
Niels Lohmann 6b4b825af2 Handle numbers that do not fit narrow number types in the binary readers (#5607)
* Handle numbers that do not fit narrow number types in the binary readers

With custom number types narrower than the values in a binary document,
for example basic_json<..., std::int32_t, std::uint32_t, float>, every
binary reader (CBOR, MessagePack, UBJSON, BJData, BSON, BON8) passed the
decoded number to the SAX interface with an implicit conversion: the
integer 5000000000 silently became 705032704, and a finite double such as
1e300 became infinity. The lexer handles the same values in JSON text: an
integer that fits neither integer type is stored as number_float_t, and a
finite number that overflows number_float_t is rejected with
out_of_range.406.

Pass every number read from binary input through three helpers that
apply the lexer's rules:
- emit_signed(): number_integer_t, else number_unsigned_t for a
  non-negative value, else number_float_t
- emit_unsigned(): number_unsigned_t, else number_float_t
- emit_float(): out_of_range.406 if a finite value overflows
  number_float_t; infinity and NaN are passed on

For consistency, a CBOR negative integer below the range of
number_integer_t is now stored as number_float_t, like a too small
integer in JSON text, instead of being rejected with parse_error.112.
With the default number types, this is the only change in behavior.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix MSVC and clang 3.5 in the narrow number type test

MSVC types 3000000000 and 5000000000 as unsigned long, so
json(-3000000000) triggered C4146 (unary minus on an unsigned type),
which /WX turns into an error. Use LL literals, as elsewhere in the
tests.

clang 3.5 cannot convert the lambdas in the braced initializer of the
format table to function pointers. Use named functions instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Check integer-to-float fallbacks for overflow in the binary readers

emit_signed, emit_unsigned, and the CBOR negative integer fallback now
pass their number_float_t fallback through emit_float, so a value that
overflows number_float_t is rejected with out_of_range.406 like a
floating-point value, instead of silently becoming infinity. This only
matters for a number_float_t that cannot represent 2^64, such as a
half-precision type. The CBOR value -1 - n is computed as long double so
that emit_float sees a finite value.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Use the input_format member instead of passing the format to binary_reader helpers

The helpers (get_number, get_to, get_string, get_binary, get_bytes,
emit_signed, emit_unsigned, emit_float, unexpect_eof, exception_message)
are members of binary_reader, which already stores the format it was
constructed with, so the parameter was redundant.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:53:29 +02:00
91 changed files with 11620 additions and 4024 deletions

No files matched your search

+18 -3
View File
@@ -16,6 +16,9 @@ only_commits:
environment:
matrix:
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
# only build the C++17 variant of each test, split into two jobs each to
# stay below AppVeyor's 60-minute limit per job.
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
configuration: Debug
platform: x86
@@ -34,7 +37,13 @@ environment:
configuration: Release
platform: x86
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
CMAKE_OPTIONS: ""
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
GENERATOR: Visual Studio 15 2017
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
configuration: Release
platform: x86
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2"
GENERATOR: Visual Studio 15 2017
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
@@ -55,7 +64,13 @@ environment:
configuration: Release
platform: x64
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
CMAKE_OPTIONS: ""
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
GENERATOR: Visual Studio 15 2017
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
configuration: Release
platform: x64
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2"
GENERATOR: Visual Studio 15 2017
init:
@@ -66,7 +81,7 @@ install:
- if "%platform%"=="x86" set GENERATOR_PLATFORM=Win32
before_build:
- cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On "%CMAKE_OPTIONS%"
- cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On %CMAKE_OPTIONS%
build_script:
- cmake --build . --config "%configuration%" --parallel 2
-1
View File
@@ -52,7 +52,6 @@ labels:
- "single_include/nlohmann/json_view\\.hpp"
- "tests/src/unit-json_view.*"
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
- "tests/benchmarks/json_view/.*"
- "tools/amalgamate/config_json_view\\.json"
- "docs/mkdocs/docs/features/json_view\\.md"
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
@@ -1,78 +0,0 @@
name: "json_view benchmarks"
# On demand only: runs the comparison of json_view with yyjson, simdjson, and
# Boost.JSON (tests/benchmarks/json_view/compare.py) on GitHub-hosted runners,
# for numbers from x86-64 and AArch64 Linux. It runs when started by hand, or
# when a pull request gets the label "benchmark" (on both architectures, with
# GCC and the default settings). Shared runners are noisy: the results show
# where json_view stands, but published numbers need a quiet machine (see
# tests/benchmarks/json_view/README.md).
on:
pull_request:
types: [labeled]
workflow_dispatch:
inputs:
runner:
description: "Runner image"
type: choice
options:
- ubuntu-24.04
- ubuntu-24.04-arm
default: ubuntu-24.04
compiler:
description: "Compiler"
type: choice
options:
- g++
- clang++
default: g++
native:
description: "Compile for the runner's CPU (-march=native)"
type: boolean
default: false
rounds:
description: "Rounds of bench_view"
type: number
default: 30
permissions:
contents: read
jobs:
compare:
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'benchmark'
strategy:
matrix:
runner: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', inputs.runner) || '["ubuntu-24.04", "ubuntu-24.04-arm"]') }}
runs-on: ${{ matrix.runner }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Download test data
run: |
cmake -S . -B build -DJSON_BuildTests=On
cmake --build build --target download_test_data
- name: Run the comparison
env:
CXX: ${{ inputs.compiler || 'g++' }}
CC: ${{ inputs.compiler == 'clang++' && 'clang' || 'gcc' }}
ROUNDS: ${{ inputs.rounds || 30 }}
NATIVE: ${{ inputs.native && '--native' || '' }}
run: python3 tests/benchmarks/json_view/compare.py --data build/test_files --download --rounds "$ROUNDS" $NATIVE
- name: Summary
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
path: tests/benchmarks/json_view/results/
+6 -6
View File
@@ -17,11 +17,11 @@ permissions:
contents: read
jobs:
macos-14:
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
macos-15:
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
strategy:
matrix:
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
@@ -36,11 +36,11 @@ jobs:
- name: Test
run: cd build ; ctest -j 10 --output-on-failure
macos-15:
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
macos-26:
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
strategy:
matrix:
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
xcode: ['26.4.1', '26.5', '26.6']
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
+2 -2
View File
@@ -107,7 +107,7 @@ jobs:
container: ubuntu:24.04
strategy:
matrix:
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_delete_deprecated_functions, ci_test_no_thread_local]
steps:
- name: Install build-essential
run: apt-get update ; apt-get install -y build-essential unzip wget git
@@ -209,7 +209,7 @@ jobs:
strategy:
matrix:
# older GCC docker images (4, 5, 6) fail to check out code
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
container: gcc:${{ matrix.compiler }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+6
View File
@@ -62,6 +62,7 @@ option(JSON_MultipleHeaders "Use non-amalgamated version of the l
option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF)
option(JSON_StrictNulHandling "Build with strict NUL-byte handling enabled." OFF)
option(JSON_StrictBinaryUTF8 "Build with UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers enabled." OFF)
option(JSON_DeleteDeprecatedFunctions "Delete the deprecated functions instead of only deprecating them." OFF)
if (JSON_CI)
include(ci)
@@ -123,6 +124,10 @@ if (JSON_StrictBinaryUTF8)
message(STATUS "Strict UTF-8 checks in binary writers enabled (JSON_STRICT_BINARY_UTF8=1)")
endif()
if (JSON_DeleteDeprecatedFunctions)
message(STATUS "Deprecated functions are deleted (JSON_DELETE_DEPRECATED_FUNCTIONS=1)")
endif()
if (JSON_Diagnostic_Positions)
message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)")
endif()
@@ -159,6 +164,7 @@ target_compile_definitions(
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
$<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1>
$<$<BOOL:${JSON_StrictBinaryUTF8}>:JSON_STRICT_BINARY_UTF8=1>
$<$<BOOL:${JSON_DeleteDeprecatedFunctions}>:JSON_DELETE_DEPRECATED_FUNCTIONS=1>
)
target_include_directories(
+16 -2
View File
@@ -249,6 +249,20 @@ add_custom_target(ci_test_strict_nul_handling
COMMENT "Compile and test with strict NUL-byte handling enabled"
)
###############################################################################
# Delete the deprecated functions.
###############################################################################
add_custom_target(ci_test_delete_deprecated_functions
COMMAND ${CMAKE_COMMAND}
-DCMAKE_BUILD_TYPE=Debug -GNinja
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DeleteDeprecatedFunctions=ON
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_delete_deprecated_functions
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_delete_deprecated_functions
COMMAND cd ${PROJECT_BINARY_DIR}/build_delete_deprecated_functions && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
COMMENT "Compile and test with the deprecated functions deleted"
)
###############################################################################
# Disable global UDLs.
###############################################################################
@@ -362,7 +376,7 @@ add_custom_target(ci_test_coverage
# Sanitizers.
###############################################################################
set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base")
set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base -fsanitize-ignorelist=${PROJECT_SOURCE_DIR}/cmake/clang_sanitizer_ignorelist.txt")
add_custom_target(ci_test_clang_sanitizer
COMMAND CXX=${CLANG_TOOL} CXXFLAGS=${CLANG_CXX_FLAGS_SANITIZER} ${CMAKE_COMMAND}
@@ -708,7 +722,7 @@ ci_get_cmake(4.0.0 CMAKE_4_0_0_BINARY)
# the tests require CMake 3.13 or later, so they are excluded for CMake 3.5.0
set(JSON_CMAKE_FLAGS_3_5_0 JSON_Diagnostics JSON_Diagnostic_Positions JSON_GlobalUDLs JSON_ImplicitConversions JSON_DisableEnumSerialization
JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind
JSON_StrictNulHandling JSON_StrictBinaryUTF8)
JSON_StrictNulHandling JSON_StrictBinaryUTF8 JSON_DeleteDeprecatedFunctions)
set(JSON_CMAKE_FLAGS_3_31_6 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
set(JSON_CMAKE_FLAGS_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
+8
View File
@@ -0,0 +1,8 @@
# Sanitizer ignore list for ci_test_clang_sanitizer (-fsanitize-ignorelist).
#
# libstdc++ 14's <format> declares `_Scanner(basic_string_view<_CharT>, size_t __nargs = -1)`, so every std::format
# call converts -1 to size_t, which -fsanitize=integer reports as implicit-integer-sign-change. This is
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119429, not a bug in this library. Only that check and only <format> are
# excluded, so implicit sign changes in the library and the tests are still reported.
[implicit-integer-sign-change]
src:*/include/c++/*/format
+2
View File
@@ -291,6 +291,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('Supported Macros', 'Guide', '
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'api/macros/json_assert/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_BRACE_INIT_COPY_SEMANTICS', 'Macro', 'api/macros/json_brace_init_copy_semantics/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'api/macros/json_throw_user/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DELETE_DEPRECATED_FUNCTIONS', 'Macro', 'api/macros/json_delete_deprecated_functions/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
@@ -321,6 +322,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS', 'Macro', 'api/macros/json_use_objects_for_enum_keyed_maps/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html');
+2 -2
View File
@@ -8,7 +8,7 @@ static basic_json diff(const basic_json& source,
Creates a [JSON Patch](http://jsonpatch.com) so that value `source` can be changed into the value `target` by calling
[`patch`](patch.md) function.
For two JSON values `source` and `target`, the following code yields always `#!cpp true`:
For two JSON values `source` and `target`, the following code always yields `#!cpp true`:
```cpp
source.patch(diff(source, target)) == target;
```
@@ -27,7 +27,7 @@ a JSON patch to convert the `source` to `target`
## Exception safety
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
Strong guarantee: `source` and `target` are never modified.
## Complexity
@@ -120,3 +120,12 @@ Linear in the size of the input.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
- Added `error_handler` parameter in version 3.13.0.
!!! warning "Deprecation"
- Overload (2) replaces calls to `from_bjdata` with a pointer and a length as first two parameters, which has been
deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like
`#!cpp from_bjdata(ptr, len, ...);` with `#!cpp from_bjdata(ptr, ptr+len, ...);`.
You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated
function.
@@ -106,3 +106,12 @@ Linear in the size of the input.
## Version history
- Added in version 3.13.0.
!!! warning "Deprecation"
- Overload (2) replaces calls to `from_bon8` with a pointer and a length as first two parameters, which has been
deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like
`#!cpp from_bon8(ptr, len, ...);` with `#!cpp from_bon8(ptr, ptr+len, ...);`.
You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated
function.
@@ -56,6 +56,10 @@ This implementation does exactly follow this approach, as it uses double precisi
smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally
and be serialized to `null`.
During deserialization (from JSON text or any of the binary formats), a finite number that does not fit into
`number_float_t` is rejected with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406), for
example a double-precision number in a binary format when `number_float_t` is `#!cpp float`.
### Storage
Floating-point number values are stored directly inside a `basic_json` type.
@@ -47,8 +47,9 @@ With the default values for `NumberIntegerType` (`std::int64_t`), the default va
When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and
the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of
range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
range will yield over/underflow when used in a constructor. During deserialization (from JSON text or any of the binary
formats), too large or small integer numbers will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md)
or [`number_float_t`](number_float_t.md).
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
@@ -48,8 +48,9 @@ With the default values for `NumberUnsignedType` (`std::uint64_t`), the default
When the default type is used, the maximal integer number that can be stored is `18446744073709551615` (UINT64_MAX) and
the minimal integer number that can be stored is `0`. Integer numbers that are out of range will yield over/underflow
when used in a constructor. During deserialization, too large or small integer numbers will automatically be stored
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
when used in a constructor. During deserialization (from JSON text or any of the binary formats), too large or small
integer numbers will automatically be stored as [`number_integer_t`](number_integer_t.md) or
[`number_float_t`](number_float_t.md).
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
+5 -1
View File
@@ -68,6 +68,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
not valid UTF-8 and `error_handler` is `strict` (the default only if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to BJData"`
## Complexity
@@ -119,4 +121,6 @@ Linear in the size of the JSON value `j`.
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
that is not valid UTF-8 unchanged, as before; `strict` (the default if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
array or object was silently skipped, producing invalid BJData.
@@ -58,6 +58,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
not valid UTF-8 and `error_handler` is `strict` (the default only if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded
(the top-level value itself is covered by `type_error.317` above, since it must be an object); example:
`"cannot serialize discarded value to BSON"`
## Complexity
@@ -110,6 +113,8 @@ pass before anything is written.
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently
skipped, producing a document whose declared size did not match what was actually written.
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
that is not valid UTF-8 unchanged, as before; `strict` (the default if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
@@ -49,6 +49,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
not valid UTF-8 and `error_handler` is `strict` (the default only if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to CBOR"`
## Complexity
@@ -86,3 +88,5 @@ Linear in the size of the JSON value `j`.
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
that is not valid UTF-8 unchanged, as before; `strict` (the default if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
array or object was silently skipped, producing invalid CBOR.
@@ -54,6 +54,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
not valid UTF-8 and `error_handler` is `strict`
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to MessagePack"`
## Complexity
@@ -108,3 +110,5 @@ Linear in the size of the JSON value `j`.
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
`number_unsigned_t`.
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
array or object was silently skipped, producing invalid MessagePack.
@@ -61,6 +61,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
not valid UTF-8 and `error_handler` is `strict` (the default only if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to UBJSON"`
## Complexity
@@ -112,3 +114,5 @@ Linear in the size of the JSON value `j`.
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
that is not valid UTF-8 unchanged, as before; `strict` (the default if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
array or object was silently skipped, producing invalid UBJSON.
+7
View File
@@ -60,6 +60,13 @@ header. See also the [macro overview page](../../features/macros.md).
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
- [**JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS**](json_use_objects_for_enum_keyed_maps.md) - opt in to storing maps with enum
keys as objects
## Deprecated functions
- [**JSON_DELETE_DEPRECATED_FUNCTIONS**](json_delete_deprecated_functions.md) - opt in to deleting the deprecated
functions ahead of their removal in version 4.0.0
## Comparison behavior
@@ -0,0 +1,96 @@
# JSON_DELETE_DEPRECATED_FUNCTIONS
```cpp
#define JSON_DELETE_DEPRECATED_FUNCTIONS /* value */
```
When defined to `1`, all [deprecated functions](../../community/roadmap.md#removal-of-deprecated-functions) of the
library are declared as deleted (`= delete`) instead of only being marked as deprecated. Code that still calls one of
them no longer compiles. This way, you can find all calls that need to be replaced before version 4.0.0 removes these
functions; the [migration guide](../../integration/migration_guide.md#replace-deprecated-functions) describes how.
A deleted function, unlike a removed one, still takes part in overload resolution. A call that would select it
therefore fails to compile instead of silently selecting another overload. This matters for the deprecated
`from_*(ptr, len)` overloads of [`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md),
[`from_ubjson`](../basic_json/from_ubjson.md), [`from_bjdata`](../basic_json/from_bjdata.md),
[`from_bon8`](../basic_json/from_bon8.md), and [`from_bson`](../basic_json/from_bson.md): without them, a call like
`from_cbor(ptr, len)` would compile, read `ptr` as a NUL-terminated string, and convert `len` to the `strict` parameter.
The macro does not affect the deprecated legacy comparison of discarded values, which is controlled by
[`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](json_use_legacy_discarded_value_comparison.md).
## Default definition
The default value is `0` (disabled, the deprecated functions can still be called, and the compiler warns about it).
```cpp
#define JSON_DELETE_DEPRECATED_FUNCTIONS 0
```
## Notes
!!! info "CMake option"
The macro can also be set with the CMake option
[`JSON_DeleteDeprecatedFunctions`](../../integration/cmake.md#json_deletedeprecatedfunctions) (`OFF` by default).
!!! warning "Opt-in only"
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no
effect. Define it for the whole project to avoid different declarations of the same class in different
translation units.
!!! note "ABI compatibility"
The macro only turns calls that compile into calls that do not; it does not change the layout or the behavior of
any type. Its value is therefore not encoded in the [namespace](../../features/namespace.md).
## Examples
??? example "Example: default behavior (macro not defined)"
Without the macro, the deprecated overload is called, and the compiler warns about it:
```cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
const std::vector<std::uint8_t> v = {0x82, 0x01, 0x02};
auto j = json::from_cbor(v.data(), v.size());
// warning: 'from_cbor' is deprecated: Since 3.8.0; use from_cbor(ptr, ptr + len)
}
```
??? example "Example: deleted deprecated functions (macro defined to 1)"
With the macro, the call does not compile:
```cpp
#define JSON_DELETE_DEPRECATED_FUNCTIONS 1
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
const std::vector<std::uint8_t> v = {0x82, 0x01, 0x02};
auto j = json::from_cbor(v.data(), v.size());
// error: call to deleted function 'from_cbor'
}
```
## See also
- [Roadmap: removal of deprecated functions](../../community/roadmap.md#removal-of-deprecated-functions) - the
deprecated functions and the version they were deprecated in
- [Migration guide: replace deprecated functions](../../integration/migration_guide.md#replace-deprecated-functions) -
how to replace each deprecated function
## Version history
- Added in version 3.13.0.
- Planned to be removed in version 4.0.0, which removes the deprecated functions. The deprecated `from_*(ptr, len)`
overloads stay deleted in version 4.0.0.
@@ -112,3 +112,4 @@ The default value is `0` (disabled — existing behavior is preserved).
## Version history
- Added in version 3.13.0.
- Planned to become the default (with the macro removed) in version 4.0.0.
@@ -44,7 +44,7 @@ By default, implicit conversions are enabled.
## Examples
??? example "Example: implicit conversion"
??? example "Example: implicit and explicit conversions"
This is an example for an implicit conversion:
@@ -0,0 +1,139 @@
# JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS /* value */
```
When defined to `1`, maps whose keys are enums (such as `std::map<E, T>` or `std::unordered_map<E, T>`) are stored as
JSON objects, using the enum's own conversion for the keys. By default, they are stored as arrays of `[key, value]`
pairs.
## Default definition
The default value is `0` (disabled — existing behavior is preserved).
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
```
## Notes
!!! note "Background"
JSON object keys are strings, so a map is only stored as an object if its keys can be converted to a string type.
Enums are not, even if [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps them to strings, so a
map with enum keys becomes an array of `[key, value]` pairs:
```json
[["stopped", "aa"], ["completed", "bb"]]
```
With this macro, the same map becomes an object
(see [#4378](https://github.com/nlohmann/json/issues/4378)):
```json
{"completed": "bb", "stopped": "aa"}
```
!!! note "Maps with non-unique keys"
Maps that allow duplicate keys, such as `std::multimap<E, T>` or `std::unordered_multimap<E, T>`, are not affected
by the macro and are still stored as arrays of `[key, value]` pairs, as an object cannot hold duplicate keys.
!!! note "Reading"
Reading is not affected by the macro: a map with enum keys can always be read from both an array of pairs and an
object. For the latter, each key is converted to the enum with its `from_json` function, e.g., the one defined by
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md). Data written without the macro can therefore
still be read after enabling it.
!!! warning "Keys must serialize to distinct strings"
Each key is converted with the enum's `to_json` function. If a key is not converted to a string (for instance, an
enum without [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md), which is stored as an integer, or an
enumerator mapped to `nullptr`), [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) is thrown.
If two keys are converted to the same string (for instance, because
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps an unlisted enumerator to the first entry),
[`type_error.318`](../../home/exceptions.md#jsonexceptiontype_error318) is thrown. In both cases, the target value
is not changed.
!!! warning "Opt-in only"
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
!!! note "ABI compatibility"
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_ekmo`), resulting in
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
## Examples
??? example "Example: default behavior (macro not defined)"
Without the macro, a map with enum keys is stored as an array of pairs:
```cpp
#include <map>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
int main()
{
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// j is [["stopped","aa"],["completed","bb"]]
}
```
??? example "Example: objects for enum-keyed maps (macro defined to 1)"
With the macro, the same map is stored as an object:
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 1
#include <map>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
int main()
{
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// j is {"completed":"bb","stopped":"aa"}
auto m2 = j.get<std::map<TaskState, std::string>>();
// m2 == m
}
```
## See also
- [Specializing enum conversion](../../features/enum_conversion.md)
- [**NLOHMANN_JSON_SERIALIZE_ENUM**](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum
- [**NLOHMANN_JSON_SERIALIZE_ENUM_STRICT**](nlohmann_json_serialize_enum_strict.md) - serialize/deserialize an enum with
exceptions
## Version history
- Added in version 3.13.0.
@@ -41,6 +41,9 @@ inline void from_json(const BasicJsonType& j, type& e);
conversion. Select this default pair carefully. See example 1 below.
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
list will be returned when converting to or from JSON. See example 2 below.
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
with the converted keys. Such maps can be read from both forms.
## Examples
@@ -80,6 +83,7 @@ inline void from_json(const BasicJsonType& j, type& e);
- [Specializing enum conversion](../../features/enum_conversion.md)
- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md)
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
## Version history
@@ -44,6 +44,9 @@ inline void from_json(const BasicJsonType& j, type& e);
`"enum value out of range for <type>"`.
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
list will be returned when converting to or from JSON. See example 2 below.
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
with the converted keys. Such maps can be read from both forms.
## Examples
@@ -99,6 +102,7 @@ inline void from_json(const BasicJsonType& j, type& e);
- [Specializing enum conversion](../../features/enum_conversion.md)
- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md)
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
## Version history
@@ -21,17 +21,18 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
| Compiler | Architecture | Operating System | CI |
|----------------------------------------------|--------------|-----------------------------------|-----------|
| AppleClang 15.0.0.15000040; Xcode 15.0.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 15.0.0.15000100; Xcode 15.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 15.0.0.15000100; Xcode 15.2 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 15.0.0.15000309; Xcode 15.3 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 15.0.0.15000309; Xcode 15.4 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000404; Xcode 26.1.1 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000603; Xcode 26.2 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000604; Xcode 26.3 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
| AppleClang 21.0.0.21000099; Xcode 26.4.1 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
| AppleClang 21.0.0.21000101; Xcode 26.5 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
| AppleClang 21.0.0.21000101; Xcode 26.6 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
@@ -89,7 +90,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 16.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 16.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
+12 -2
View File
@@ -64,6 +64,8 @@ The following macros guard changes that are planned to become the default in ver
| [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) | `0` | `1`: reading from a stream does not consume the character after a number | – | 3.13.0 |
| [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) | `0` | `1`: a NUL byte in the input is a parse error instead of the end of input | [`JSON_StrictNulHandling`](../integration/cmake.md#json_strictnulhandling) | 3.13.0 |
| [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) | `0` | `1`: `to_cbor`, `to_ubjson`, `to_bjdata`, and `to_bson` throw for strings that are not valid UTF-8 by default | [`JSON_StrictBinaryUTF8`](../integration/cmake.md#json_strictbinaryutf8) | 3.13.0 |
| [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) | `0` | `1`: a `basic_json` value can no longer be created from a one-element tuple of a reference to it, such as `std::forward_as_tuple(j)`| [`JSON_DisableTupleReferenceConversion`](../integration/cmake.md#json_disabletuplereferenceconversion) | 3.13.0 |
| [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) | `0` | removed: the deprecated functions are removed (see below); the `from_*(ptr, len)` overloads stay deleted | [`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) | 3.13.0 |
For example, the following makes a 3.x release behave like version 4.0 with respect to these changes:
@@ -75,6 +77,8 @@ For example, the following makes a 3.x release behave like version 4.0 with resp
#define JSON_PRECISE_STREAM_POSITION 1
#define JSON_STRICT_NUL_HANDLING 1
#define JSON_STRICT_BINARY_UTF8 1
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
#define JSON_DELETE_DEPRECATED_FUNCTIONS 1
#include <nlohmann/json.hpp>
```
@@ -84,8 +88,13 @@ way to achieve this.
### Removal of deprecated functions
Version 4.0 will remove all deprecated functions. Compiling with deprecation warnings enabled shows which of them your
code still uses. The [migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to
replace each of them.
code still uses. Defining [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) to
`1` turns these warnings into errors, as the deprecated functions are then deleted. The
[migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to replace each of them.
The `from_*` overloads taking a pointer and a length are not removed in version 4.0, but stay deleted. Without them, a
call like `from_cbor(ptr, len)` would still compile: it would read `ptr` as a NUL-terminated string and convert `len`
to the `strict` parameter.
| Deprecated | Since | Migration |
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------------------------------------------------------------------------------|
@@ -97,6 +106,7 @@ replace each of them.
| [`json_pointer::operator string_t`](../api/json_pointer/operator_string_t.md) | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
| [`json_pointer`](../api/json_pointer/index.md) with a `basic_json` type as template argument, and the overloads of `value`, `contains`, `operator[]`, and `at` accepting such a pointer | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
| Comparing a [`json_pointer`](../api/json_pointer/index.md) with a string via [`operator==`](../api/json_pointer/operator_eq.md) or [`operator!=`](../api/json_pointer/operator_ne.md) | 3.11.2 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
| [`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) with `(ptr, len)` | 3.13.0 | [Parsing](../integration/migration_guide.md#parsing) |
The deprecated legacy comparison of discarded values is controlled by a macro and therefore listed in the table above.
@@ -168,9 +168,9 @@ The library maps CBOR types to JSON value types as follows:
!!! warning "Negative integer overflow"
CBOR negative integers (major type 1) are decoded as `-1 - n`. If the encoded magnitude `n` is too large for the
result to fit into `number_integer_t` (`std::int64_t` by default), parsing fails with a
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) exception rather than overflowing
silently.
result to fit into `number_integer_t` (`std::int64_t` by default), the result is stored as `number_float_t`, like
a too small integer in JSON text. For example, `-18446744073709551616` (`0x3B` followed by eight `0xFF` bytes) is
stored as `-1.8446744073709552e+19`.
!!! warning "Object keys"
@@ -58,6 +58,23 @@ assert(jPi.get<TaskState>() == TS_INVALID );
--8<-- "examples/nlohmann_json_serialize_enum.output"
```
## Maps with enum keys
By default, maps with enum keys, such as `std::map<TaskState, std::string>`, are stored as arrays of `[key, value]`
pairs, because JSON object keys must be strings. Define
[`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) before including the
library to store them as objects, with the keys converted by the enum's `to_json()` function:
```cpp
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// default: [["stopped","aa"],["completed","bb"]]
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS: {"completed":"bb","stopped":"aa"}
```
Either form can be read back, with or without the macro.
## Notes
Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
+20 -1
View File
@@ -23,6 +23,17 @@ This macro overrides [`#!cpp catch`](https://en.cppreference.com/w/cpp/language/
See [full documentation of `JSON_CATCH_USER(exception)`](../api/macros/json_throw_user.md).
## `JSON_DELETE_DEPRECATED_FUNCTIONS`
When defined to `1`, all deprecated functions are declared as deleted instead of only being marked as deprecated, so
code that still calls them no longer compiles. This way, you can find all calls that need to be replaced before version
4.0.0 removes these functions.
The macro can also be set with the CMake option
[`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) (`OFF` by default).
See [full documentation of `JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md).
## `JSON_DIAGNOSTICS`
This macro enables extended diagnostics for exception messages. Possible values are `1` to enable or `0` to disable
@@ -86,7 +97,8 @@ See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise.
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise. This
is planned to become the default in version 4.0.0.
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
@@ -198,6 +210,13 @@ default.
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
## `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`
When defined to `1`, maps with enum keys (e.g., `std::map<E, T>`) are stored as objects, using the enum's conversion for
the keys, instead of arrays of `[key, value]` pairs. It is switched off (`0`) by default.
See [full documentation of `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md).
## `JSON_USE_SIMDUTF`
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
+2
View File
@@ -21,6 +21,8 @@ The complete default namespace name is derived as follows:
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`.
- [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`.
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero
appends `_ekmo`.
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
below.
+8 -1
View File
@@ -55,7 +55,7 @@ always use the scalar path regardless of this macro.
Parsing always produces SAX events internally; [`parse`](../api/basic_json/parse.md) simply feeds them to a consumer
that builds a complete `basic_json` value tree (a DOM) in memory. For documents too large to comfortably hold as
a DOM, two alternatives avoid building it:
a DOM, three alternatives avoid building it:
- Implement the [SAX interface](parsing/sax_interface.md) directly and pass it to
[`sax_parse`](../api/basic_json/sax_parse.md); only the parts of the input you choose to keep ever become
@@ -64,6 +64,12 @@ a DOM, two alternatives avoid building it:
discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the
unparsed remainder of the input) instead of the whole document -- see the [recipe for streaming a large homogeneous
array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array).
- Parse into a [`json_document`](json_view.md) (`#!cpp <nlohmann/json_view.hpp>`) instead of a `basic_json`. It keeps
the input text and builds a flat index of 16 bytes per value; strings and numbers are not copied, but read from the
text when needed. Read-only [views](../api/basic_json_view/index.md) give the familiar element access, and only the
parts you [`materialize()`](../api/basic_json_view/materialize.md) become `basic_json` values. A document that
borrows the text instead of owning a copy needs the text to outlive it; see
[choosing between `json`, the SAX interface, and `json_view`](json_view.md#choosing-between-json-ordered_json-the-sax-interface-and-json_view).
If the data is naturally record-oriented, consider [JSON Lines](parsing/json_lines.md) instead of one large JSON
document: reading and parsing it line by line with `#!cpp std::getline` means only one line's value is ever in memory
@@ -209,6 +215,7 @@ those headers are then never processed by the compiler at all.
- [Architecture](../home/architecture.md) - how input adapters, the lexer, and the serializer fit together
- [Parsing](parsing/index.md) - the available parsing functions and inputs
- [SAX interface](parsing/sax_interface.md) - parse without building a DOM
- [Zero-copy JSON views](json_view.md) - parse into a flat index of the text and read it without building a DOM
- [Binary formats](binary_formats/index.md) - compact alternatives to JSON text
- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices
- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types
+37 -5
View File
@@ -331,9 +331,6 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1
```
```
[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
```
```
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
```
@@ -616,6 +613,9 @@ During implicit or explicit value conversion, the JSON type must be compatible w
[json.exception.type_error.302] type must be string, but is object
```
This exception is also thrown with [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)
if a key of a map with enum keys is not converted to a string, for instance, because the enum is stored as an integer.
### json.exception.type_error.303
To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`.
@@ -808,6 +808,19 @@ The dynamic type of the object cannot be represented in the requested serializat
Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`
### json.exception.type_error.318
With [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md), a map with enum
keys is stored as an object. This exception is thrown if two of its keys are converted to the same string, so one of the
entries would be lost. This happens, for instance, if [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md)
does not list an enumerator and it is therefore converted like the first listed one.
!!! failure "Example message"
```
[json.exception.type_error.318] duplicate object key 'red'
```
### json.exception.type_error.319
[`basic_json_document::set`](../api/basic_json_document/set.md) and
@@ -846,6 +859,20 @@ exception on a big-endian target, since the image format is little-endian only.
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
### json.exception.type_error.321
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with
[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or
nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON.
!!! failure "Example message"
Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback:
```
[json.exception.type_error.321] cannot serialize discarded value to CBOR
```
## Out of range
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
@@ -918,13 +945,18 @@ The JSON Patch operations 'remove' and 'add' cannot be applied to the root eleme
### json.exception.out_of_range.406
A parsed number could not be stored as without changing it to NaN or INF.
A parsed number could not be stored without changing it to NaN or INF. For the binary formats, this happens when a
finite floating-point number does not fit into [`number_float_t`](../api/basic_json/number_float_t.md), for example a
double-precision number when `number_float_t` is `#!cpp float`.
!!! failure "Example message"
!!! failure "Example messages"
```
number overflow parsing '10E1000'
```
```
[json.exception.out_of_range.406] syntax error while parsing CBOR value: number overflow
```
### json.exception.out_of_range.407
+6
View File
@@ -132,6 +132,12 @@ Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/
Enable CI build targets. The exact targets are used during the several CI steps and are subject to change without notice. This option is `OFF` by default.
### `JSON_DeleteDeprecatedFunctions`
Delete the deprecated functions instead of only deprecating them by defining the macro
[`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md). This option is `OFF` by
default.
### `JSON_Diagnostics`
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
@@ -14,6 +14,13 @@ deprecations are annotated with
[`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which
function to use instead.
!!! tip "Find all calls of deprecated functions"
Define [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) to `1` (or set the
CMake option [`JSON_DeleteDeprecatedFunctions`](cmake.md#json_deletedeprecatedfunctions)) to delete all deprecated
functions. Every remaining call then fails to compile, even if deprecation warnings are disabled, so your code is
ready for version 4.0.0 once it compiles with the macro.
### Parsing
- Function `friend std::istream& operator<<(basic_json&, std::istream&)` is deprecated since 3.0.0. Please use
@@ -41,8 +48,10 @@ function to use instead.
[`from_ubjson`](../api/basic_json/from_ubjson.md), and [`from_bson`](../api/basic_json/from_bson.md)) via initializer
lists is deprecated since 3.8.0. Instead, pass two iterators; for instance, call `from_cbor(ptr, ptr+len)` instead of
`from_cbor({ptr, len})`. Likewise, passing a pointer and a length as two separate arguments to `from_cbor`,
`from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0; call `from_cbor(ptr, ptr+len)` instead of
`from_cbor(ptr, len)`.
`from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0, and to
[`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) since 3.13.0; call
`from_cbor(ptr, ptr+len)` instead of `from_cbor(ptr, len)`. These overloads will not be removed in version 4.0.0, but
deleted, so a call like `from_cbor(ptr, len)` cannot compile and convert `len` to the `strict` parameter.
=== "Deprecated"
+2
View File
@@ -366,6 +366,7 @@ nav:
- 'JSON_BRACE_INIT_COPY_SEMANTICS': api/macros/json_brace_init_copy_semantics.md
- 'JSON_CATCH_USER, JSON_THROW_USER, JSON_TRY_USER': api/macros/json_throw_user.md
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
@@ -387,6 +388,7 @@ nav:
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
- 'JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS': api/macros/json_use_objects_for_enum_keyed_maps.md
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
- 'JSON_VIEW_NO_SIMD': api/macros/json_view_no_simd.md
- 'JSON_VIEW_USE_SSSE3': api/macros/json_view_use_ssse3.md
+15 -4
View File
@@ -50,6 +50,10 @@
#define JSON_STRICT_BINARY_UTF8 0
#endif
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -92,14 +96,20 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
#else
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -109,7 +119,8 @@
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
@@ -172,7 +172,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e)
typename BasicJsonType::number_unsigned_t, underlying_type>::type;
value_type val;
get_arithmetic_value(j, val);
e = static_cast<EnumType>(static_cast<underlying_type>(val));
e = static_cast<EnumType>(bool_aware_static_cast<underlying_type>(val));
}
#endif // JSON_DISABLE_ENUM_SERIALIZATION
@@ -530,11 +530,40 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m)
}
}
// read a map with enum keys from an object, using the enum's own from_json for
// the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename BasicJsonType, typename Map>
inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/)
{
if (!j.is_object())
{
return false;
}
m.clear();
for (const auto& p : *j.template get_ptr<const typename BasicJsonType::object_t*>())
{
m.emplace(BasicJsonType(p.first).template get<typename Map::key_type>(), p.second.template get<typename Map::mapped_type>());
}
return true;
}
template<typename BasicJsonType, typename Map>
inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/)
{
return false;
}
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
typename = enable_if_t < !std::is_constructible <
typename BasicJsonType::string_t, Key >::value >>
void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
{
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
{
return;
}
from_json_pair_array_to_map(j, m);
}
@@ -543,6 +572,11 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
typename BasicJsonType::string_t, Key >::value >>
void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
{
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
{
return;
}
from_json_pair_array_to_map(j, m);
}
@@ -23,6 +23,7 @@
#include <valarray> // valarray
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/std_fs.hpp>
@@ -286,9 +287,20 @@ struct external_constructor<value_t::object>
/////////////
#ifdef JSON_HAS_CPP_17
// whether storing the value of a std::optional<T> cannot throw; MSVC 2017
// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so
// the exception would call std::terminate (#5642)
#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920
template<typename BasicJsonType, typename T>
using is_nothrow_optional_to_json = std::false_type;
#else
template<typename BasicJsonType, typename T>
using is_nothrow_optional_to_json = std::is_nothrow_assignable<BasicJsonType&, const T&>;
#endif
template<typename BasicJsonType, typename T,
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_nothrow_assignable<BasicJsonType&, const T&>::value)
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(is_nothrow_optional_to_json<BasicJsonType, T>::value)
{
if (opt.has_value())
{
@@ -364,7 +376,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept
{
using underlying_type = typename std::underlying_type<EnumType>::type;
static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;
external_constructor<integral_value_t>::construct(j, static_cast<underlying_type>(e));
external_constructor<integral_value_t>::construct(j, bool_aware_static_cast<underlying_type>(e));
}
#endif // JSON_DISABLE_ENUM_SERIALIZATION
@@ -384,6 +396,9 @@ template < typename BasicJsonType, typename CompatibleArrayType,
!is_basic_json<CompatibleArrayType>::value
#if JSON_HAS_RANGE_VIEW_CONVERSION
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
&& !is_enum_keyed_map<CompatibleArrayType>::value
#endif
,
int > = 0 >
@@ -438,6 +453,33 @@ inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
external_constructor<value_t::object>::construct(j, obj);
}
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
// store a map with enum keys as an object, using the enum's own to_json for the
// keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); without the macro, such maps
// are stored as arrays of [key, value] pairs
template < typename BasicJsonType, typename EnumKeyedMap,
enable_if_t < is_enum_keyed_map<EnumKeyedMap>::value&& !is_basic_json<EnumKeyedMap>::value, int > = 0 >
inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
{
typename BasicJsonType::object_t obj;
for (const auto& p : map)
{
BasicJsonType key = p.first;
if (JSON_HEDLEY_UNLIKELY(!key.is_string()))
{
JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key));
}
auto& key_string = *key.template get_ptr<typename BasicJsonType::string_t*>();
if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second))
{
JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key));
}
}
external_constructor<value_t::object>::construct(j, std::move(obj));
}
#endif
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
File diff suppressed because it is too large. Load diff
+34 -2
View File
@@ -221,7 +221,7 @@ class lexer : public lexer_base<BasicJsonType>
// scan functions
/////////////////////
/// contiguous input: try to decode the 4 hex digits following `\u`
/// contiguous input: try to decode the 4 hex digits following `\\u`
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
/// to get(). On success, advances the adapter and the position counters
/// exactly as those 4 get() calls would (a hex digit is never '\n', so
@@ -2070,6 +2070,39 @@ scan_number_done:
// read the next character and ignore whitespace
skip_whitespace();
return scan_after_whitespace();
}
/*!
@brief scan the next token when the caller expects a separator (':' or
',') most of the time
After an object key the next token is almost always ':', after a value
inside an object or array almost always ','. Testing for that character
first is a compare and a well-predicted branch, where the switch in
scan_after_whitespace() is an indirect jump through a table. Anything else
goes through the switch, so the result is the same as scan()'s.
May only be called after scan() has run once (the BOM check is skipped).
*/
token_type scan_expecting(token_type expected_type)
{
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
JSON_ASSERT(position.chars_read_total > 0);
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
skip_whitespace();
if (JSON_HEDLEY_LIKELY(current == expected_char))
{
return expected_type;
}
return scan_after_whitespace();
}
private:
/// the part of scan() after the leading whitespace: skip comments and
/// scan the token that starts with current
token_type scan_after_whitespace()
{
// ignore comments
while (ignore_comments && current == '/')
{
@@ -2149,7 +2182,6 @@ scan_number_done:
}
}
private:
/// input adapter
InputAdapterType ia;
+11 -4
View File
@@ -260,7 +260,7 @@ class parser
}
// parse separator (:)
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
@@ -423,7 +423,7 @@ class parser
{
// comma -> next value
// or end of array (ignore_trailing_commas = true)
if (get_token() == token_type::value_separator)
if (get_token_expecting(token_type::value_separator))
{
// parse a new value
get_token();
@@ -463,7 +463,7 @@ class parser
// comma -> next value
// or end of object (ignore_trailing_commas = true)
if (get_token() == token_type::value_separator)
if (get_token_expecting(token_type::value_separator))
{
get_token();
@@ -484,7 +484,7 @@ class parser
}
// parse separator (:)
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
@@ -528,6 +528,13 @@ class parser
return last_token = m_lexer.scan();
}
/// get next token from lexer; true if it is the separator @a expected_type
/// (name_separator or value_separator), which it usually is
bool get_token_expecting(token_type expected_type)
{
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
}
std::string exception_message(const token_type expected, const std::string& context)
{
std::string error_msg = "syntax error ";
+24
View File
@@ -88,9 +88,13 @@ class json_pointer
/// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())
operator string_t() const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return to_string();
}
#endif
#ifndef JSON_NO_IO
/// @brief write string representation of the JSON pointer to stream
@@ -1029,9 +1033,13 @@ class json_pointer
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))
bool operator==(const string_t& rhs) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return *this == json_pointer(rhs);
}
#endif
/// @brief 3-way compares two JSON pointers
template<typename RefStringTypeRhs>
@@ -1109,18 +1117,26 @@ template<typename RefStringTypeLhs,
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
const StringType& rhs)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return lhs == json_pointer<RefStringTypeLhs>(rhs);
}
#endif
template<typename RefStringTypeRhs,
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
inline bool operator==(const StringType& lhs,
const json_pointer<RefStringTypeRhs>& rhs)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
}
#endif
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
@@ -1134,18 +1150,26 @@ template<typename RefStringTypeLhs,
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
const StringType& rhs)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return !(lhs == rhs);
}
#endif
template<typename RefStringTypeRhs,
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
inline bool operator!=(const StringType& lhs,
const json_pointer<RefStringTypeRhs>& rhs)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return !(lhs == rhs);
}
#endif
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
+4
View File
@@ -888,6 +888,10 @@
#define JSON_DISABLE_ENUM_SERIALIZATION 0
#endif
#ifndef JSON_DELETE_DEPRECATED_FUNCTIONS
#define JSON_DELETE_DEPRECATED_FUNCTIONS 0
#endif
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
#endif
@@ -45,6 +45,8 @@
#undef JSON_PRECISE_STREAM_POSITION
#undef JSON_STRICT_NUL_HANDLING
#undef JSON_STRICT_BINARY_UTF8
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
@@ -438,6 +438,30 @@ template<typename BasicJsonType, typename CompatibleObjectType>
struct is_compatible_object_type
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
template<typename T>
using insert_result_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()));
template<typename T>
using insert_result_second_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()).second);
// a map-like type (std::map, std::unordered_map, ...) whose keys are enums; see
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename T, typename = void>
struct is_enum_keyed_map : std::false_type {};
template<typename T>
struct is_enum_keyed_map <
T, enable_if_t < is_detected<mapped_type_t, T>::value&&
is_detected<key_type_t, T>::value >>
{
// maps with non-unique keys (std::multimap, std::unordered_multimap, ...)
// are excluded, because an object cannot hold duplicate keys; they are
// detected by insert() returning an iterator instead of a pair<iterator, bool>
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
static constexpr bool value = std::is_enum<typename T::key_type>::value &&
!(is_detected<insert_result_t, T>::value && !is_detected<insert_result_second_t, T>::value);
};
template<typename BasicJsonType, typename ConstructibleObjectType,
typename = void>
struct is_constructible_object_type_impl : std::false_type {};
@@ -882,6 +906,21 @@ T conditional_static_cast(U value)
return value;
}
// like conditional_static_cast, but converts to bool by comparing with zero,
// because MSVC 2015 warns about any conversion to bool (C4800), even with an
// explicit cast; used for enums whose underlying type is bool
template < typename T, typename U, enable_if_t < !std::is_same<T, bool>::value, int > = 0 >
T bool_aware_static_cast(U value)
{
return conditional_static_cast<T>(value);
}
template<typename T, typename U, enable_if_t<std::is_same<T, bool>::value, int> = 0>
bool bool_aware_static_cast(U value)
{
return value != U();
}
template<typename... Types>
using all_integral = conjunction<std::is_integral<Types>...>;
@@ -127,6 +127,7 @@ class binary_writer
@throw type_error.316 if a string value or an object key is not valid
UTF-8
@throw type_error.317 if @a j is not an object
@throw type_error.321 if a value nested in @a j is discarded
*/
void write_bson(const BasicJsonType& j)
{
@@ -158,6 +159,7 @@ class binary_writer
@param[in] j JSON value to serialize
@throw type_error.316 if a string value or an object key is not valid
UTF-8
@throw type_error.321 if @a j or a value nested in it is discarded
*/
void write_cbor(const BasicJsonType& j)
{
@@ -322,7 +324,7 @@ class binary_writer
case value_t::discarded:
default:
break;
throw_on_discarded(j, "CBOR");
}
}
@@ -382,6 +384,7 @@ class binary_writer
/*!
@param[in] j JSON value to serialize
@throw type_error.321 if @a j or a value nested in it is discarded
*/
void write_msgpack(const BasicJsonType& j)
{
@@ -655,7 +658,7 @@ class binary_writer
case value_t::discarded:
default:
break;
throw_on_discarded(j, "MessagePack");
}
}
@@ -668,6 +671,7 @@ class binary_writer
@param[in] bjdata_version which BJData version to use, default is draft2
@throw type_error.316 if a string value or an object key is not valid
UTF-8
@throw type_error.321 if @a j or a value nested in it is discarded
*/
void write_ubjson(const BasicJsonType& j, const bool use_count,
const bool use_type, const bool add_prefix = true,
@@ -901,7 +905,7 @@ class binary_writer
case value_t::discarded:
default:
break;
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
}
}
@@ -921,6 +925,15 @@ class binary_writer
}
private:
/*!
@brief throws because @a j is discarded and cannot be serialized
@throw type_error.321 always
*/
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
{
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
}
//////////
// BSON //
//////////
@@ -1172,6 +1185,7 @@ class binary_writer
into a byte, before anything is written
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
anything is written
@throw type_error.321 if @a j is discarded
*/
std::size_t calc_bson_value_size(const BasicJsonType& j)
{
@@ -1198,10 +1212,12 @@ class binary_writer
case value_t::null:
return 0ul;
case value_t::discarded:
throw_on_discarded(j, "BSON");
// LCOV_EXCL_START
case value_t::object:
case value_t::array:
case value_t::discarded:
default:
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
return 0ul;
@@ -1238,10 +1254,12 @@ class binary_writer
case value_t::null:
return write_bson_null(name);
case value_t::discarded:
throw_on_discarded(j, "BSON");
// LCOV_EXCL_START
case value_t::object:
case value_t::array:
case value_t::discarded:
default:
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
return;
@@ -1308,6 +1326,8 @@ class binary_writer
byte, before anything is written
@throw type_error.316 if a string value or a key is not valid UTF-8,
before anything is written
@throw type_error.321 if a value nested in @a document is discarded,
before anything is written
*/
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
{
+2 -4
View File
@@ -164,14 +164,12 @@ class builder
if (p[1] == '/')
{
p += 2;
// (as in parse(), a null byte is the end of the input, so it is
// left for the caller to see)
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
{
++p;
}
if (NulIsEnd && p != e && *p == 0)
{
++p; // as in parse(), a null byte ends the comment like a line break
}
return p;
}
if (p[1] == '*')
+5 -514
View File
@@ -31,11 +31,7 @@ namespace view
{
/// append-only output buffer: writes through a raw pointer into a string that
/// is resized ahead, and trimmed by finish(). The estimate is reserved, and the
/// string grows in steps of 64 KiB within it: resize() fills the new bytes
/// with zeros (before C++23, a string cannot grow without), and a small step
/// is filled while the writer is about to use it, in the cache, instead of
/// filling the whole estimate in memory first.
/// is resized ahead, and trimmed by finish()
template<typename StringType>
class output_buffer
{
@@ -79,49 +75,17 @@ class output_buffer
m_pos += n;
}
/// the write position and the end of the writable space, for a writer
/// that keeps the position in a local variable (set_cursor() hands it back)
char* cursor() const noexcept
{
return m_pos;
}
char* limit() const noexcept
{
return m_end;
}
void set_cursor(char* p) noexcept
{
m_pos = p;
}
private:
/// the size of a growth step (a function: std::min() takes a reference,
/// which a static constexpr member does not have before C++17)
static constexpr std::size_t step() noexcept
{
return 65536;
}
static StringType& sized(StringType& out, std::size_t estimate)
{
out.reserve(estimate);
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
out.resize((std::max)(estimate, static_cast<std::size_t>(64)));
return out;
}
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
{
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good
// estimate is never copied to a larger allocation)
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
if (size > m_out.capacity())
{
m_out.reserve((std::max)(m_out.capacity() * 2, size));
}
m_out.resize(size);
m_out.resize((std::max)(m_out.size() * 2, used + n + 256));
m_pos = &m_out[0] + used;
m_end = &m_out[0] + m_out.size();
}
@@ -131,105 +95,6 @@ class output_buffer
char* m_end;
};
/// The length of the run at s that dump() writes unchanged without
/// ensure_ascii: all bytes but quotes, backslashes, and control characters.
/// Unlike detail::string_bulk_run(), non-ASCII bytes are not validated: the
/// strings of a document are valid UTF-8 (a damaged image loaded with
/// image_check::bounds can have others, which are then written unchanged).
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
{
constexpr std::uint64_t ones = 0x0101010101010101ull;
constexpr std::uint64_t high = 0x8080808080808080ull;
std::size_t i = 0;
for (; i + 8 <= n; i += 8)
{
const std::uint64_t v = read_eight_bytes(s + i);
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
const std::uint64_t stop = (((q - ones) & ~q) | ((b - ones) & ~b) | ((v - 0x2020202020202020ull) & ~v)) & high;
if (stop != 0)
{
// the lowest flagged byte is the first stop: borrows only flag bytes above a true one
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
}
}
for (; i < n; ++i)
{
if (s[i] == '"' || s[i] == '\\' || s[i] < 0x20)
{
return i;
}
}
return n;
}
/// A stack that starts in a buffer of the caller (a local array) and moves to
/// the heap (a vector of the caller) only when that is full, so that dumps of
/// shallow documents need no allocation. The top is a pointer, as in
/// std::vector. The address of the stack never escapes (the growth gets the
/// vector and returns the new storage), so its pointers stay in registers.
template<typename T>
class small_stack
{
public:
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(&heap)
{}
small_stack(const small_stack&) = delete;
small_stack(small_stack&&) = delete;
small_stack& operator=(const small_stack&) = delete;
small_stack& operator=(small_stack&&) = delete;
~small_stack() = default;
NLOHMANN_VIEW_ALWAYS_INLINE void push_back(const T& x)
{
if (NLOHMANN_VIEW_UNLIKELY(m_top == m_end))
{
const std::size_t used = size();
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
m_begin = grow(*m_heap, m_begin, used, capacity);
m_top = m_begin + used;
m_end = m_begin + capacity;
}
*m_top++ = x;
}
NLOHMANN_VIEW_ALWAYS_INLINE T& back() noexcept
{
return m_top[-1];
}
NLOHMANN_VIEW_ALWAYS_INLINE void pop_back() noexcept
{
--m_top;
}
NLOHMANN_VIEW_ALWAYS_INLINE bool empty() const noexcept
{
return m_top == m_begin;
}
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t size() const noexcept
{
return static_cast<std::size_t>(m_top - m_begin);
}
private:
/// the used entries moved to heap storage of the given capacity
NLOHMANN_VIEW_NOINLINE static T* grow(std::vector<T>& heap, const T* begin, std::size_t used, std::size_t capacity)
{
std::vector<T> bigger(capacity);
std::copy(begin, begin + used, bigger.begin());
heap.swap(bigger);
return heap.data();
}
T* m_begin;
T* m_top;
T* m_end;
std::vector<T>* m_heap;
};
/// how the view's dump() writes a value
struct dump_style
{
@@ -264,18 +129,6 @@ class view_serializer
void dump(const node* root)
{
if (!m_style.pretty && !m_style.ensure_ascii)
{
if (m_style.source_numbers)
{
dump_compact<true>(root);
}
else
{
dump_compact<false>(root);
}
return;
}
struct frame
{
const node* pos; ///< next element, or key of the next member
@@ -283,9 +136,7 @@ class view_serializer
bool object;
bool first; ///< nothing written yet
};
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::vector<frame> heap;
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
std::vector<frame> stack;
const node* n = root;
for (;;)
{
@@ -356,289 +207,6 @@ class view_serializer
}
private:
/*!
@brief the compact output without ensure_ascii (the default dump())
The same walk as dump(), with the write position in a local variable
(stores through char pointers would otherwise force a reload of the
buffer's members after each one), and with strings and number tokens of
the source copied by fixed-size moves of 32 bytes where the source has
that many bytes left, instead of a library call per token. The buffer
keeps 64 bytes of slack for the overshoot.
*/
/// a string that is not a plain string of the source (decoded, or written
/// by an edit), without ensure_ascii: runs without characters to escape
/// are copied
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
{
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
m_out.put('"');
for (std::size_t i = 0; i < n.len;)
{
const std::size_t run = plain_output_run(s + i, n.len - i);
if (run != 0)
{
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
i += run;
continue;
}
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
++i;
}
m_out.put('"');
}
/// the copies of dump_compact() that are not fixed-size moves (long
/// strings, or near the end of the source); out of line, so that the
/// compiler does not merge the fixed-size moves into this call
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
{
std::memcpy(to, from, n);
}
template<bool SourceNumbers>
void dump_compact(const node* root)
{
struct frame
{
const node* pos; ///< (editable documents) next element, or key of the next member
const node* end;
bool object;
};
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::vector<frame> heap;
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
const char* const src = m_doc.src;
const char* const src_end = src + m_doc.size;
char* w = m_out.cursor();
char* lim = m_out.limit();
// room for n bytes and the slack
const auto room = [&](std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(lim - w) < n + 64))
{
m_out.set_cursor(w);
m_out.reserve(n + 64);
w = m_out.cursor();
lim = m_out.limit();
}
};
// copy n bytes of the source (after room(n))
const auto copy = [&](const char* from, std::size_t n)
{
if (n <= 32 && src_end - from >= 32)
{
std::memcpy(w, from, 32);
}
else if (n <= 256 && src_end - from >= static_cast<std::ptrdiff_t>(n) + 32)
{
for (std::size_t i = 0; i < n; i += 32)
{
std::memcpy(w + i, from + i, 32);
}
}
else
{
copy_long(w, from, n);
}
w += n;
};
// a literal of n bytes (after room(n))
const auto literal = [&](const char* text, std::size_t n)
{
std::memcpy(w, text, n);
w += n;
};
// a string that is not a plain string of the source (out of line, so
// that the cursor stays in a register here)
const auto escaped = [&](const node & n)
{
m_out.set_cursor(w);
write_decoded(n);
w = m_out.cursor();
lim = m_out.limit();
};
// Read-only documents: the elements of a container follow it in the
// node array, so the walk goes through the array in order, and a
// frame only needs the end of its container. Editable documents: the
// elements of a moved container live elsewhere, so a frame keeps the
// position of the next element (see navigation).
// The innermost open container is kept in registers (cur; end ==
// nullptr: none), the stack holds the ones around it.
frame cur{nullptr, nullptr, false};
const node* n = root;
for (;;)
{
// write the value at n (read-only documents: and advance n)
bool opened = false;
switch (static_cast<value_t>(n->kind))
{
case value_t::string:
if ((n->flags & node_flags::storage) == 0)
{
room(n->len + 2);
*w++ = '"';
copy(src + n->off, n->len);
*w++ = '"';
}
else
{
escaped(*n);
}
break;
case value_t::number_integer:
case value_t::number_unsigned:
{
const std::uint32_t len = number_length(*n);
room(len);
if (Editable && (n->flags & node_flags::storage) != 0)
{
copy_long(w, m_doc.str(*n), len); // a canonical token written by an edit
w += len;
break;
}
const char* const token = src + n->off;
if (!SourceNumbers && NLOHMANN_VIEW_UNLIKELY(len == 2 && token[0] == '-' && token[1] == '0'))
{
*w++ = '0'; // parse() reads -0 as the integer 0
}
else
{
copy(token, len);
}
break;
}
case value_t::number_float:
if (SourceNumbers && (n->flags & node_flags::storage) != node_flags::edited)
{
room(n->len);
copy(src + n->off, n->len);
}
else if (std::is_same<number_float_t, double>::value)
{
room(64);
w = write_double_at(w, *n);
}
else
{
m_out.set_cursor(w);
write_float_node(*n);
w = m_out.cursor();
lim = m_out.limit();
}
break;
case value_t::boolean:
room(8);
if ((n->flags & node_flags::is_true) != 0)
{
literal("true", 4);
}
else
{
literal("false", 5);
}
break;
case value_t::object:
case value_t::array:
{
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
room(8);
if (n->len == 0)
{
literal(object ? "{}" : "[]", 2);
}
else
{
*w++ = object ? '{' : '[';
stack.push_back(cur);
if (Editable)
{
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
}
else
{
cur = frame{nullptr, n + n->next, object};
}
opened = true;
}
break;
}
case value_t::null:
room(8);
literal("null", 4);
break;
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
case value_t::discarded: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
}
if (!Editable)
{
++n; // the next node: the first element of an opened container, or the node after a scalar
}
// go to the next value: close finished containers, then separate
// (a container just opened has an element)
if (!opened)
{
for (;;)
{
if (cur.end == nullptr)
{
m_out.set_cursor(w);
m_out.finish();
return;
}
if ((Editable ? cur.pos : n) != cur.end)
{
break;
}
room(1);
*w++ = cur.object ? '}' : ']';
cur = stack.back();
stack.pop_back();
}
room(1);
*w++ = ',';
}
const node* const at = Editable ? cur.pos : n;
if (cur.object)
{
const node& key = *at;
if ((key.flags & node_flags::storage) == 0)
{
room(key.len + 3);
*w++ = '"';
copy(src + key.off, key.len);
w[0] = '"';
w[1] = ':';
w += 2;
}
else
{
escaped(key);
room(1);
*w++ = ':';
}
if (Editable)
{
n = nav::value(at + 1);
cur.pos = document_data::after(at + 1);
}
else
{
++n;
}
}
else if (Editable)
{
n = nav::value(at);
cur.pos = document_data::after(at);
}
}
}
void newline(std::size_t level)
{
if (m_style.pretty)
@@ -690,7 +258,7 @@ class view_serializer
}
else
{
write_float_node(n);
write_float(float_value<number_float_t>(m_doc, n));
}
break;
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
@@ -702,83 +270,6 @@ class view_serializer
}
}
/// a float node as dump() writes it
void write_float_node(const node& n)
{
write_float_node(n, std::is_same<number_float_t, double> {});
}
void write_float_node(const node& n, std::false_type /*other*/)
{
write_float(float_value<number_float_t>(m_doc, n));
}
void write_float_node(const node& n, std::true_type /*double*/)
{
m_out.reserve(64);
m_out.set_cursor(write_double_at(m_out.cursor(), n));
}
/*!
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
A token of at most 15 significant digits is written from its digits,
without a conversion: two decimals of at most 15 digits are farther
apart than the rounding interval of a (normal) double (the argument
behind DBL_DIG), so the token's digits are the shortest ones of its
double, which the library's conversion writes (Zmij). Other tokens are
converted from the digits already read.
*/
char* write_double_at(char* w, const node& n)
{
const unsigned int_digits = n.extra & 0xFFu;
const unsigned frac_digits = n.extra >> 8u;
if ((n.flags & node_flags::storage) != node_flags::edited && int_digits + frac_digits <= 19)
{
const auto* const first = reinterpret_cast<const unsigned char*>(m_doc.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const float_significand d = layout_decimal(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
// (the exponent keeps the value far from subnormals and overflow)
if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290)
{
*w = '-';
w += d.negative ? 1 : 0;
// (without leading zeros, all digits of the token count)
const unsigned char lead = first[d.negative ? 1 : 0];
return lead != '0' ? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(int_digits + frac_digits), static_cast<int>(d.exponent))
: ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(d.exponent));
}
return write_double_value_at(w, decimal_to_float<double>(d)); // (without reading the token again)
}
return write_double_value_at(w, static_cast<double>(float_value<number_float_t>(m_doc, n)));
}
/// n bytes of text at w
static char* write_text_at(char* w, const char* text, std::size_t n) noexcept
{
std::memcpy(w, text, n);
return w + n;
}
/// a double as dump() writes it, at w (64 bytes of room)
static char* write_double_value_at(char* w, double x)
{
// (from the bits: without the checks of to_chars())
std::uint64_t bits = 0;
std::memcpy(&bits, &x, sizeof(bits));
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
{
return write_text_at(w, "null", 4);
}
*w = '-';
w += bits >> 63u;
bits &= ~(std::uint64_t{1} << 63u);
if (bits == 0)
{
return write_text_at(w, "0.0", 3);
}
return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits));
}
/// as serializer::dump_float()
void write_float(number_float_t x)
{
+328 -87
View File
@@ -613,100 +613,210 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// constructor for rvalue binary arrays (internal type)
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
void destroy(value_t t)
private:
// raw, allocation-free transfer of m_data from src to dst: no
// set_parents()/assert_invariant() (the former is O(#children) per
// call under JSON_DIAGNOSTICS, which would make the walk below
// quadratic); dst takes ownership, src is left as value_t::null.
static void take(basic_json& dst, basic_json& src) noexcept
{
dst.m_data.m_type = src.m_data.m_type;
dst.m_data.m_value = src.m_data.m_value;
src.m_data.m_type = value_t::null;
}
// true if v is not an array/object, or is an already-empty one
static bool has_no_children(const basic_json& v) noexcept
{
switch (v.m_data.m_type)
{
case value_t::array:
return v.m_data.m_value.array->empty();
case value_t::object:
return v.m_data.m_value.object->empty();
default:
return true;
}
}
static basic_json& last_child(basic_json& v)
{
if (v.m_data.m_type == value_t::array)
{
return v.m_data.m_value.array->back();
}
JSON_ASSERT(v.m_data.m_type == value_t::object);
return v.m_data.m_value.object->rbegin()->second;
}
// removes the last child of a non-empty array/object v; this never
// allocates, and since it is only ever called when that child is a
// scalar or an already-empty array/object, destroying it never
// recurses more than one level deep (see destroy() below)
static void pop_last_child(basic_json& v)
{
if (v.m_data.m_type == value_t::array)
{
v.m_data.m_value.array->pop_back();
}
else
{
JSON_ASSERT(v.m_data.m_type == value_t::object);
// erase() needs a forward iterator, so std::prev(end()) is
// used here rather than rbegin() (see last_child() above)
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
}
}
// deallocates the (already empty) array/object held by v; this is
// the same allocator-based free the old recursive implementation
// used, just factored out so every level of the walk in destroy()
// can share it
static void free_container(basic_json& v) noexcept
{
if (v.m_data.m_type == value_t::array)
{
JSON_ASSERT(v.m_data.m_value.array->empty());
AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
}
else
{
JSON_ASSERT(v.m_data.m_type == value_t::object);
JSON_ASSERT(v.m_data.m_value.object->empty());
AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
}
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
}
public:
void destroy_string() noexcept
{
if (string == nullptr)
{
// not initialized (e.g., due to exception in the ctor)
return;
}
AllocatorType<string_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
}
void destroy_binary() noexcept
{
if (binary == nullptr)
{
// not initialized (e.g., due to exception in the ctor)
return;
}
AllocatorType<binary_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
}
// t must be value_t::array or value_t::object
void destroy_container(value_t t) noexcept
{
if (
(t == value_t::object && object == nullptr) ||
(t == value_t::array && array == nullptr) ||
(t == value_t::string && string == nullptr) ||
(t == value_t::binary && binary == nullptr)
(t == value_t::array && array == nullptr)
)
{
// not initialized (e.g., due to exception in the ctor)
return;
}
if (t == value_t::array || t == value_t::object)
// Destroy the tree without recursing per nesting level and
// without any heap allocation: a heap-allocated flattening
// stack (the previous implementation) can itself throw
// bad_alloc, which would escape this noexcept destructor and
// terminate the program (#5135).
//
// Instead, walk down the "last child" chain, reversing links
// as we go: cur is the container currently being emptied,
// and prev is its parent (value_t::null when there is none).
// Each parent's last child slot doubles as storage for that
// parent's own parent link while we are below it, so no
// extra memory is needed. We only ever remove a child once
// it is a scalar or an empty array/object, which neither
// allocates nor recurses more than one level deep.
//
// This json_value is not itself a basic_json, so the
// top-level container is first moved into a local stand-in
// ("cur"); a default-constructed basic_json has a null
// pointer in its m_value (see data::m_value's initializer),
// so swapping it with *this leaves this union's own pointer
// null, and it is never looked at or freed a second time.
basic_json cur;
cur.m_data.m_type = t;
using std::swap;
swap(cur.m_data.m_value, *this);
basic_json prev; // value_t::null: no parent
while (true)
{
// flatten the current json_value to a heap-allocated stack
std::vector<basic_json> stack;
// move the top-level items to stack
if (t == value_t::array)
if (has_no_children(cur))
{
stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
}
else
{
stack.reserve(object->size());
for (auto&& it : *object)
if (prev.m_data.m_type == value_t::null)
{
stack.push_back(std::move(it.second));
}
}
while (!stack.empty())
{
// move the last item to a local variable to be processed
basic_json current_item(std::move(stack.back()));
stack.pop_back();
// if current_item is array/object, move
// its children to the stack to be processed later
if (current_item.is_array())
{
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
current_item.m_data.m_value.array->clear();
}
else if (current_item.is_object())
{
for (auto&& it : *current_item.m_data.m_value.object)
{
stack.push_back(std::move(it.second));
}
current_item.m_data.m_value.object->clear();
free_container(cur);
return; // back at the top with nothing left to do
}
// it's now safe that current_item gets destructed
// since it doesn't have any children
// ascend: detach the grandparent link from prev's
// last slot, drop that (now null) slot, free cur
// (it is empty), then move up one level
basic_json gp;
take(gp, last_child(prev));
pop_last_child(prev);
free_container(cur);
take(cur, prev);
take(prev, gp);
continue;
}
basic_json& cur_last_ref = last_child(cur);
if (has_no_children(cur_last_ref))
{
// scalar, or already-empty array/object
pop_last_child(cur);
continue;
}
// descend into the non-empty last child, reversing the
// link: its slot takes over prev, and the child becomes
// the new cur
basic_json tmp;
take(tmp, cur_last_ref);
take(cur_last_ref, prev);
take(prev, cur);
take(cur, tmp);
}
}
void destroy(value_t t)
{
switch (t)
{
case value_t::object:
{
AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
break;
}
case value_t::array:
{
AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
break;
}
case value_t::string:
{
AllocatorType<string_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
destroy_string();
break;
}
case value_t::binary:
{
AllocatorType<binary_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
destroy_binary();
break;
case value_t::object:
case value_t::array:
destroy_container(t);
break;
}
case value_t::null:
case value_t::boolean:
@@ -715,9 +825,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
case value_t::number_float:
case value_t::discarded:
default:
{
break;
}
}
}
};
@@ -3493,9 +3601,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
ValueType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, const ValueType& default_value) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return value(ptr.convert(), default_value);
}
#endif
template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type<ValueType>::type,
detail::enable_if_t <
@@ -3504,9 +3616,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
ReturnType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, ValueType && default_value) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return value(ptr.convert(), std::forward<ValueType>(default_value));
}
#endif
/// @brief access the first element
/// @sa https://json.nlohmann.me/api/basic_json/front/
@@ -3864,9 +3980,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.contains(this);
}
#endif
/// @}
@@ -3977,9 +4097,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// that is, replace `json::iterator_wrapper(j)` with `j.items()`.
JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())
static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ref.items();
}
#endif
/// @brief wrapper to access iterator member functions in range-based for
/// @sa https://json.nlohmann.me/api/basic_json/items/
@@ -3988,9 +4112,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// that is, replace `json::iterator_wrapper(j)` with `j.items()`.
JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())
static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ref.items();
}
#endif
/// @brief helper to access iterator member functions in range-based for
/// @sa https://json.nlohmann.me/api/basic_json/items/
@@ -5079,7 +5207,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
template<typename ScalarType>
requires std::is_scalar_v<ScalarType>
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
{
return basic_json(lhs) <= rhs;
}
@@ -5088,7 +5216,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
template<typename ScalarType>
requires std::is_scalar_v<ScalarType>
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
{
return basic_json(lhs) >= rhs;
}
@@ -5109,11 +5237,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
#endif
}
// The friend comparisons with a scalar name the JSON type via decltype of
// their parameter in noexcept, because older MSVC versions do not see the
// class scope there: MSVC 2015 and 2017 take basic_json as the template,
// and MSVC 2019 16.0 rejects member types and template parameters.
/// @brief comparison: equal
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
{
return lhs == basic_json(rhs);
}
@@ -5122,7 +5255,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
{
return basic_json(lhs) == rhs;
}
@@ -5138,7 +5271,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
{
return lhs != basic_json(rhs);
}
@@ -5147,7 +5280,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
{
return basic_json(lhs) != rhs;
}
@@ -5167,7 +5300,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
{
return lhs < basic_json(rhs);
}
@@ -5176,7 +5309,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
{
return basic_json(lhs) < rhs;
}
@@ -5196,7 +5329,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
{
return lhs <= basic_json(rhs);
}
@@ -5205,7 +5338,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
{
return basic_json(lhs) <= rhs;
}
@@ -5226,7 +5359,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
{
return lhs > basic_json(rhs);
}
@@ -5235,7 +5368,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
{
return basic_json(lhs) > rhs;
}
@@ -5255,7 +5388,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
{
return lhs >= basic_json(rhs);
}
@@ -5264,7 +5397,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
{
return basic_json(lhs) >= rhs;
}
@@ -5300,6 +5433,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return o;
}
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
// the deleted version is a function template after the class, because
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
/// @brief serialize to stream
/// @sa https://json.nlohmann.me/api/operator_ltlt/
/// @deprecated This function is deprecated since 3.0.0 and will be removed in
@@ -5311,6 +5447,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{
return o << j;
}
#endif
#endif // JSON_NO_IO
/// @}
@@ -5362,12 +5499,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const bool allow_exceptions = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
basic_json result;
auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
p.parse(true, result);
return result;
}
#endif
/// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/
@@ -5397,9 +5538,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static bool accept(detail::span_input_adapter&& i,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
}
#endif
/// @brief generate SAX events
/// @sa https://json.nlohmann.me/api/basic_json/sax_parse/
@@ -5460,6 +5605,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const bool strict = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
auto ia = i.get();
return format == input_format_t::json
@@ -5468,10 +5616,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict);
}
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#ifndef JSON_NO_IO
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
// the deleted version is a function template after the class, because
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
/// @brief deserialize from stream
/// @sa https://json.nlohmann.me/api/operator_gtgt/
/// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in
@@ -5483,6 +5635,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{
return operator>>(i, j);
}
#endif
/// @brief deserialize from stream
/// @sa https://json.nlohmann.me/api/operator_gtgt/
@@ -5808,9 +5961,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const bool strict = true,
const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler);
}
#endif
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))
@@ -5818,9 +5975,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const bool strict = true,
const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, error_handler_t::keep, tag_handler);
}
#endif
/// @brief create a JSON value from an input in MessagePack format
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
@@ -5853,18 +6014,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json from_msgpack(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_msgpack(ptr, ptr + len, strict, allow_exceptions);
}
#endif
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))
static basic_json from_msgpack(detail::span_input_adapter&& i,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_binary_impl(i.get(), input_format_t::msgpack, strict, allow_exceptions);
}
#endif
/// @brief create a JSON value from an input in UBJSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
@@ -5897,18 +6066,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json from_ubjson(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_ubjson(ptr, ptr + len, strict, allow_exceptions);
}
#endif
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))
static basic_json from_ubjson(detail::span_input_adapter&& i,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_binary_impl(i.get(), input_format_t::ubjson, strict, allow_exceptions);
}
#endif
/// @brief create a JSON value from an input in BJData format
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
@@ -5935,6 +6112,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions, error_handler);
}
template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bjdata(ptr, ptr + len))
static basic_json from_bjdata(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_bjdata(ptr, ptr + len, strict, allow_exceptions);
}
#endif
/// @brief create a JSON value from an input in BON8 format
/// @sa https://json.nlohmann.me/api/basic_json/from_bon8/
template<typename InputType>
@@ -5958,6 +6149,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bon8, strict, allow_exceptions);
}
template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bon8(ptr, ptr + len))
static basic_json from_bon8(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_bon8(ptr, ptr + len, strict, allow_exceptions);
}
#endif
/// @brief create a JSON value from an input in BSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename InputType>
@@ -5989,18 +6194,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json from_bson(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_bson(ptr, ptr + len, strict, allow_exceptions);
}
#endif
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))
static basic_json from_bson(detail::span_input_adapter&& i,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_binary_impl(i.get(), input_format_t::bson, strict, allow_exceptions);
}
#endif
/// @}
//////////////////////////
@@ -6020,9 +6233,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.get_unchecked(this);
}
#endif
/// @brief access specified element via JSON Pointer
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
@@ -6034,9 +6251,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.get_unchecked(this);
}
#endif
/// @brief access specified element via JSON Pointer
/// @sa https://json.nlohmann.me/api/basic_json/at/
@@ -6048,9 +6269,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.get_checked(this);
}
#endif
/// @brief access specified element via JSON Pointer
/// @sa https://json.nlohmann.me/api/basic_json/at/
@@ -6062,9 +6287,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.get_checked(this);
}
#endif
/// @brief return flattened JSON value
/// @sa https://json.nlohmann.me/api/basic_json/flatten/
@@ -7041,6 +7270,18 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j)
return j.dump();
}
#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO)
/// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&))
/// @sa https://json.nlohmann.me/api/operator_ltlt/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete;
/// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&))
/// @sa https://json.nlohmann.me/api/operator_gtgt/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete;
#endif
NLOHMANN_JSON_NAMESPACE_END
///////////////////////
+2 -4
View File
@@ -592,10 +592,8 @@ class basic_json_view
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;
// the compact text is about as long as the source text of the value
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 64;
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
return out;
}
+7680
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+15 -4
View File
@@ -67,6 +67,10 @@
#define JSON_STRICT_BINARY_UTF8 0
#endif
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -109,14 +113,20 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
#else
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -126,7 +136,8 @@
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+9 -522
View File
@@ -1218,14 +1218,12 @@ class builder
if (p[1] == '/')
{
p += 2;
// (as in parse(), a null byte is the end of the input, so it is
// left for the caller to see)
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
{
++p;
}
if (NulIsEnd && p != e && *p == 0)
{
++p; // as in parse(), a null byte ends the comment like a line break
}
return p;
}
if (p[1] == '*')
@@ -5409,11 +5407,7 @@ namespace view
{
/// append-only output buffer: writes through a raw pointer into a string that
/// is resized ahead, and trimmed by finish(). The estimate is reserved, and the
/// string grows in steps of 64 KiB within it: resize() fills the new bytes
/// with zeros (before C++23, a string cannot grow without), and a small step
/// is filled while the writer is about to use it, in the cache, instead of
/// filling the whole estimate in memory first.
/// is resized ahead, and trimmed by finish()
template<typename StringType>
class output_buffer
{
@@ -5457,49 +5451,17 @@ class output_buffer
m_pos += n;
}
/// the write position and the end of the writable space, for a writer
/// that keeps the position in a local variable (set_cursor() hands it back)
char* cursor() const noexcept
{
return m_pos;
}
char* limit() const noexcept
{
return m_end;
}
void set_cursor(char* p) noexcept
{
m_pos = p;
}
private:
/// the size of a growth step (a function: std::min() takes a reference,
/// which a static constexpr member does not have before C++17)
static constexpr std::size_t step() noexcept
{
return 65536;
}
static StringType& sized(StringType& out, std::size_t estimate)
{
out.reserve(estimate);
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
out.resize((std::max)(estimate, static_cast<std::size_t>(64)));
return out;
}
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
{
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good
// estimate is never copied to a larger allocation)
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
if (size > m_out.capacity())
{
m_out.reserve((std::max)(m_out.capacity() * 2, size));
}
m_out.resize(size);
m_out.resize((std::max)(m_out.size() * 2, used + n + 256));
m_pos = &m_out[0] + used;
m_end = &m_out[0] + m_out.size();
}
@@ -5509,105 +5471,6 @@ class output_buffer
char* m_end;
};
/// The length of the run at s that dump() writes unchanged without
/// ensure_ascii: all bytes but quotes, backslashes, and control characters.
/// Unlike detail::string_bulk_run(), non-ASCII bytes are not validated: the
/// strings of a document are valid UTF-8 (a damaged image loaded with
/// image_check::bounds can have others, which are then written unchanged).
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
{
constexpr std::uint64_t ones = 0x0101010101010101ull;
constexpr std::uint64_t high = 0x8080808080808080ull;
std::size_t i = 0;
for (; i + 8 <= n; i += 8)
{
const std::uint64_t v = read_eight_bytes(s + i);
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
const std::uint64_t stop = (((q - ones) & ~q) | ((b - ones) & ~b) | ((v - 0x2020202020202020ull) & ~v)) & high;
if (stop != 0)
{
// the lowest flagged byte is the first stop: borrows only flag bytes above a true one
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
}
}
for (; i < n; ++i)
{
if (s[i] == '"' || s[i] == '\\' || s[i] < 0x20)
{
return i;
}
}
return n;
}
/// A stack that starts in a buffer of the caller (a local array) and moves to
/// the heap (a vector of the caller) only when that is full, so that dumps of
/// shallow documents need no allocation. The top is a pointer, as in
/// std::vector. The address of the stack never escapes (the growth gets the
/// vector and returns the new storage), so its pointers stay in registers.
template<typename T>
class small_stack
{
public:
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(&heap)
{}
small_stack(const small_stack&) = delete;
small_stack(small_stack&&) = delete;
small_stack& operator=(const small_stack&) = delete;
small_stack& operator=(small_stack&&) = delete;
~small_stack() = default;
NLOHMANN_VIEW_ALWAYS_INLINE void push_back(const T& x)
{
if (NLOHMANN_VIEW_UNLIKELY(m_top == m_end))
{
const std::size_t used = size();
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
m_begin = grow(*m_heap, m_begin, used, capacity);
m_top = m_begin + used;
m_end = m_begin + capacity;
}
*m_top++ = x;
}
NLOHMANN_VIEW_ALWAYS_INLINE T& back() noexcept
{
return m_top[-1];
}
NLOHMANN_VIEW_ALWAYS_INLINE void pop_back() noexcept
{
--m_top;
}
NLOHMANN_VIEW_ALWAYS_INLINE bool empty() const noexcept
{
return m_top == m_begin;
}
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t size() const noexcept
{
return static_cast<std::size_t>(m_top - m_begin);
}
private:
/// the used entries moved to heap storage of the given capacity
NLOHMANN_VIEW_NOINLINE static T* grow(std::vector<T>& heap, const T* begin, std::size_t used, std::size_t capacity)
{
std::vector<T> bigger(capacity);
std::copy(begin, begin + used, bigger.begin());
heap.swap(bigger);
return heap.data();
}
T* m_begin;
T* m_top;
T* m_end;
std::vector<T>* m_heap;
};
/// how the view's dump() writes a value
struct dump_style
{
@@ -5642,18 +5505,6 @@ class view_serializer
void dump(const node* root)
{
if (!m_style.pretty && !m_style.ensure_ascii)
{
if (m_style.source_numbers)
{
dump_compact<true>(root);
}
else
{
dump_compact<false>(root);
}
return;
}
struct frame
{
const node* pos; ///< next element, or key of the next member
@@ -5661,9 +5512,7 @@ class view_serializer
bool object;
bool first; ///< nothing written yet
};
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::vector<frame> heap;
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
std::vector<frame> stack;
const node* n = root;
for (;;)
{
@@ -5734,289 +5583,6 @@ class view_serializer
}
private:
/*!
@brief the compact output without ensure_ascii (the default dump())
The same walk as dump(), with the write position in a local variable
(stores through char pointers would otherwise force a reload of the
buffer's members after each one), and with strings and number tokens of
the source copied by fixed-size moves of 32 bytes where the source has
that many bytes left, instead of a library call per token. The buffer
keeps 64 bytes of slack for the overshoot.
*/
/// a string that is not a plain string of the source (decoded, or written
/// by an edit), without ensure_ascii: runs without characters to escape
/// are copied
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
{
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
m_out.put('"');
for (std::size_t i = 0; i < n.len;)
{
const std::size_t run = plain_output_run(s + i, n.len - i);
if (run != 0)
{
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
i += run;
continue;
}
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
++i;
}
m_out.put('"');
}
/// the copies of dump_compact() that are not fixed-size moves (long
/// strings, or near the end of the source); out of line, so that the
/// compiler does not merge the fixed-size moves into this call
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
{
std::memcpy(to, from, n);
}
template<bool SourceNumbers>
void dump_compact(const node* root)
{
struct frame
{
const node* pos; ///< (editable documents) next element, or key of the next member
const node* end;
bool object;
};
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::vector<frame> heap;
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
const char* const src = m_doc.src;
const char* const src_end = src + m_doc.size;
char* w = m_out.cursor();
char* lim = m_out.limit();
// room for n bytes and the slack
const auto room = [&](std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(lim - w) < n + 64))
{
m_out.set_cursor(w);
m_out.reserve(n + 64);
w = m_out.cursor();
lim = m_out.limit();
}
};
// copy n bytes of the source (after room(n))
const auto copy = [&](const char* from, std::size_t n)
{
if (n <= 32 && src_end - from >= 32)
{
std::memcpy(w, from, 32);
}
else if (n <= 256 && src_end - from >= static_cast<std::ptrdiff_t>(n) + 32)
{
for (std::size_t i = 0; i < n; i += 32)
{
std::memcpy(w + i, from + i, 32);
}
}
else
{
copy_long(w, from, n);
}
w += n;
};
// a literal of n bytes (after room(n))
const auto literal = [&](const char* text, std::size_t n)
{
std::memcpy(w, text, n);
w += n;
};
// a string that is not a plain string of the source (out of line, so
// that the cursor stays in a register here)
const auto escaped = [&](const node & n)
{
m_out.set_cursor(w);
write_decoded(n);
w = m_out.cursor();
lim = m_out.limit();
};
// Read-only documents: the elements of a container follow it in the
// node array, so the walk goes through the array in order, and a
// frame only needs the end of its container. Editable documents: the
// elements of a moved container live elsewhere, so a frame keeps the
// position of the next element (see navigation).
// The innermost open container is kept in registers (cur; end ==
// nullptr: none), the stack holds the ones around it.
frame cur{nullptr, nullptr, false};
const node* n = root;
for (;;)
{
// write the value at n (read-only documents: and advance n)
bool opened = false;
switch (static_cast<value_t>(n->kind))
{
case value_t::string:
if ((n->flags & node_flags::storage) == 0)
{
room(n->len + 2);
*w++ = '"';
copy(src + n->off, n->len);
*w++ = '"';
}
else
{
escaped(*n);
}
break;
case value_t::number_integer:
case value_t::number_unsigned:
{
const std::uint32_t len = number_length(*n);
room(len);
if (Editable && (n->flags & node_flags::storage) != 0)
{
copy_long(w, m_doc.str(*n), len); // a canonical token written by an edit
w += len;
break;
}
const char* const token = src + n->off;
if (!SourceNumbers && NLOHMANN_VIEW_UNLIKELY(len == 2 && token[0] == '-' && token[1] == '0'))
{
*w++ = '0'; // parse() reads -0 as the integer 0
}
else
{
copy(token, len);
}
break;
}
case value_t::number_float:
if (SourceNumbers && (n->flags & node_flags::storage) != node_flags::edited)
{
room(n->len);
copy(src + n->off, n->len);
}
else if (std::is_same<number_float_t, double>::value)
{
room(64);
w = write_double_at(w, *n);
}
else
{
m_out.set_cursor(w);
write_float_node(*n);
w = m_out.cursor();
lim = m_out.limit();
}
break;
case value_t::boolean:
room(8);
if ((n->flags & node_flags::is_true) != 0)
{
literal("true", 4);
}
else
{
literal("false", 5);
}
break;
case value_t::object:
case value_t::array:
{
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
room(8);
if (n->len == 0)
{
literal(object ? "{}" : "[]", 2);
}
else
{
*w++ = object ? '{' : '[';
stack.push_back(cur);
if (Editable)
{
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
}
else
{
cur = frame{nullptr, n + n->next, object};
}
opened = true;
}
break;
}
case value_t::null:
room(8);
literal("null", 4);
break;
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
case value_t::discarded: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
}
if (!Editable)
{
++n; // the next node: the first element of an opened container, or the node after a scalar
}
// go to the next value: close finished containers, then separate
// (a container just opened has an element)
if (!opened)
{
for (;;)
{
if (cur.end == nullptr)
{
m_out.set_cursor(w);
m_out.finish();
return;
}
if ((Editable ? cur.pos : n) != cur.end)
{
break;
}
room(1);
*w++ = cur.object ? '}' : ']';
cur = stack.back();
stack.pop_back();
}
room(1);
*w++ = ',';
}
const node* const at = Editable ? cur.pos : n;
if (cur.object)
{
const node& key = *at;
if ((key.flags & node_flags::storage) == 0)
{
room(key.len + 3);
*w++ = '"';
copy(src + key.off, key.len);
w[0] = '"';
w[1] = ':';
w += 2;
}
else
{
escaped(key);
room(1);
*w++ = ':';
}
if (Editable)
{
n = nav::value(at + 1);
cur.pos = document_data::after(at + 1);
}
else
{
++n;
}
}
else if (Editable)
{
n = nav::value(at);
cur.pos = document_data::after(at);
}
}
}
void newline(std::size_t level)
{
if (m_style.pretty)
@@ -6068,7 +5634,7 @@ class view_serializer
}
else
{
write_float_node(n);
write_float(float_value<number_float_t>(m_doc, n));
}
break;
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
@@ -6080,83 +5646,6 @@ class view_serializer
}
}
/// a float node as dump() writes it
void write_float_node(const node& n)
{
write_float_node(n, std::is_same<number_float_t, double> {});
}
void write_float_node(const node& n, std::false_type /*other*/)
{
write_float(float_value<number_float_t>(m_doc, n));
}
void write_float_node(const node& n, std::true_type /*double*/)
{
m_out.reserve(64);
m_out.set_cursor(write_double_at(m_out.cursor(), n));
}
/*!
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
A token of at most 15 significant digits is written from its digits,
without a conversion: two decimals of at most 15 digits are farther
apart than the rounding interval of a (normal) double (the argument
behind DBL_DIG), so the token's digits are the shortest ones of its
double, which the library's conversion writes (Zmij). Other tokens are
converted from the digits already read.
*/
char* write_double_at(char* w, const node& n)
{
const unsigned int_digits = n.extra & 0xFFu;
const unsigned frac_digits = n.extra >> 8u;
if ((n.flags & node_flags::storage) != node_flags::edited && int_digits + frac_digits <= 19)
{
const auto* const first = reinterpret_cast<const unsigned char*>(m_doc.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const float_significand d = layout_decimal(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
// (the exponent keeps the value far from subnormals and overflow)
if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290)
{
*w = '-';
w += d.negative ? 1 : 0;
// (without leading zeros, all digits of the token count)
const unsigned char lead = first[d.negative ? 1 : 0];
return lead != '0' ? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(int_digits + frac_digits), static_cast<int>(d.exponent))
: ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(d.exponent));
}
return write_double_value_at(w, decimal_to_float<double>(d)); // (without reading the token again)
}
return write_double_value_at(w, static_cast<double>(float_value<number_float_t>(m_doc, n)));
}
/// n bytes of text at w
static char* write_text_at(char* w, const char* text, std::size_t n) noexcept
{
std::memcpy(w, text, n);
return w + n;
}
/// a double as dump() writes it, at w (64 bytes of room)
static char* write_double_value_at(char* w, double x)
{
// (from the bits: without the checks of to_chars())
std::uint64_t bits = 0;
std::memcpy(&bits, &x, sizeof(bits));
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
{
return write_text_at(w, "null", 4);
}
*w = '-';
w += bits >> 63u;
bits &= ~(std::uint64_t{1} << 63u);
if (bits == 0)
{
return write_text_at(w, "0.0", 3);
}
return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits));
}
/// as serializer::dump_float()
void write_float(number_float_t x)
{
@@ -7075,10 +6564,8 @@ class basic_json_view
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;
// the compact text is about as long as the source text of the value
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 64;
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
return out;
}
+32
View File
@@ -8,6 +8,7 @@ set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_Te
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
if (NOT "" STREQUAL "$ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS}")
@@ -288,6 +289,33 @@ elseif(NOT json_32bit_test)
list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp)
endif()
# with JSON_TestShard=<index>/<count>, keep every <count>-th unit test file,
# starting at <index> (the glob is sorted, so the split is stable)
set(test_shard_index 0)
if(NOT "${JSON_TestShard}" STREQUAL "")
if(NOT JSON_TestShard MATCHES "^([0-9]+)/([1-9][0-9]*)$")
message(FATAL_ERROR "JSON_TestShard must be <index>/<count>, e.g. 0/2, not '${JSON_TestShard}'.")
endif()
set(test_shard_index ${CMAKE_MATCH_1})
set(test_shard_count ${CMAKE_MATCH_2})
if(NOT test_shard_index LESS test_shard_count)
message(FATAL_ERROR "JSON_TestShard: the index must be less than the count, not '${JSON_TestShard}'.")
endif()
list(LENGTH files test_file_count)
set(shard_files "")
set(file_position 0)
foreach(file ${files})
math(EXPR file_shard "${file_position} % ${test_shard_count}")
if(file_shard EQUAL test_shard_index)
list(APPEND shard_files ${file})
endif()
math(EXPR file_position "${file_position} + 1")
endforeach()
set(files ${shard_files})
list(LENGTH files shard_file_count)
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
endif()
foreach(file ${files})
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
endforeach()
@@ -305,6 +333,9 @@ if(json_32bit_test_only)
return()
endif()
# the following variants of single test files are only built in the first shard
if(test_shard_index EQUAL 0)
# test legacy comparison of discarded values
json_test_set_test_options(test-comparison_legacy
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
@@ -351,6 +382,7 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC)
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
endif()
endif()
# *DO NOT* use json_test_set_test_options() below this line
+4
View File
@@ -48,6 +48,10 @@ TEST_CASE("default namespace")
expected += "_sbu8";
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
expected += "_ekmo";
#endif
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
+4
View File
@@ -49,6 +49,10 @@ TEST_CASE("default namespace without version component")
expected += "_sbu8";
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
expected += "_ekmo";
#endif
expected += "::basic_json";
// fallback for Clang
-1
View File
@@ -1 +0,0 @@
build/
-90
View File
@@ -1,90 +0,0 @@
# json_view compared with other libraries
The in-tree benchmarks in [`tests/benchmarks`](../README.md) measure `json_document` against `json::parse` only. The
programs here compare it with [yyjson](https://github.com/ibireme/yyjson),
[simdjson](https://github.com/simdjson/simdjson), and [Boost.JSON](https://github.com/boostorg/json): the question
users ask when they pick a library. They are not built by CMake or run by CI.
## Reproducing the numbers
`compare.py` builds the programs against `include/` of this checkout, runs them, and writes the results together with
everything needed to reproduce them to `results/<date>-<host>.md` (and `.csv`): the date, the commit, the CPU, the
OS, the compiler, the flags, and the versions of all libraries.
```sh
python3 tests/benchmarks/json_view/compare.py --data <json_test_data directory> [--native] [--rounds 30]
```
- `--data` is the downloaded [test data](https://github.com/nlohmann/json_test_data), e.g. the `test_files` directory
of a CMake build directory. It needs `nativejson-benchmark/{twitter,citm_catalog,canada}.json` and
`jeopardy/jeopardy.json`.
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
- Only the Python 3 standard library is used; a C++17 compiler is needed (`CXX` and `CC` are honored).
For numbers worth publishing, use a quiet machine (see [Getting stable numbers](../README.md#getting-stable-numbers)),
the default 30 rounds or more, and `--native` only if the other libraries were built for the same CPU.
### On GitHub-hosted runners
The workflow [json_view benchmarks](../../../.github/workflows/json_view_benchmarks.yml) runs `compare.py --download`
on demand: by hand (Actions → "json_view benchmarks" → "Run workflow"), on an x86-64 or AArch64 Ubuntu runner with GCC
or Clang, or when a pull request gets the label `benchmark`, on both architectures with GCC. The results appear as the
job summary and as an artifact. Shared runners are noisy, so these numbers show
where `json_view` stands on another architecture; they are not meant for publication.
## What is measured
`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
JSON-RPC request (`rpc`):
| workload | what it does |
|---|---|
| parse | build and free a document |
| traverse | parse, then visit every value, convert every number, touch every string and key |
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
| dump | serialize a parsed document (compact) |
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
yyjson with numbers read as raw text (`YYJSON_READ_NUMBER_AS_RAW`), without converting them.
`bench_edit.cpp` measures read-modify-write: parse, apply the same logical edits with each library's own API, and
serialize (compact). Workloads: `patch` (a handful of edits at fixed places) and `update` (edits in every record).
An editable `json_document` edits in place; yyjson copies its immutable document into a mutable one first
(`yyjson_doc_mut_copy`); Boost.JSON and `json::parse` build mutable DOMs; simdjson cannot edit a document. All
outputs are checked to describe the same value.
Before anything is timed, all engines must accept each document and agree on the traversal: the number of values, the
bytes of all strings and keys, and the sum of all numbers. All engines run interleaved in every round, and the best
round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`). Each timed
call follows an untimed call of the same engine: otherwise the engine after `json::parse` pays for the allocator
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
slower on citm_catalog traverse).
The engines do not all offer the same features, which the numbers should be read with:
| engine | document | random access | editable | notes |
|---|---|---|---|---|
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
| Boost.JSON | owning, mutable DOM | yes | yes | monotonic resource |
| `json::parse` | owning, mutable DOM | yes | yes | |
Reusing memory matters as much as the parser. simdjson DOM reuses its parser, so it writes into memory it already
touched; a fresh `json_view` document or yyjson document gets new memory for every parse. On Linux, glibc returns large
blocks to the system when they are freed, so every fresh parse of a large document pays a page fault per 4 KiB page:
on x86-64 Linux, a fresh `json_view` parse of jeopardy took about twice as long as a reused one. On macOS on Apple
silicon, with 16 KiB pages, the difference is much smaller. Compare "json_view (reused)" with "simdjson DOM", and the
fresh `json_view` with "simdjson DOM (fresh)" and yyjson.
## Published results
Results are only published with the file `compare.py` wrote, which names the machine and the versions; see
`results/`. Numbers from one machine and compiler do not carry over to another: rerun the script.
-342
View File
@@ -1,342 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// Corpus benchmark: the read-only workloads of bench_view.cpp on any list of
// JSON files (for example the benchmark sets of simdjson and yyjson).
//
// ./bench_corpus [--rounds N] file...
//
// For every file, all engines must accept it and agree on a traversal (value
// count, string bytes, sum of numbers) before anything is timed. Workloads:
// parse (build and free a document), traverse (visit every value, convert
// every number), dump (compact), and for json_view also dump with the source
// number text, compared with yyjson writing numbers read as raw text
// (YYJSON_READ_NUMBER_AS_RAW). Results go to bench_corpus.csv.
#include <nlohmann/json_view.hpp>
#if JSON_VIEW_BENCH_BOOST
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#endif
#include <simdjson.h>
#include <yyjson.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <functional>
#include <sstream>
#include <string>
#include <vector>
using nlohmann::json;
using nlohmann::json_document;
using nlohmann::json_view;
static volatile double g_sink;
struct stats
{
double num = 0;
std::size_t str = 0, nodes = 0;
};
static void walk(json_view v, stats& st)
{
++st.nodes;
switch (v.type())
{
case json::value_t::object:
for (auto it = v.begin(); it != v.end(); ++it)
{
st.str += it.key().size();
walk(*it, st);
}
break;
case json::value_t::array:
for (const json_view e : v)
{
walk(e, st);
}
break;
case json::value_t::string:
st.str += v.get_string().size();
break;
case json::value_t::number_integer:
st.num += static_cast<double>(v.get<std::int64_t>());
break;
case json::value_t::number_unsigned:
st.num += static_cast<double>(v.get<std::uint64_t>());
break;
case json::value_t::number_float:
st.num += v.get<double>();
break;
default:
break;
}
}
static void walk(yyjson_val* v, stats& st)
{
++st.nodes;
switch (yyjson_get_type(v))
{
case YYJSON_TYPE_OBJ:
{
std::size_t idx, max;
yyjson_val* k, * val;
yyjson_obj_foreach(v, idx, max, k, val)
{
st.str += yyjson_get_len(k);
walk(val, st);
}
break;
}
case YYJSON_TYPE_ARR:
{
std::size_t idx, max;
yyjson_val* val;
yyjson_arr_foreach(v, idx, max, val)
{
walk(val, st);
}
break;
}
case YYJSON_TYPE_STR:
st.str += yyjson_get_len(v);
break;
case YYJSON_TYPE_NUM:
st.num += yyjson_is_sint(v) ? static_cast<double>(yyjson_get_sint(v)) : yyjson_is_uint(v) ? static_cast<double>(yyjson_get_uint(v)) : yyjson_get_real(v);
break;
default:
break;
}
}
static void walk(simdjson::dom::element e, stats& st)
{
++st.nodes;
switch (e.type())
{
case simdjson::dom::element_type::OBJECT:
for (auto f : simdjson::dom::object(e))
{
st.str += f.key.size();
walk(f.value, st);
}
break;
case simdjson::dom::element_type::ARRAY:
for (auto c : simdjson::dom::array(e))
{
walk(c, st);
}
break;
case simdjson::dom::element_type::STRING:
st.str += std::string_view(e).size();
break;
case simdjson::dom::element_type::INT64:
st.num += static_cast<double>(int64_t(e));
break;
case simdjson::dom::element_type::UINT64:
st.num += static_cast<double>(uint64_t(e));
break;
case simdjson::dom::element_type::DOUBLE:
st.num += double(e);
break;
default:
break;
}
}
#if JSON_VIEW_BENCH_BOOST
static void walk(const boost::json::value& v, stats& st)
{
++st.nodes;
switch (v.kind())
{
case boost::json::kind::object:
for (const auto& kv : v.get_object())
{
st.str += kv.key().size();
walk(kv.value(), st);
}
break;
case boost::json::kind::array:
for (const auto& c : v.get_array())
{
walk(c, st);
}
break;
case boost::json::kind::string:
st.str += v.get_string().size();
break;
case boost::json::kind::int64:
st.num += static_cast<double>(v.get_int64());
break;
case boost::json::kind::uint64:
st.num += static_cast<double>(v.get_uint64());
break;
case boost::json::kind::double_:
st.num += v.get_double();
break;
default:
break;
}
}
#endif
static std::string slurp(const std::string& p)
{
std::ifstream f(p, std::ios::binary);
if (!f)
{
std::fprintf(stderr, "cannot open %s\n", p.c_str());
std::exit(1);
}
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
}
static bool same(const stats& a, const stats& b)
{
return a.nodes == b.nodes && a.str == b.str && (a.num == b.num || std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num));
}
int main(int argc, char** argv)
{
int rounds = 0; // 0: by size
std::vector<std::string> files;
for (int i = 1; i < argc; ++i)
{
if (std::strcmp(argv[i], "--rounds") == 0 && i + 1 < argc)
{
rounds = std::atoi(argv[++i]);
}
else
{
files.push_back(argv[i]);
}
}
std::FILE* csv = std::fopen("bench_corpus.csv", "w");
std::fprintf(csv, "file,bytes,workload,engine,ns\n");
json_document reused;
simdjson::dom::parser sj;
for (const auto& path : files)
{
const std::string s = slurp(path);
const std::string name = path.substr(path.rfind('/') + 1);
const simdjson::padded_string ps(s);
// all engines must agree before timing
stats a, b, c, d;
const json_document doc = json_document::parse(s);
walk(doc.root(), a);
yyjson_doc* y = yyjson_read(s.data(), s.size(), 0);
auto sjr = sj.parse(ps);
#if JSON_VIEW_BENCH_BOOST
boost::json::parse_options opt;
opt.numbers = boost::json::number_precision::precise;
boost::json::monotonic_resource mr0;
const boost::json::value bv = boost::json::parse(s, &mr0, opt);
#endif
if (y == nullptr || sjr.error())
{
std::printf("%-34s skipped (an engine rejects it)\n", name.c_str());
yyjson_doc_free(y);
continue;
}
walk(yyjson_doc_get_root(y), b);
walk(sjr.value_unsafe(), c);
#if JSON_VIEW_BENCH_BOOST
walk(bv, d);
#else
d = a;
#endif
yyjson_doc_free(y);
const bool ok = same(a, b) && same(a, c) && same(a, d);
const int r = rounds > 0 ? rounds : static_cast<int>(std::max<std::size_t>(3, std::min<std::size_t>(60, 400000000 / (s.size() + 1))));
struct engine
{
std::string name;
std::function<void()> fn;
};
json_document vd = json_document::parse(s);
yyjson_doc* yd = yyjson_read(s.data(), s.size(), 0);
yyjson_doc* yd_raw = yyjson_read(s.data(), s.size(), YYJSON_READ_NUMBER_AS_RAW);
simdjson::dom::parser sjd;
const simdjson::dom::element se = sjd.parse(ps).value_unsafe();
const std::vector<std::pair<std::string, std::vector<engine>>> workloads =
{
{
"parse", {
{"json_view", [&] { auto x = json_document::parse(s); g_sink = static_cast<double>(x.node_count()); }},
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(x)); yyjson_doc_free(x); }},
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
#if JSON_VIEW_BENCH_BOOST
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
#endif
}
},
{
"traverse", {
{"json_view", [&] { auto x = json_document::parse(s); stats st; walk(x.root(), st); g_sink = st.num; }},
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(x), st); g_sink = st.num; yyjson_doc_free(x); }},
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
#if JSON_VIEW_BENCH_BOOST
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
#endif
}
},
{
"dump", {
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
{"json_view (source numbers)", [&] { std::string o = vd.root().dump(-1, ' ', false, json_view::number_format::source); g_sink = static_cast<double>(o.size()); }},
{"yyjson (raw numbers)", [&] { std::size_t n = 0; char* o = yyjson_write(yd_raw, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
}
},
};
std::printf("%-34s %9zu B%s\n", name.c_str(), s.size(), ok ? "" : " [ENGINES DISAGREE]");
for (const auto& wl : workloads)
{
std::vector<double> best(wl.second.size(), 1e300);
for (int i = 0; i < r; ++i)
{
for (std::size_t k = 0; k < wl.second.size(); ++k)
{
// an untimed call first: whatever the previous engine left to the allocator
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
wl.second[k].fn();
const auto t0 = std::chrono::steady_clock::now();
wl.second[k].fn();
best[k] = std::min(best[k], std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count());
}
}
std::printf(" %-9s", wl.first.c_str());
for (std::size_t k = 0; k < wl.second.size(); ++k)
{
std::printf(" %s %.2f GB/s (%.2fx)", wl.second[k].name.c_str(), static_cast<double>(s.size()) / best[k], best[k] / best[0]);
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
}
std::printf("\n");
std::fflush(stdout);
}
yyjson_doc_free(yd);
yyjson_doc_free(yd_raw);
}
std::fclose(csv);
}
-583
View File
@@ -1,583 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// Read-modify-write benchmark: parse a document, apply the same logical edits
// with each library's own API, and serialize it (compact).
//
// json_view json_editable_document: edits in place, unchanged values stay in the index
// yyjson yyjson_read + yyjson_doc_mut_copy (the way to edit a parsed document)
// Boost.JSON parse into a mutable DOM (monotonic resource, precise numbers), serialize
// json::parse nlohmann::json today
// simdjson has no mutable document and is not part of this comparison.
//
// Workloads:
// patch a handful of edits at fixed places (scalars, a new member, a new array element)
// update edits in every record (twitter: 100 statuses, citm: 243 performances,
// canada: 480 rings, jeopardy: 216,930 questions): set scalars, erase a
// member, add a member (canada: replace the first point of every ring)
//
// Build: see README.md (same flags as bench_view.cpp).
#include <nlohmann/json_view.hpp>
#if JSON_VIEW_BENCH_BOOST
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#endif
#include <yyjson.h>
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <sstream>
using nlohmann::json;
using nlohmann::json_editable_document;
using nlohmann::json_editable_view;
#if JSON_VIEW_BENCH_BOOST
namespace bj = boost::json;
#endif
static volatile std::size_t g_sink;
// ---------------- json_view ----------------
static std::string edit_view(const std::string& name, const std::string& s, bool update)
{
json_editable_document d = json_editable_document::parse(s);
const json_editable_view r = d.root();
if (name == "twitter")
{
if (update)
{
std::int64_t i = 0;
for (const json_editable_view st : r["statuses"])
{
d.set(st, "retweet_count", i++);
d.set(st, "favorited", true);
d.set(st, "text", "redacted");
d.erase(st, "entities");
d.set(st, "edited", true);
}
}
else
{
d.set(r["search_metadata"], "count", 200);
d.set(r["statuses"][0], "text", "patched");
d.set(r["statuses"][0]["user"], "followers_count", 1);
d.set(r["statuses"][99], "favorited", true);
d.set(r, "patched", true);
}
}
else if (name == "citm_catalog")
{
if (update)
{
for (const json_editable_view p : r["performances"])
{
d.set(p, "name", "performance");
d.set(p, "start", p["start"].get<std::int64_t>() + 1);
d.erase(p, "seatMapImage");
d.set(p, "edited", true);
}
}
else
{
d.set(r["events"]["138586341"], "name", "patched");
d.set(r["performances"][0], "start", 0);
d.set(r["venueNames"], "PLEYEL_PLEYEL", "Salle");
d.set(r, "patched", true);
}
}
else if (name == "canada")
{
const json_editable_view coords = r["features"][0]["geometry"]["coordinates"];
if (update)
{
for (const json_editable_view ring : coords)
{
d.set(ring, 0, json::array({0.5, 0.5}));
}
}
else
{
d.set(r["features"][0]["properties"], "name", "patched");
d.set(r, "type", "FeatureCollection2");
d.set(coords[0], 0, json::array({0.0, 0.0}));
}
}
else if (name == "jeopardy")
{
if (update)
{
for (const json_editable_view q : r)
{
d.set(q, "value", "$1");
d.erase(q, "air_date");
}
}
else
{
d.set(r[0], "value", "$0");
d.set(r[100000], "answer", "patched");
d.set(r[216929], "round", "x");
d.push_back(r, json::object({{"category", "NEW"}, {"value", "$5"}}));
}
}
else if (name == "status")
{
d.set(r, "retweet_count", 1);
d.set(r["user"], "name", "x");
}
else if (name == "rpc")
{
d.set(r, "id", 4);
d.set(r["params"], "subtrahend", 24);
}
return r.dump();
}
// ---------------- nlohmann::json ----------------
static std::string edit_json(const std::string& name, const std::string& s, bool update)
{
json r = json::parse(s);
if (name == "twitter")
{
if (update)
{
std::int64_t i = 0;
for (auto& st : r["statuses"])
{
st["retweet_count"] = i++;
st["favorited"] = true;
st["text"] = "redacted";
st.erase("entities");
st["edited"] = true;
}
}
else
{
r["search_metadata"]["count"] = 200;
r["statuses"][0]["text"] = "patched";
r["statuses"][0]["user"]["followers_count"] = 1;
r["statuses"][99]["favorited"] = true;
r["patched"] = true;
}
}
else if (name == "citm_catalog")
{
if (update)
{
for (auto& p : r["performances"])
{
p["name"] = "performance";
p["start"] = p["start"].get<std::int64_t>() + 1;
p.erase("seatMapImage");
p["edited"] = true;
}
}
else
{
r["events"]["138586341"]["name"] = "patched";
r["performances"][0]["start"] = 0;
r["venueNames"]["PLEYEL_PLEYEL"] = "Salle";
r["patched"] = true;
}
}
else if (name == "canada")
{
json& coords = r["features"][0]["geometry"]["coordinates"];
if (update)
{
for (auto& ring : coords)
{
ring[0] = json::array({0.5, 0.5});
}
}
else
{
r["features"][0]["properties"]["name"] = "patched";
r["type"] = "FeatureCollection2";
coords[0][0] = json::array({0.0, 0.0});
}
}
else if (name == "jeopardy")
{
if (update)
{
for (auto& q : r)
{
q["value"] = "$1";
q.erase("air_date");
}
}
else
{
r[0]["value"] = "$0";
r[100000]["answer"] = "patched";
r[216929]["round"] = "x";
r.push_back(json::object({{"category", "NEW"}, {"value", "$5"}}));
}
}
else if (name == "status")
{
r["retweet_count"] = 1;
r["user"]["name"] = "x";
}
else if (name == "rpc")
{
r["id"] = 4;
r["params"]["subtrahend"] = 24;
}
return r.dump();
}
// ---------------- yyjson ----------------
static std::string edit_yyjson(const std::string& name, const std::string& s, bool update)
{
yyjson_doc* idoc = yyjson_read(s.data(), s.size(), 0);
yyjson_mut_doc* d = yyjson_doc_mut_copy(idoc, nullptr);
yyjson_doc_free(idoc);
yyjson_mut_val* r = yyjson_mut_doc_get_root(d);
auto get = [](yyjson_mut_val * o, const char* k)
{
return yyjson_mut_obj_get(o, k);
};
if (name == "twitter")
{
yyjson_mut_val* sts = get(r, "statuses");
if (update)
{
std::size_t idx, max;
yyjson_mut_val* st;
std::int64_t i = 0;
yyjson_mut_arr_foreach(sts, idx, max, st)
{
yyjson_mut_set_sint(get(st, "retweet_count"), i++);
yyjson_mut_set_bool(get(st, "favorited"), true);
yyjson_mut_set_str(get(st, "text"), "redacted");
yyjson_mut_obj_remove_key(st, "entities");
yyjson_mut_obj_add_bool(d, st, "edited", true);
}
}
else
{
yyjson_mut_set_sint(get(get(r, "search_metadata"), "count"), 200);
yyjson_mut_val* s0 = yyjson_mut_arr_get(sts, 0);
yyjson_mut_set_str(get(s0, "text"), "patched");
yyjson_mut_set_sint(get(get(s0, "user"), "followers_count"), 1);
yyjson_mut_set_bool(get(yyjson_mut_arr_get(sts, 99), "favorited"), true);
yyjson_mut_obj_add_bool(d, r, "patched", true);
}
}
else if (name == "citm_catalog")
{
if (update)
{
std::size_t idx, max;
yyjson_mut_val* p;
yyjson_mut_arr_foreach(get(r, "performances"), idx, max, p)
{
yyjson_mut_set_str(get(p, "name"), "performance");
yyjson_mut_val* start = get(p, "start");
yyjson_mut_set_sint(start, yyjson_mut_get_sint(start) + 1);
yyjson_mut_obj_remove_key(p, "seatMapImage");
yyjson_mut_obj_add_bool(d, p, "edited", true);
}
}
else
{
yyjson_mut_set_str(get(get(get(r, "events"), "138586341"), "name"), "patched");
yyjson_mut_set_sint(get(yyjson_mut_arr_get(get(r, "performances"), 0), "start"), 0);
yyjson_mut_set_str(get(get(r, "venueNames"), "PLEYEL_PLEYEL"), "Salle");
yyjson_mut_obj_add_bool(d, r, "patched", true);
}
}
else if (name == "canada")
{
yyjson_mut_val* f0 = yyjson_mut_arr_get(get(r, "features"), 0);
yyjson_mut_val* coords = get(get(f0, "geometry"), "coordinates");
if (update)
{
static const double half[2] = {0.5, 0.5};
std::size_t idx, max;
yyjson_mut_val* ring;
yyjson_mut_arr_foreach(coords, idx, max, ring)
{
yyjson_mut_arr_replace(ring, 0, yyjson_mut_arr_with_real(d, half, 2));
}
}
else
{
static const double zero[2] = {0.0, 0.0};
yyjson_mut_set_str(get(get(f0, "properties"), "name"), "patched");
yyjson_mut_set_str(get(r, "type"), "FeatureCollection2");
yyjson_mut_arr_replace(yyjson_mut_arr_get(coords, 0), 0, yyjson_mut_arr_with_real(d, zero, 2));
}
}
else if (name == "jeopardy")
{
if (update)
{
std::size_t idx, max;
yyjson_mut_val* q;
yyjson_mut_arr_foreach(r, idx, max, q)
{
yyjson_mut_set_str(get(q, "value"), "$1");
yyjson_mut_obj_remove_key(q, "air_date");
}
}
else
{
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 0), "value"), "$0");
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 100000), "answer"), "patched");
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 216929), "round"), "x");
yyjson_mut_val* o = yyjson_mut_obj(d);
yyjson_mut_obj_add_str(d, o, "category", "NEW");
yyjson_mut_obj_add_str(d, o, "value", "$5");
yyjson_mut_arr_append(r, o);
}
}
else if (name == "status")
{
yyjson_mut_set_sint(get(r, "retweet_count"), 1);
yyjson_mut_set_str(get(get(r, "user"), "name"), "x");
}
else if (name == "rpc")
{
yyjson_mut_set_sint(get(r, "id"), 4);
yyjson_mut_set_sint(get(get(r, "params"), "subtrahend"), 24);
}
std::size_t n = 0;
char* out = yyjson_mut_write(d, 0, &n);
std::string result(out, n);
std::free(out);
yyjson_mut_doc_free(d);
return result;
}
#if JSON_VIEW_BENCH_BOOST
// ---------------- Boost.JSON ----------------
static std::string edit_boost(const std::string& name, const std::string& s, bool update)
{
bj::monotonic_resource mr;
bj::parse_options opt;
opt.numbers = bj::number_precision::precise; // correctly rounded, like the others
bj::value v = bj::parse(s, &mr, opt);
bj::object* const obj = v.if_object(); // nullptr for jeopardy (an array)
if (name == "twitter")
{
bj::array& sts = (*obj)["statuses"].as_array();
if (update)
{
std::int64_t i = 0;
for (auto& e : sts)
{
bj::object& st = e.as_object();
st["retweet_count"] = i++;
st["favorited"] = true;
st["text"] = "redacted";
st.erase("entities");
st["edited"] = true;
}
}
else
{
(*obj)["search_metadata"].as_object()["count"] = 200;
bj::object& s0 = sts[0].as_object();
s0["text"] = "patched";
s0["user"].as_object()["followers_count"] = 1;
sts[99].as_object()["favorited"] = true;
(*obj)["patched"] = true;
}
}
else if (name == "citm_catalog")
{
if (update)
{
for (auto& e : (*obj)["performances"].as_array())
{
bj::object& p = e.as_object();
p["name"] = "performance";
p["start"] = p["start"].as_int64() + 1;
p.erase("seatMapImage");
p["edited"] = true;
}
}
else
{
(*obj)["events"].as_object()["138586341"].as_object()["name"] = "patched";
(*obj)["performances"].as_array()[0].as_object()["start"] = 0;
(*obj)["venueNames"].as_object()["PLEYEL_PLEYEL"] = "Salle";
(*obj)["patched"] = true;
}
}
else if (name == "canada")
{
bj::object& f0 = (*obj)["features"].as_array()[0].as_object();
bj::array& coords = f0["geometry"].as_object()["coordinates"].as_array();
if (update)
{
for (auto& ring : coords)
{
ring.as_array()[0] = bj::array({0.5, 0.5});
}
}
else
{
f0["properties"].as_object()["name"] = "patched";
(*obj)["type"] = "FeatureCollection2";
coords[0].as_array()[0] = bj::array({0.0, 0.0});
}
}
else if (name == "jeopardy")
{
bj::array& a = v.as_array();
if (update)
{
for (auto& e : a)
{
bj::object& q = e.as_object();
q["value"] = "$1";
q.erase("air_date");
}
}
else
{
a[0].as_object()["value"] = "$0";
a[100000].as_object()["answer"] = "patched";
a[216929].as_object()["round"] = "x";
a.push_back(bj::object({{"category", "NEW"}, {"value", "$5"}}));
}
}
else if (name == "status")
{
(*obj)["retweet_count"] = 1;
(*obj)["user"].as_object()["name"] = "x";
}
else if (name == "rpc")
{
(*obj)["id"] = 4;
(*obj)["params"].as_object()["subtrahend"] = 24;
}
return bj::serialize(v);
}
#endif
// ---------------- harness ----------------
static std::string slurp(const std::string& p)
{
std::ifstream f(p, std::ios::binary);
if (!f)
{
std::fprintf(stderr, "cannot open %s\n", p.c_str());
std::exit(1);
}
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
}
int main(int argc, char** argv)
{
if (argc < 2)
{
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds] [document]\n", argv[0]);
return 1;
}
const std::string T = std::string(argv[1]) + "/";
const int rounds = argc > 2 ? std::atoi(argv[2]) : 20;
const std::string only = argc > 3 ? argv[3] : "";
struct doc
{
std::string name, text;
int batch;
};
std::vector<doc> docs;
for (const char* f :
{"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"
})
{
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
}
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
docs.push_back({"rpc", R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 3})", 5000});
using fn = std::string (*)(const std::string&, const std::string&, bool);
const std::vector<std::pair<std::string, fn>> engines =
{
{"json_view", edit_view}, {"yyjson", edit_yyjson},
#if JSON_VIEW_BENCH_BOOST
{"Boost.JSON", edit_boost},
#endif
{"json::parse", edit_json}
};
std::FILE* csv = std::fopen("bench_edit.csv", "w");
std::fprintf(csv, "doc,bytes,workload,engine,ns\n");
for (const auto& dc : docs)
{
if (!only.empty() && dc.name != only)
{
continue;
}
for (const bool update :
{
false, true
})
{
if (update && (dc.name == "status" || dc.name == "rpc"))
{
continue;
}
// all engines must produce the same value
const json expected = json::parse(edit_json(dc.name, dc.text, update));
bool ok = true;
for (const auto& e : engines)
{
ok = ok && json::parse(e.second(dc.name, dc.text, update)) == expected;
}
std::vector<double> best(engines.size(), 1e300);
const int r = dc.text.size() > 10000000 ? std::max(3, rounds / 4) : rounds;
for (int i = 0; i < r; ++i)
{
for (std::size_t k = 0; k < engines.size(); ++k)
{
// an untimed call first: whatever the previous engine left to the allocator
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
g_sink = engines[k].second(dc.name, dc.text, update).size();
const auto t0 = std::chrono::steady_clock::now();
for (int b = 0; b < dc.batch; ++b)
{
g_sink = engines[k].second(dc.name, dc.text, update).size();
}
const double ns = std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch;
best[k] = std::min(best[k], ns);
}
}
const char* wl = update ? "update" : "patch";
std::printf("%-13s %-7s %s", dc.name.c_str(), wl, ok ? "" : "[OUTPUT MISMATCH] ");
for (std::size_t k = 0; k < engines.size(); ++k)
{
const double us = best[k] / 1e3;
std::printf(" %s %.*fus (%.2fx)", engines[k].first.c_str(), us < 10 ? 3 : (us < 1000 ? 1 : 0), us, best[k] / best[0]);
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", dc.name.c_str(), dc.text.size(), wl, engines[k].first.c_str(), best[k]);
}
std::printf("\n");
std::fflush(stdout);
}
}
std::fclose(csv);
}
-749
View File
@@ -1,749 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// Same-feature-set benchmark: read-only JSON documents with random access.
//
// json_view nlohmann/json_view.hpp (fresh document per parse / reused)
// yyjson yyjson_read(): immutable document, random access
// simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser
// per parse): immutable, random access
// references (different feature sets):
// simdjson OD On-Demand: forward-only, lazy
// Boost.JSON owning, mutable DOM (monotonic resource)
// json::parse owning, mutable DOM (nlohmann today)
//
// Workloads: parse (build + free), traverse (visit everything, convert every
// number, touch every string and key), select (a few fields per document),
// dump (compact serialization of the parsed document).
// All engines run interleaved in every round, each timed call after an untimed
// one of the same engine; the best round is reported.
#include <nlohmann/json_view.hpp>
#if JSON_VIEW_BENCH_BOOST
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#endif
#include <simdjson.h>
#include <yyjson.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <map>
#include <sstream>
using nlohmann::json;
using nlohmann::json_document;
using nlohmann::json_view;
static volatile double g_sink;
struct stats
{
double num = 0;
std::size_t str = 0, nodes = 0;
};
// ---------------- traversal ----------------
static void walk(json_view v, stats& st)
{
++st.nodes;
switch (v.type())
{
case json::value_t::object:
for (auto it = v.begin(); it != v.end(); ++it)
{
st.str += it.key().size();
walk(*it, st);
}
break;
case json::value_t::array:
for (const json_view e : v)
{
walk(e, st);
}
break;
case json::value_t::string:
st.str += v.get_string().size();
break;
case json::value_t::number_integer:
st.num += static_cast<double>(v.get<std::int64_t>());
break;
case json::value_t::number_unsigned:
st.num += static_cast<double>(v.get<std::uint64_t>());
break;
case json::value_t::number_float:
st.num += v.get<double>();
break;
default:
break;
}
}
static void walk(const json& j, stats& st)
{
++st.nodes;
switch (j.type())
{
case json::value_t::object:
for (const auto& kv : j.get_ref<const json::object_t&>())
{
st.str += kv.first.size();
walk(kv.second, st);
}
break;
case json::value_t::array:
for (const auto& e : j.get_ref<const json::array_t&>())
{
walk(e, st);
}
break;
case json::value_t::string:
st.str += j.get_ref<const std::string&>().size();
break;
case json::value_t::number_integer:
st.num += static_cast<double>(*j.get_ptr<const json::number_integer_t*>());
break;
case json::value_t::number_unsigned:
st.num += static_cast<double>(*j.get_ptr<const json::number_unsigned_t*>());
break;
case json::value_t::number_float:
st.num += *j.get_ptr<const json::number_float_t*>();
break;
default:
break;
}
}
static void walk(yyjson_val* v, stats& st)
{
++st.nodes;
switch (yyjson_get_type(v))
{
case YYJSON_TYPE_OBJ:
{
std::size_t idx, max;
yyjson_val* k, * val;
yyjson_obj_foreach(v, idx, max, k, val)
{
st.str += yyjson_get_len(k);
walk(val, st);
}
break;
}
case YYJSON_TYPE_ARR:
{
std::size_t idx, max;
yyjson_val* val;
yyjson_arr_foreach(v, idx, max, val)
{
walk(val, st);
}
break;
}
case YYJSON_TYPE_STR:
st.str += yyjson_get_len(v);
break;
case YYJSON_TYPE_NUM:
if (yyjson_is_sint(v))
{
st.num += static_cast<double>(yyjson_get_sint(v));
}
else if (yyjson_is_uint(v))
{
st.num += static_cast<double>(yyjson_get_uint(v));
}
else
{
st.num += yyjson_get_real(v);
}
break;
default:
break;
}
}
static void walk(simdjson::dom::element e, stats& st)
{
++st.nodes;
switch (e.type())
{
case simdjson::dom::element_type::OBJECT:
for (auto f : simdjson::dom::object(e))
{
st.str += f.key.size();
walk(f.value, st);
}
break;
case simdjson::dom::element_type::ARRAY:
for (auto c : simdjson::dom::array(e))
{
walk(c, st);
}
break;
case simdjson::dom::element_type::STRING:
st.str += std::string_view(e).size();
break;
case simdjson::dom::element_type::INT64:
st.num += static_cast<double>(int64_t(e));
break;
case simdjson::dom::element_type::UINT64:
st.num += static_cast<double>(uint64_t(e));
break;
case simdjson::dom::element_type::DOUBLE:
st.num += double(e);
break;
default:
break;
}
}
static void walk_od(simdjson::ondemand::value v, stats& st)
{
++st.nodes;
switch (v.type())
{
case simdjson::ondemand::json_type::object:
for (auto f : v.get_object())
{
st.str += std::string_view(f.unescaped_key()).size();
walk_od(f.value(), st);
}
break;
case simdjson::ondemand::json_type::array:
for (auto c : v.get_array())
{
walk_od(c.value(), st);
}
break;
case simdjson::ondemand::json_type::string:
st.str += std::string_view(v.get_string()).size();
break;
case simdjson::ondemand::json_type::number:
{
simdjson::ondemand::number n = v.get_number();
switch (n.get_number_type())
{
case simdjson::ondemand::number_type::signed_integer:
st.num += static_cast<double>(n.get_int64());
break;
case simdjson::ondemand::number_type::unsigned_integer:
st.num += static_cast<double>(n.get_uint64());
break;
default:
st.num += n.get_double();
break;
}
break;
}
case simdjson::ondemand::json_type::boolean:
(void)bool(v.get_bool());
break;
default:
(void)v.is_null();
break;
}
}
#if JSON_VIEW_BENCH_BOOST
static void walk(const boost::json::value& v, stats& st)
{
++st.nodes;
switch (v.kind())
{
case boost::json::kind::object:
for (const auto& kv : v.get_object())
{
st.str += kv.key().size();
walk(kv.value(), st);
}
break;
case boost::json::kind::array:
for (const auto& c : v.get_array())
{
walk(c, st);
}
break;
case boost::json::kind::string:
st.str += v.get_string().size();
break;
case boost::json::kind::int64:
st.num += static_cast<double>(v.get_int64());
break;
case boost::json::kind::uint64:
st.num += static_cast<double>(v.get_uint64());
break;
case boost::json::kind::double_:
st.num += v.get_double();
break;
default:
break;
}
}
#endif
// ---------------- selective access ----------------
// twitter: per status id, user.screen_name, retweet_count
// citm: per performance id, eventId, #seatCategories; #events
// canada: type, features[0].geometry.type, #coordinates
// jeopardy: per question: round == "Final Jeopardy!", len(category)
// status (one tweet): id, user.screen_name, retweet_count
// rpc: method, params.minuend, id
static double pick(const std::string& name, json_view r)
{
double acc = 0;
if (name == "twitter" || name == "status")
{
auto one = [&](json_view s)
{
acc += static_cast<double>(s["id"].get<std::uint64_t>());
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
};
if (name == "twitter")
{
for (const json_view s : r["statuses"])
{
one(s);
}
}
else
{
one(r);
}
}
else if (name == "citm_catalog")
{
for (const json_view p : r["performances"])
{
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
}
acc += static_cast<double>(r["events"].size());
}
else if (name == "canada")
{
const json_view g = r["features"][0]["geometry"];
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
}
else if (name == "jeopardy")
{
for (const json_view q : r)
{
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
acc += static_cast<double>(q["category"].get_string().size());
}
}
else if (name == "rpc")
{
acc += static_cast<double>(r["method"].get_string().size());
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
}
return acc;
}
static double pick(const std::string& name, const json& r)
{
double acc = 0;
if (name == "twitter" || name == "status")
{
auto one = [&](const json & s)
{
acc += static_cast<double>(s["id"].get<std::uint64_t>());
acc += static_cast<double>(s["user"]["screen_name"].get_ref<const std::string&>().size());
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
};
if (name == "twitter")
{
for (const auto& s : r["statuses"])
{
one(s);
}
}
else
{
one(r);
}
}
else if (name == "citm_catalog")
{
for (const auto& p : r["performances"])
{
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
}
acc += static_cast<double>(r["events"].size());
}
else if (name == "canada")
{
const json& g = r["features"][0]["geometry"];
acc += static_cast<double>(r["type"].get_ref<const std::string&>().size() + g["type"].get_ref<const std::string&>().size() + g["coordinates"].size());
}
else if (name == "jeopardy")
{
for (const auto& q : r)
{
acc += q["round"].get_ref<const std::string&>() == "Final Jeopardy!" ? 1 : 0;
acc += static_cast<double>(q["category"].get_ref<const std::string&>().size());
}
}
else if (name == "rpc")
{
acc += static_cast<double>(r["method"].get_ref<const std::string&>().size());
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
}
return acc;
}
static double pick(const std::string& name, yyjson_val* r)
{
double acc = 0;
auto get = [](yyjson_val * o, const char* k)
{
return yyjson_obj_get(o, k);
};
if (name == "twitter" || name == "status")
{
auto one = [&](yyjson_val * s)
{
acc += static_cast<double>(yyjson_get_uint(get(s, "id")));
acc += static_cast<double>(yyjson_get_len(get(get(s, "user"), "screen_name")));
acc += static_cast<double>(yyjson_get_sint(get(s, "retweet_count")));
};
if (name == "twitter")
{
std::size_t idx, max;
yyjson_val* s;
yyjson_arr_foreach(get(r, "statuses"), idx, max, s)
{
one(s);
}
}
else
{
one(r);
}
}
else if (name == "citm_catalog")
{
std::size_t idx, max;
yyjson_val* p;
yyjson_arr_foreach(get(r, "performances"), idx, max, p)
{
acc += static_cast<double>(yyjson_get_uint(get(p, "id")) + yyjson_get_uint(get(p, "eventId")) + yyjson_arr_size(get(p, "seatCategories")));
}
acc += static_cast<double>(yyjson_obj_size(get(r, "events")));
}
else if (name == "canada")
{
yyjson_val* g = get(yyjson_arr_get(get(r, "features"), 0), "geometry");
acc += static_cast<double>(yyjson_get_len(get(r, "type")) + yyjson_get_len(get(g, "type")) + yyjson_arr_size(get(g, "coordinates")));
}
else if (name == "jeopardy")
{
std::size_t idx, max;
yyjson_val* q;
yyjson_arr_foreach(r, idx, max, q)
{
acc += yyjson_equals_str(get(q, "round"), "Final Jeopardy!") ? 1 : 0;
acc += static_cast<double>(yyjson_get_len(get(q, "category")));
}
}
else if (name == "rpc")
{
acc += static_cast<double>(yyjson_get_len(get(r, "method")));
acc += static_cast<double>(yyjson_get_sint(get(get(r, "params"), "minuend")) + yyjson_get_sint(get(r, "id")));
}
return acc;
}
static double pick(const std::string& name, simdjson::dom::element r)
{
double acc = 0;
if (name == "twitter" || name == "status")
{
auto one = [&](simdjson::dom::element s)
{
acc += static_cast<double>(uint64_t(s["id"]));
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
acc += static_cast<double>(int64_t(s["retweet_count"]));
};
if (name == "twitter")
{
for (auto s : simdjson::dom::array(r["statuses"]))
{
one(s);
}
}
else
{
one(r);
}
}
else if (name == "citm_catalog")
{
for (auto p : simdjson::dom::array(r["performances"]))
{
acc += static_cast<double>(uint64_t(p["id"]) + uint64_t(p["eventId"]) + simdjson::dom::array(p["seatCategories"]).size());
}
acc += static_cast<double>(simdjson::dom::object(r["events"]).size());
}
else if (name == "canada")
{
auto g = r["features"].at(0)["geometry"];
acc += static_cast<double>(std::string_view(r["type"]).size() + std::string_view(g["type"]).size() + simdjson::dom::array(g["coordinates"]).size());
}
else if (name == "jeopardy")
{
for (auto q : simdjson::dom::array(r))
{
acc += std::string_view(q["round"]) == "Final Jeopardy!" ? 1 : 0;
acc += static_cast<double>(std::string_view(q["category"]).size());
}
}
else if (name == "rpc")
{
acc += static_cast<double>(std::string_view(r["method"]).size());
acc += static_cast<double>(int64_t(r["params"]["minuend"]) + int64_t(r["id"]));
}
return acc;
}
static double pick_od(const std::string& name, simdjson::ondemand::document& d)
{
double acc = 0;
if (name == "twitter" || name == "status")
{
auto one = [&](simdjson::ondemand::object s)
{
acc += static_cast<double>(uint64_t(s["id"]));
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
acc += static_cast<double>(int64_t(s["retweet_count"]));
};
if (name == "twitter")
{
for (auto s : d["statuses"])
{
one(s.get_object());
}
}
else
{
one(d.get_object());
}
}
else if (name == "citm_catalog")
{
simdjson::ondemand::object ev = d["events"].get_object();
acc += static_cast<double>(ev.count_fields());
for (auto p : d["performances"])
{
simdjson::ondemand::object o = p.get_object();
const auto a = uint64_t(o["eventId"]) + uint64_t(o["id"]);
simdjson::ondemand::array sc = o["seatCategories"].get_array();
acc += static_cast<double>(a + sc.count_elements());
}
}
else if (name == "canada")
{
acc += static_cast<double>(std::string_view(d["type"]).size());
auto g = d["features"].at(0)["geometry"];
acc += static_cast<double>(std::string_view(g["type"]).size());
simdjson::ondemand::array co = g["coordinates"].get_array();
acc += static_cast<double>(co.count_elements());
}
else if (name == "jeopardy")
{
for (auto q : d)
{
simdjson::ondemand::object o = q.get_object();
acc += static_cast<double>(std::string_view(o["category"]).size());
acc += std::string_view(o["round"]) == "Final Jeopardy!" ? 1 : 0;
}
}
else if (name == "rpc")
{
acc += static_cast<double>(std::string_view(d["method"]).size());
acc += static_cast<double>(int64_t(d["params"]["minuend"]));
acc += static_cast<double>(int64_t(d["id"]));
}
return acc;
}
// ---------------- harness ----------------
static std::string slurp(const std::string& p)
{
std::ifstream f(p, std::ios::binary);
if (!f)
{
std::fprintf(stderr, "cannot open %s\n", p.c_str());
std::exit(1);
}
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
}
struct engine
{
std::string name;
std::function<void()> fn;
};
int main(int argc, char** argv)
{
if (argc < 2)
{
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds] [document]\n", argv[0]);
return 1;
}
const std::string T = std::string(argv[1]) + "/";
const int rounds = argc > 2 ? std::atoi(argv[2]) : 30;
const std::string only = argc > 3 ? argv[3] : "";
struct doc
{
std::string name, text;
int batch;
};
std::vector<doc> docs;
for (const char* f :
{"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"
})
{
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
}
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
docs.push_back({"rpc", R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 3})", 5000});
// correctness cross-check of the workloads
for (const auto& dc : docs)
{
stats a, b, c, dd;
walk(json::parse(dc.text), a);
auto d = json_document::parse(dc.text);
walk(d.root(), b);
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
walk(yyjson_doc_get_root(y), c);
simdjson::dom::parser p;
walk(p.parse(dc.text).value(), dd);
const bool ok = a.nodes == b.nodes && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
&& std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num);
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pc && pa == pd) ? "OK" : "MISMATCH");
yyjson_doc_free(y);
}
std::FILE* csv = std::fopen("bench_view.csv", "w");
std::fprintf(csv, "doc,bytes,workload,engine,ns\n");
json_document reused;
simdjson::dom::parser sj;
simdjson::ondemand::parser od;
for (const auto& dc : docs)
{
if (!only.empty() && dc.name != only)
{
continue;
}
const std::string& s = dc.text;
const simdjson::padded_string ps(s);
const std::string name = dc.name;
std::vector<std::pair<std::string, std::vector<engine>>> workloads;
workloads.push_back({"parse", {
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
#if JSON_VIEW_BENCH_BOOST
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
#endif
{"json::parse", [&] { json j = json::parse(s); g_sink = static_cast<double>(j.size()); }},
}});
workloads.push_back({"traverse", {
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
#if JSON_VIEW_BENCH_BOOST
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
#endif
{"json::parse", [&] { json j = json::parse(s); stats st; walk(j, st); g_sink = st.num; }},
}});
workloads.push_back({"select", {
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
{"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }},
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
{"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }},
}});
{
// serialization of an already parsed document
static json_document vd;
vd.read(s);
static yyjson_doc* yd = nullptr;
if (yd)
{
yyjson_doc_free(yd);
}
yd = yyjson_read(s.data(), s.size(), 0);
static simdjson::dom::parser sjd;
static simdjson::dom::element se;
se = sjd.parse(ps).value_unsafe();
static json jd;
jd = json::parse(s);
workloads.push_back({"dump", {
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
{"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast<double>(o.size()); }},
}});
}
for (auto& wl : workloads)
{
std::vector<double> best(wl.second.size(), 1e300);
const int r = s.size() > 10000000 ? std::max(3, rounds / 5) : rounds;
for (int i = 0; i < r; ++i)
{
for (std::size_t k = 0; k < wl.second.size(); ++k)
{
// an untimed call first: whatever the previous engine left to the allocator
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
wl.second[k].fn();
const auto t0 = std::chrono::steady_clock::now();
for (int b = 0; b < dc.batch; ++b)
{
wl.second[k].fn();
}
const double ns = std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch;
best[k] = std::min(best[k], ns);
}
}
const double ref = best[0];
std::printf("%-13s %-9s", dc.name.c_str(), wl.first.c_str());
for (std::size_t k = 0; k < wl.second.size(); ++k)
{
const double us = best[k] / 1e3;
std::printf(" %s %s%s (%.2fx)", wl.second[k].name.c_str(), us >= 100 ? "" : "", (us >= 1000 ? std::to_string(static_cast<long>(us)) + "us" : (std::to_string(us).substr(0, 5) + "us")).c_str(), best[k] / ref);
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", dc.name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
}
std::printf("\n");
std::fflush(stdout);
}
}
std::fclose(csv);
}
-313
View File
@@ -1,313 +0,0 @@
#!/usr/bin/env python3
# __ _____ _____ _____
# __| | __| | | | JSON for Modern C++ (supporting code)
# | | |__ | | | | | | version 3.12.0
# |_____|_____|_____|_|___| https://github.com/nlohmann/json
#
# SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
# SPDX-License-Identifier: MIT
"""Compare json_view with yyjson, simdjson, Boost.JSON, and json::parse.
Builds bench_view.cpp, bench_corpus.cpp, and bench_edit.cpp against the include/ directory of
this checkout, runs them, and writes the results with everything needed to
reproduce them (date, commit, CPU, OS, compiler, library versions, flags) to
results/<date>-<host>.md and .csv next to this script.
The other libraries come from the system (--system, the default: pkg-config
or Homebrew) or are downloaded as pinned releases and checked against their
SHA-256 (--download). Boost.JSON is optional: without Boost headers, its
columns are skipped, and the results say so.
Only the Python 3 standard library is used; a C++17 compiler is needed.
"""
import argparse
import datetime
import hashlib
import os
import platform
import re
import shlex
import shutil
# runs only the compilers and benchmark binaries this script builds
import subprocess # nosec B404
import sys
import tarfile
import urllib.request
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.abspath(os.path.join(HERE, '..', '..', '..'))
# pinned releases for --download; the hashes are those of the archives
PINNED = {
'yyjson': {
'version': '0.13.0',
'url': 'https://github.com/ibireme/yyjson/archive/refs/tags/0.13.0.tar.gz',
'sha256': '34e0f62a2bc11ab20d601e8ca1cc2b2079503aa45119a19133d89d19b94a0fae',
'dir': 'yyjson-0.13.0',
},
'simdjson': {
'version': '4.6.11',
'url': 'https://github.com/simdjson/simdjson/archive/refs/tags/v4.6.11.tar.gz',
'sha256': '61d948fc24f0d793829ad658058e7597d064988a89b4607ea02e401a82df98ff',
'dir': 'simdjson-4.6.11',
},
'boost': {
'version': '1.92.0',
'url': 'https://archives.boost.io/release/1.92.0/source/boost_1_92_0.tar.gz',
'sha256': 'c4a3b310ddd2472416e091067166b0713be97c63f38c212c484ada022fd296ce',
'dir': 'boost_1_92_0',
},
}
# the documents of bench_view.cpp, relative to the json_test_data directory
DEFAULT_CORPUS = [
'nativejson-benchmark/twitter.json',
'nativejson-benchmark/citm_catalog.json',
'nativejson-benchmark/canada.json',
'jeopardy/jeopardy.json',
]
def run(cmd, **kwargs):
print('+ ' + ' '.join(shlex.quote(c) for c in cmd), flush=True)
# cmd is an argument list built by this script, never a shell string
return subprocess.run(cmd, check=True, **kwargs) # nosec B603
def output(cmd):
try:
# cmd is an argument list built by this script, never a shell string
return subprocess.run(cmd, check=True, capture_output=True, text=True).stdout.strip() # nosec B603
except (OSError, subprocess.CalledProcessError):
return ''
# ---------------------------------------------------------------------------
# libraries
# ---------------------------------------------------------------------------
class Library:
"""include directories, sources to compile, and linker flags of a library"""
def __init__(self, name, include=None, sources=None, link=None, version=''):
self.name = name
self.include = include or []
self.sources = sources or []
self.link = link or []
self.version = version
def header_version(path, pattern):
try:
with open(path, encoding='utf-8', errors='replace') as f:
m = re.search(pattern, f.read())
return m.group(1) if m else ''
except OSError:
return ''
def library_version(name, include_dirs):
patterns = {
'yyjson': ('yyjson.h', r'#define\s+YYJSON_VERSION_STRING\s+"([^"]+)"'),
'simdjson': ('simdjson.h', r'#define\s+SIMDJSON_VERSION\s+"?([0-9.]+)"?'),
'boost': (os.path.join('boost', 'version.hpp'), r'#define\s+BOOST_LIB_VERSION\s+"([^"]+)"'),
}
header, pattern = patterns[name]
for d in include_dirs:
v = header_version(os.path.join(d, header), pattern)
if v:
return v.replace('_', '.')
return ''
def system_library(name):
"""a library found with pkg-config or Homebrew, or None"""
flags = output(['pkg-config', '--cflags', '--libs', name]).split()
if flags:
include = [f[2:] for f in flags if f.startswith('-I')]
link = [f for f in flags if f.startswith('-L') or f.startswith('-l')]
libdirs = [f[2:] for f in link if f.startswith('-L')]
link += ['-Wl,-rpath,' + d for d in libdirs]
return Library(name, include, [], link, library_version(name, include))
prefix = output(['brew', '--prefix', name]) if shutil.which('brew') else ''
if prefix and os.path.isdir(os.path.join(prefix, 'include')):
include = [os.path.join(prefix, 'include')]
link = []
if name != 'boost':
lib = os.path.join(prefix, 'lib')
link = ['-L' + lib, '-l' + name, '-Wl,-rpath,' + lib]
return Library(name, include, [], link, library_version(name, include))
if name == 'boost':
for d in ['/usr/include', '/usr/local/include']:
if os.path.isfile(os.path.join(d, 'boost', 'json.hpp')):
return Library(name, [d], [], [], library_version(name, [d]))
return None
def download_library(name, work):
"""a pinned release, downloaded and checked, or an error"""
pin = PINNED[name]
archive = os.path.join(work, 'download', os.path.basename(pin['url']))
os.makedirs(os.path.dirname(archive), exist_ok=True)
if not os.path.isfile(archive):
print(f'downloading {pin["url"]}', flush=True)
# the URLs are the https constants in PINNED, and the SHA-256 is checked below
# (into a .part file first, so that an interrupted download is not kept)
urllib.request.urlretrieve(pin['url'], archive + '.part') # nosec B310
os.replace(archive + '.part', archive)
with open(archive, 'rb') as f:
digest = hashlib.sha256(f.read()).hexdigest()
if digest != pin['sha256']:
os.remove(archive) # downloaded again by the next run
sys.exit(f'error: SHA-256 of {archive} is {digest}, expected {pin["sha256"]} (removed)')
src = os.path.join(work, 'download', pin['dir'])
if not os.path.isdir(src):
with tarfile.open(archive) as t:
# (the 'data' filter rejects links and paths outside the target where Python has it)
kwargs = {'filter': 'data'} if hasattr(tarfile, 'data_filter') else {}
t.extractall(os.path.join(work, 'download'), **kwargs) # noqa: S202 (checked archive) # nosec B202
if name == 'yyjson':
return Library(name, [os.path.join(src, 'src')], [os.path.join(src, 'src', 'yyjson.c')], [], pin['version'])
if name == 'simdjson':
single = os.path.join(src, 'singleheader')
return Library(name, [single], [os.path.join(single, 'simdjson.cpp')], [], pin['version'])
return Library(name, [src], [], [], pin['version'])
# ---------------------------------------------------------------------------
# machine description
# ---------------------------------------------------------------------------
def cpu_model():
if sys.platform == 'darwin':
return output(['sysctl', '-n', 'machdep.cpu.brand_string'])
try:
with open('/proc/cpuinfo', encoding='utf-8') as f:
for line in f:
if line.startswith('model name') or line.startswith('Model'):
return line.split(':', 1)[1].strip()
except OSError:
pass
# (AArch64 Linux: /proc/cpuinfo has no model name, lscpu knows it)
for line in output(['lscpu']).splitlines():
if line.startswith('Model name:'):
return line.split(':', 1)[1].strip()
return platform.processor() or platform.machine()
def git_commit():
commit = output(['git', '-C', REPO, 'rev-parse', '--short=12', 'HEAD'])
dirty = output(['git', '-C', REPO, 'status', '--porcelain', '--untracked-files=no'])
return commit + (' (with local changes)' if dirty else '')
# ---------------------------------------------------------------------------
# main
# ---------------------------------------------------------------------------
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--data', required=True, help='json_test_data directory (with nativejson-benchmark/ and jeopardy/)')
ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries')
ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON')
ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)')
ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)')
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
args = ap.parse_args()
# the benchmarks run in the build directory: make the paths absolute
args.data = os.path.abspath(args.data)
args.corpus = [os.path.abspath(f) for f in args.corpus]
args.build_dir = os.path.abspath(args.build_dir)
cxx = os.environ.get('CXX', 'c++')
cc = os.environ.get('CC', 'cc')
os.makedirs(args.build_dir, exist_ok=True)
libs = {}
for name in ['yyjson', 'simdjson', 'boost']:
if name == 'boost' and args.no_boost:
continue
lib = download_library(name, args.build_dir) if args.download else system_library(name)
if lib is None and name != 'boost':
sys.exit(f'error: {name} not found; install it, or use --download')
if lib is not None:
libs[name] = lib
with_boost = 'boost' in libs
if not with_boost:
print('Boost.JSON not found: its columns are skipped', flush=True)
flags = ['-std=c++17', '-O3', '-DNDEBUG', f'-DJSON_VIEW_BENCH_BOOST={1 if with_boost else 0}']
if args.native:
flags.append('-mcpu=native' if platform.machine().lower() in ('arm64', 'aarch64') else '-march=native')
include = ['-I' + os.path.join(REPO, 'include')] + ['-I' + d for lib in libs.values() for d in lib.include]
link = [f for lib in libs.values() for f in lib.link]
# C sources of downloaded libraries are compiled once
objects = []
for lib in libs.values():
for src in lib.sources:
obj = os.path.join(args.build_dir, os.path.basename(src) + '.o')
compiler = cc if src.endswith('.c') else cxx
run([compiler] + (['-std=c++17'] if compiler == cxx else []) + ['-O3', '-DNDEBUG', '-c', src, '-o', obj]
+ ['-I' + d for d in lib.include])
objects.append(obj)
binaries = {}
for bench in ['bench_view', 'bench_corpus', 'bench_edit']:
exe = os.path.join(args.build_dir, bench)
run([cxx] + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
binaries[bench] = exe
# run: bench_view on its documents, bench_corpus on those and the given files
corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus
outputs = {}
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=args.build_dir,
capture_output=True, text=True).stdout
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=args.build_dir,
capture_output=True, text=True).stdout
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
capture_output=True, text=True).stdout
for name, text in outputs.items():
print(text)
# results with their metadata
now = datetime.datetime.now()
host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host'
stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}')
os.makedirs(os.path.dirname(stem), exist_ok=True)
meta = [
('date', f'{now:%Y-%m-%d %H:%M}'),
('commit', git_commit()),
('CPU', cpu_model()),
('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'),
('compiler', output([cxx, '--version']).splitlines()[0] if output([cxx, '--version']) else cxx),
('flags', ' '.join(flags)),
('yyjson', libs['yyjson'].version),
('simdjson', libs['simdjson'].version),
('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'),
('libraries from', 'pinned downloads' if args.download else 'the system'),
('rounds', str(args.rounds)),
]
with open(stem + '.md', 'w', encoding='utf-8') as f:
f.write(f'# json_view comparison, {now:%Y-%m-%d}\n\n')
f.write('Generated by `tests/benchmarks/json_view/compare.py`; best of the interleaved rounds.\n\n')
f.write('| | |\n|---|---|\n')
for key, value in meta:
f.write(f'| {key} | {value} |\n')
for name, text in outputs.items():
f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n')
with open(stem + '.csv', 'w', encoding='utf-8') as out:
out.write(''.join(f'# {key}: {value}\n' for key, value in meta))
for name in ['bench_view', 'bench_corpus', 'bench_edit']:
path = os.path.join(args.build_dir, name + '.csv')
if os.path.isfile(path):
with open(path, encoding='utf-8') as f:
out.write(f'# {name}\n' + f.read())
print(f'results: {stem}.md, {stem}.csv')
if __name__ == '__main__':
main()
+90
View File
@@ -521,6 +521,10 @@ struct countdown_allocator : std::allocator<T>
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
{
// MSVC 2015's debug STL constructs the containers' debug proxies through
// the allocator in noexcept constructors, so a failing construction crashes
// the program there instead of throwing std::bad_alloc. Nothing to check.
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
using countdown_json = nlohmann::basic_json<std::map,
std::vector,
std::string,
@@ -558,6 +562,7 @@ TEST_CASE("converting a deeply nested value from another specialization fails cl
}
}
CHECK(failures > 0);
#endif
}
namespace
@@ -602,3 +607,88 @@ TEST_CASE("bad my_allocator::construct")
j["test"].push_back("should not leak");
}
}
namespace
{
std::size_t counting_allocator_allocations = 0;
std::size_t counting_allocator_deallocations = 0;
template<class T>
struct counting_allocator : std::allocator<T>
{
using std::allocator<T>::allocator;
T* allocate(std::size_t n)
{
++counting_allocator_allocations;
return std::allocator<T>::allocate(n);
}
void deallocate(T* p, std::size_t n)
{
++counting_allocator_deallocations;
std::allocator<T>::deallocate(p, n);
}
template <class U>
struct rebind
{
using other = counting_allocator<U>;
};
};
} // namespace
TEST_CASE("destructor performs no allocation, only deallocation")
{
// see https://github.com/nlohmann/json/issues/4842 and
// https://github.com/nlohmann/json/issues/5135: destroying nested
// arrays/objects used to allocate a temporary stack (first with
// std::allocator, later - after #4842 - with the provided allocator).
// Since that stack could itself throw bad_alloc from inside the
// noexcept destructor (#5135), destroy() no longer allocates anything:
// it only ever frees what is already there.
using counting_json = nlohmann::basic_json<std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
counting_allocator>;
SECTION("array")
{
auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory)
const auto allocations_before = counting_allocator_allocations;
const auto deallocations_before = counting_allocator_deallocations;
delete j; // NOLINT(cppcoreguidelines-owning-memory)
CHECK(counting_allocator_allocations == allocations_before);
CHECK(counting_allocator_deallocations > deallocations_before);
}
SECTION("object")
{
auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory)
const auto allocations_before = counting_allocator_allocations;
const auto deallocations_before = counting_allocator_deallocations;
delete j; // NOLINT(cppcoreguidelines-owning-memory)
CHECK(counting_allocator_allocations == allocations_before);
CHECK(counting_allocator_deallocations > deallocations_before);
}
SECTION("mixed tree of empty/non-empty arrays and objects")
{
auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory)
{
{"empty_obj", counting_json::object()},
{"empty_arr", counting_json::array()},
{"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}},
{"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})}
});
const auto allocations_before = counting_allocator_allocations;
const auto deallocations_before = counting_allocator_deallocations;
delete j; // NOLINT(cppcoreguidelines-owning-memory)
CHECK(counting_allocator_allocations == allocations_before);
CHECK(counting_allocator_deallocations > deallocations_before);
}
}
+141
View File
@@ -11,7 +11,12 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cmath>
#include <fstream>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include "make_test_data_available.hpp"
TEST_CASE("Binary Formats" * doctest::skip())
@@ -224,3 +229,139 @@ TEST_CASE("Binary Formats" * doctest::skip())
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450));
}
}
namespace
{
// the binary formats as function pointers for "Binary formats with narrow number types";
// named functions rather than lambdas, because clang 3.5 cannot convert a lambda
// to a function pointer in the braced initializer of the format table
using narrow_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
using bytes = std::vector<std::uint8_t>;
bytes encode_cbor(const json& j)
{
return json::to_cbor(j);
}
narrow_json decode_cbor(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_cbor(v, true, allow_exceptions);
}
bytes encode_msgpack(const json& j)
{
return json::to_msgpack(j);
}
narrow_json decode_msgpack(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_msgpack(v, true, allow_exceptions);
}
bytes encode_ubjson(const json& j)
{
return json::to_ubjson(j);
}
narrow_json decode_ubjson(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_ubjson(v, true, allow_exceptions);
}
bytes encode_bjdata(const json& j)
{
return json::to_bjdata(j);
}
narrow_json decode_bjdata(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_bjdata(v, true, allow_exceptions);
}
// BSON can only store numbers as object members
bytes encode_bson(const json& j)
{
return json::to_bson(json{{"a", j}});
}
narrow_json decode_bson(const bytes& v, bool allow_exceptions)
{
const auto result = narrow_json::from_bson(v, true, allow_exceptions);
return result.is_discarded() ? result : result.at("a");
}
bytes encode_bon8(const json& j)
{
return json::to_bon8(j);
}
narrow_json decode_bon8(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_bon8(v, true, allow_exceptions);
}
} // namespace
TEST_CASE("Binary formats with narrow number types")
{
// Numbers that do not fit the number types are handled like the lexer
// handles them in JSON text: an integer that fits neither integer type is
// stored as a floating-point number, and a finite floating-point number
// that overflows number_float_t is rejected with out_of_range.406.
struct binary_format
{
const char* name;
bytes (*encode)(const json&);
narrow_json (*decode)(const bytes&, bool);
};
const std::vector<binary_format> formats =
{
{"CBOR", encode_cbor, decode_cbor},
{"MessagePack", encode_msgpack, decode_msgpack},
{"UBJSON", encode_ubjson, decode_ubjson},
{"BJData", encode_bjdata, decode_bjdata},
{"BSON", encode_bson, decode_bson},
{"BON8", encode_bon8, decode_bon8},
};
for (const auto& format : formats)
{
const std::string name = format.name;
INFO("format := ", name);
const auto roundtrip = [&format](const json & j)
{
return format.decode(format.encode(j), true);
};
// integers that fit keep their type
CHECK(roundtrip(json(-5)).is_number_integer());
CHECK(roundtrip(json(-5)).get<std::int32_t>() == -5);
CHECK(roundtrip(json(3000000000u)).is_number_unsigned());
CHECK(roundtrip(json(3000000000u)).get<std::uint32_t>() == 3000000000u);
// integers that fit neither integer type are stored as float
CHECK(roundtrip(json(5000000000u)).is_number_float());
CHECK(roundtrip(json(5000000000u)).get<float>() == 5000000000.0f);
if (name != "BON8") // BON8 cannot encode integers above INT64_MAX
{
CHECK(roundtrip(json(10000000000000000000u)).is_number_float());
CHECK(roundtrip(json(10000000000000000000u)).get<float>() == 10000000000000000000.0f);
}
CHECK(roundtrip(json(-3000000000LL)).is_number_float());
CHECK(roundtrip(json(-3000000000LL)).get<float>() == -3000000000.0f);
CHECK(roundtrip(json(-5000000000LL)).is_number_float());
CHECK(roundtrip(json(-5000000000LL)).get<float>() == -5000000000.0f);
// floating-point numbers that fit
CHECK(roundtrip(json(1.5)).get<float>() == 1.5f);
const auto just_above_max = std::nextafter(static_cast<double>((std::numeric_limits<float>::max)()),
std::numeric_limits<double>::infinity());
CHECK(roundtrip(json(just_above_max)).get<float>() == (std::numeric_limits<float>::max)());
// infinity and NaN are passed on
CHECK(std::isinf(roundtrip(json(std::numeric_limits<double>::infinity())).get<float>()));
CHECK(std::isnan(roundtrip(json(std::numeric_limits<double>::quiet_NaN())).get<float>()));
// finite floating-point numbers that overflow number_float_t are rejected
const std::string message = "[json.exception.out_of_range.406] syntax error while parsing " + name
+ " value: number overflow";
CHECK_THROWS_WITH_AS(roundtrip(json(1e300)), message.c_str(), narrow_json::out_of_range&);
CHECK_THROWS_WITH_AS(roundtrip(json(-1e300)), message.c_str(), narrow_json::out_of_range&);
CHECK(format.decode(format.encode(json(1e300)), false).is_discarded());
}
}
+90 -3
View File
@@ -9,6 +9,14 @@
#include "doctest_compatibility.h"
#define JSON_TESTS_PRIVATE
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -106,10 +114,59 @@ TEST_CASE("BJData")
{
SECTION("discarded")
{
// discarded values are not serialized
// a discarded value cannot be serialized to BJData
json const j = json::value_t::discarded;
const auto result = json::to_bjdata(j);
CHECK(result.empty());
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
}
SECTION("discarded values nested in a container")
{
json const discarded = json::value_t::discarded;
SECTION("in an array")
{
json const j = {1, discarded, 2};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BJData", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
#endif
}
SECTION("as an object value")
{
json j;
j["a"] = 1;
j["b"] = discarded;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BJData", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
#endif
}
SECTION("nested deeper (array in object in array)")
{
json inner_array = {1, discarded};
json middle_object;
middle_object["x"] = inner_array;
json const j = {middle_object};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to BJData", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
#endif
}
SECTION("optimized array of all-discarded elements")
{
json const j = {discarded, discarded};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to BJData", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
#endif
}
}
SECTION("null")
@@ -4490,3 +4547,33 @@ TEST_CASE("BJData roundtrips" * doctest::skip())
}
}
}
TEST_CASE("issue #5648 - from_bjdata(ptr, len) must read len bytes, not treat ptr as a C string")
{
// to_bjdata() encodes the integer 0 as the two bytes 'i' 0x00 (a BJData
// type marker followed by the value byte 0x00), so the packed data
// below contains a 0x00 byte before its end.
const json j = {{"a", 0}};
const std::vector<std::uint8_t> packed = json::to_bjdata(j);
bool contains_nul = false;
for (const auto byte : packed)
{
contains_nul |= (byte == 0x00);
}
REQUIRE(contains_nul);
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
// before the fix, from_bjdata had no (ptr, len) overload, so this call
// bound to from_bjdata(InputType&&, bool strict) instead: ptr was read
// as a NUL-terminated C string (stopping at the embedded 0x00 byte), and
// len was silently converted to the strict flag. The deprecated
// overload added for this issue forwards to from_bjdata(ptr, ptr + len,
// ...) instead, like from_ubjson's deprecated (ptr, len) overload does.
json result;
CHECK_NOTHROW(result = json::from_bjdata(packed.data(), packed.size()));
CHECK(result == j);
// len must not collapse into the strict flag either
CHECK(json::from_bjdata(packed.data(), packed.size(), false) == j);
#endif
}
+38
View File
@@ -8,6 +8,14 @@
#include "doctest_compatibility.h"
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
@@ -1014,6 +1022,36 @@ TEST_CASE("BON8 roundtrips" * doctest::skip())
}
}
TEST_CASE("issue #5648 - from_bon8(ptr, len) must read len bytes, not treat ptr as a C string")
{
// to_bon8() encodes the integer 40 as the two bytes 0xC2 0x00 (a BON8
// lead byte followed by a continuation byte of 0x00), so the packed data
// below contains a 0x00 byte before its end.
const json j = {{"a", 40}, {"b", "x"}};
const std::vector<std::uint8_t> packed = json::to_bon8(j);
bool contains_nul = false;
for (const auto byte : packed)
{
contains_nul |= (byte == 0x00);
}
REQUIRE(contains_nul);
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
// before the fix, from_bon8 had no (ptr, len) overload, so this call
// bound to from_bon8(InputType&&, bool strict) instead: ptr was read as
// a NUL-terminated C string (stopping at the embedded 0x00 byte), and
// len was silently converted to the strict flag. The deprecated
// overload added for this issue forwards to from_bon8(ptr, ptr + len,
// ...) instead, like from_cbor's deprecated (ptr, len) overload does.
json result;
CHECK_NOTHROW(result = json::from_bon8(packed.data(), packed.size()));
CHECK(result == j);
// len must not collapse into the strict flag either
CHECK(json::from_bon8(packed.data(), packed.size(), false) == j);
#endif
}
#ifdef JSON_HAS_CPP_17
TEST_CASE("BON8 with std::byte")
{
+60
View File
@@ -8,6 +8,14 @@
#include "doctest_compatibility.h"
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -141,6 +149,54 @@ TEST_CASE("BSON")
json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&);
}
SECTION("discarded")
{
json const j = json::value_t::discarded;
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is discarded", json::type_error&);
}
}
SECTION("discarded values nested in a container cannot be serialized to BSON")
{
json const discarded = json::value_t::discarded;
SECTION("as an object value")
{
json j;
j["a"] = 1;
j["b"] = discarded;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BSON", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
#endif
}
SECTION("in an array that is an object value")
{
json j;
j["a"] = json::array({1, discarded, 2});
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/a/1) cannot serialize discarded value to BSON", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
#endif
}
SECTION("nested deeper (array in object in object)")
{
json inner_array = {1, discarded};
json middle_object;
middle_object["x"] = inner_array;
json j;
j["outer"] = middle_object;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/outer/x/1) cannot serialize discarded value to BSON", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
#endif
}
}
SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
@@ -1261,8 +1317,10 @@ TEST_CASE("BSON input that cannot be read is discarded by every overload")
CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_bson(input, true, false).is_discarded());
CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded());
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded());
CHECK(json::from_bson({input.data(), input.size()}, true, false).is_discarded());
#endif
}
TEST_CASE("BSON SAX parsing stops at every event")
@@ -1740,6 +1798,7 @@ TEST_CASE("BSON roundtrips" * doctest::skip())
CHECK(j1 == j2);
}
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
{
INFO_WITH_TEMP(filename + ": uint8_t* and size");
// parse JSON file
@@ -1754,6 +1813,7 @@ TEST_CASE("BSON roundtrips" * doctest::skip())
// compare parsed JSON values
CHECK(j1 == j2);
}
#endif
{
INFO_WITH_TEMP(filename + ": output to output adapters");
+70 -17
View File
@@ -8,6 +8,14 @@
#include "doctest_compatibility.h"
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -30,10 +38,49 @@ TEST_CASE("CBOR")
{
SECTION("discarded")
{
// discarded values are not serialized
// a discarded value cannot be serialized to CBOR
json const j = json::value_t::discarded;
const auto result = json::to_cbor(j);
CHECK(result.empty());
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
}
SECTION("discarded values nested in a container")
{
json const discarded = json::value_t::discarded;
SECTION("in an array")
{
json const j = {1, discarded, 2};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to CBOR", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
#endif
}
SECTION("as an object value")
{
json j;
j["a"] = 1;
j["b"] = discarded;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to CBOR", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
#endif
}
SECTION("nested deeper (array in object in array)")
{
json inner_array = {1, discarded};
json middle_object;
middle_object["x"] = inner_array;
json const j = {middle_object};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to CBOR", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
#endif
}
}
SECTION("NaN")
@@ -2215,8 +2262,10 @@ TEST_CASE("CBOR input that cannot be read is discarded by every overload")
CHECK_THROWS_AS(_ = json::from_cbor(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_cbor(input, true, false).is_discarded());
CHECK(json::from_cbor(input.begin(), input.end(), true, false).is_discarded());
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
CHECK(json::from_cbor(input.data(), input.size(), true, false).is_discarded());
CHECK(json::from_cbor({input.data(), input.size()}, true, false).is_discarded());
#endif
// a string that ends early, read through iterators that are not
// contiguous and have to be copied from one element at a time
@@ -2613,12 +2662,14 @@ TEST_CASE("CBOR roundtrips" * doctest::skip())
CHECK(j1 == j2);
}
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
{
INFO_WITH_TEMP(filename + ": uint8_t* and size");
json j2;
CHECK_NOTHROW(j2 = json::from_cbor({packed.data(), packed.size()}));
CHECK(j1 == j2);
}
#endif
{
INFO_WITH_TEMP(filename + ": output to output adapters");
@@ -3185,7 +3236,8 @@ TEST_CASE("Tagged values")
// CBOR encodes negative integers as: result = -1 - n
// For type 0x3B, n is an 8-byte uint64_t. Valid range for n with
// the default int64_t is [0, INT64_MAX], producing results in [INT64_MIN, -1].
// When n > INT64_MAX, the result exceeds int64_t range and is rejected.
// When n > INT64_MAX, the result exceeds int64_t range and is stored
// as a floating-point number, as the lexer does for JSON text.
SECTION("n = 0 is valid (result = -1)")
{
@@ -3206,33 +3258,34 @@ TEST_CASE("Tagged values")
CHECK(result.get<int64_t>() == (std::numeric_limits<int64_t>::min)());
}
SECTION("n = INT64_MAX + 1 is rejected (overflow)")
SECTION("n = INT64_MAX + 1 is stored as float")
{
// n = INT64_MAX + 1 (0x8000000000000000)
// result = -1 - n = -9223372036854775809, which exceeds int64_t range
// result = -1 - n = -9223372036854775809, which exceeds int64_t range;
// the nearest double is -9223372036854775808.0
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
json::parse_error);
const auto result = json::from_cbor(input);
CHECK(result.is_number_float());
CHECK(result.get<double>() == -9223372036854775808.0);
CHECK(result == json::parse("-9223372036854775809"));
}
SECTION("n = UINT64_MAX is rejected (overflow)")
SECTION("n = UINT64_MAX is stored as float")
{
// n = UINT64_MAX (0xFFFFFFFFFFFFFFFF)
// result = -1 - n = -18446744073709551616, which exceeds int64_t range
const std::vector<uint8_t> input = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
json::parse_error);
const auto result = json::from_cbor(input);
CHECK(result.is_number_float());
CHECK(result.get<double>() == -18446744073709551616.0);
CHECK(result == json::parse("-18446744073709551616"));
}
SECTION("overflow with allow_exceptions=false returns discarded")
SECTION("overflow with allow_exceptions=false is not an error")
{
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const auto result = json::from_cbor(input, true, false);
CHECK(result.is_discarded());
CHECK(result.is_number_float());
}
}
+11 -11
View File
@@ -689,7 +689,7 @@ TEST_CASE("lexer escape fast path")
})
{
const std::string doc = "[\"" + std::string(offset, 'a') + c.first + "\"]";
CAPTURE(doc);
CAPTURE(doc)
CHECK(json::accept(doc) == c.second);
std::stringstream ss(doc);
CHECK(json::accept(ss) == c.second);
@@ -748,14 +748,14 @@ TEST_CASE("lexer escape fast path")
})
{
const std::string doc = "[\"" + std::string(offset, 'a') + escape + "\"]";
CAPTURE(doc);
CAPTURE(doc)
CHECK(outcome(doc, false) == outcome(doc, true));
}
// the escape is the last thing before end of input: no closing
// quote at all
const std::string truncated_doc = "[\"" + escape;
CAPTURE(truncated_doc);
CAPTURE(truncated_doc)
CHECK(outcome(truncated_doc, false) == outcome(truncated_doc, true));
}
}
@@ -773,7 +773,7 @@ TEST_CASE("lexer escape fast path")
})
{
const std::string doc = "[\"\\u" + tail;
CAPTURE(doc);
CAPTURE(doc)
CHECK(outcome(doc, false) == outcome(doc, true));
CHECK(outcome(doc, false).find("must be followed by 4 hex digits") != std::string::npos);
}
@@ -789,7 +789,7 @@ TEST_CASE("lexer escape fast path")
std::string digits = "1234";
digits[bad_pos] = 'g'; // not a hex digit
const std::string doc = "[\"\\u" + digits + "\"]";
CAPTURE(doc);
CAPTURE(doc)
CHECK(outcome(doc, false) == outcome(doc, true));
CHECK(outcome(doc, false).find("must be followed by 4 hex digits") != std::string::npos);
}
@@ -837,7 +837,7 @@ TEST_CASE("lexer escape fast path")
mismatches.push_back(doc);
}
}
CAPTURE(mismatches);
CAPTURE(mismatches)
CHECK(mismatches.empty());
}
#endif
@@ -970,7 +970,7 @@ TEST_CASE("parse_float_native rounds correctly")
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
for (const auto& t : tokens)
{
CAPTURE(t);
CAPTURE(t)
const auto layout = float_token_layout(t);
const char* const first = t.data();
const char* const last = first + t.size();
@@ -1572,7 +1572,7 @@ TEST_CASE("Eisel-Lemire float conversion")
std::array<char, 64> buffer{};
const char* end = nlohmann::detail::to_chars(buffer.data(), buffer.data() + buffer.size(), f);
const std::string token(buffer.data(), static_cast<std::size_t>(end - buffer.data()));
CAPTURE(token);
CAPTURE(token)
CHECK(native_bits32(token) == b);
}
}
@@ -1626,7 +1626,7 @@ TEST_CASE("float conversion of hard cases")
for (const auto& c : float_hard_cases::cases())
{
const std::string token = c.token;
CAPTURE(token);
CAPTURE(token)
CHECK(native_bits64(token) == c.bits64);
CHECK(native_bits32(token) == c.bits32);
check_parse<json>(token, c.bits64, std::uint64_t{0x7FF0000000000000u});
@@ -1746,8 +1746,8 @@ TEST_CASE("string scanning kernels")
for (std::size_t offset = 0; offset < 3 && offset <= text.size(); ++offset)
{
const std::size_t n = text.size() - offset;
CAPTURE(text);
CAPTURE(offset);
CAPTURE(text)
CAPTURE(offset)
CHECK(nlohmann::detail::find_string_special(data + offset, n) == reference_special(data + offset, n));
CHECK(nlohmann::detail::find_ascii_copyable_run(data + offset, n) == reference_copyable(data + offset, n));
CHECK(nlohmann::detail::scalar_string_bulk_run(data + offset, n) == reference_bulk_run(data + offset, n));
+52
View File
@@ -2317,6 +2317,58 @@ TEST_CASE("parser class")
#endif
}
SECTION("comments before separators")
{
// The parser first checks for the expected ':' or ',' and only then
// falls back to the full token switch, which skips comments. A comment
// directly before a separator takes that fallback.
json _;
SECTION("ignored")
{
const std::vector<std::pair<std::string, json>> inputs =
{
{"{\"a\" /* c */ : 1}", {{"a", 1}}},
{"{\"a\" // c\n: 1}", {{"a", 1}}},
{R"({"a": 1, "b" /* c */ : 2})", {{"a", 1}, {"b", 2}}},
{R"({"a": 1 /* c */ , "b": 2})", {{"a", 1}, {"b", 2}}},
{"{\"a\": 1 // c\n, \"b\": 2}", {{"a", 1}, {"b", 2}}},
{"[1 /* c */ , 2]", {1, 2}},
{"[1 // c\n, 2]", {1, 2}},
{"{\"a\" /* c */ /* d */ : [1 // c\n , 2 /**/ ] /**/ , \"b\" : 3}", {{"a", {1, 2}}, {"b", 3}}}
};
for (const auto& input : inputs)
{
CAPTURE(input.first)
CHECK(json::parse(input.first, nullptr, true, true) == input.second);
CHECK(json::accept(input.first, true));
}
}
SECTION("ignored, with trailing commas")
{
CHECK(json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true, true) == json({1}));
CHECK(json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true, true) == json({{"a", 1}}));
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true),
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error);
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true),
"[json.exception.parse_error.101] parse error at line 1, column 19: syntax error while parsing object key - unexpected '}'; expected string literal", json::parse_error);
}
SECTION("not ignored")
{
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\" /* c */ : 1}")),
"[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing object separator - invalid literal; last read: '\"a\" /'; expected ':'", json::parse_error);
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1, \"b\" /* c */ : 2}")),
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing object separator - invalid literal; last read: '\"b\" /'; expected ':'", json::parse_error);
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , \"b\": 2}")),
"[json.exception.parse_error.101] parse error at line 1, column 9: syntax error while parsing object - invalid literal; last read: '1 /'; expected '}'", json::parse_error);
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , 2]")),
"[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing array - invalid literal; last read: '1 /'; expected ']'", json::parse_error);
CHECK(!json::accept(std::string("[1 /* c */ , 2]")));
}
}
#if JSON_DIAGNOSTIC_POSITIONS
// Macro for all test cases for start_pos and end_pos
#define SETUP_TESTCASES() \
+7 -2
View File
@@ -191,8 +191,10 @@ TEST_CASE("value conversion")
{
enum class bool_enum : bool { off, on };
CHECK(json(bool_enum::off).get<bool_enum>() == bool_enum::off);
CHECK(json(bool_enum::on).get<bool_enum>() == bool_enum::on);
// the extra parentheses keep doctest from printing the enum via its
// underlying type, which MSVC 2015 reports as C4800
CHECK((json(bool_enum::off).get<bool_enum>() == bool_enum::off));
CHECK((json(bool_enum::on).get<bool_enum>() == bool_enum::on));
}
#endif
@@ -851,8 +853,11 @@ TEST_CASE("std::optional")
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
// the conversion is noexcept exactly when converting the contained value is
// (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
#endif
}
#endif
}
@@ -0,0 +1,229 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
// This file tests the opt-in JSON_DELETE_DEPRECATED_FUNCTIONS, so it defines the
// macro itself instead of relying on the build configuration.
#ifdef JSON_DELETE_DEPRECATED_FUNCTIONS
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
#endif
#define JSON_DELETE_DEPRECATED_FUNCTIONS 1
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cstddef>
#include <cstdint>
#include <istream>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
namespace
{
template<typename...>
struct make_void
{
using type = void;
};
template<typename... Ts>
using void_t = typename make_void<Ts...>::type;
// A deleted function is still found by overload resolution, but naming it in an
// unevaluated operand is ill-formed, so each of the following traits is false
// exactly if the expression selects a deleted (or no) function.
#define JSON_TEST_DETECT(name, expr) \
template<typename J, typename = void> \
struct name : std::false_type {}; \
template<typename J> \
struct name<J, void_t<decltype(expr)>> : std::true_type {}
using ptr_t = const std::uint8_t*;
JSON_TEST_DETECT(from_cbor_ptr_len, J::from_cbor(std::declval<ptr_t>(), std::declval<std::size_t>()));
JSON_TEST_DETECT(from_msgpack_ptr_len, J::from_msgpack(std::declval<ptr_t>(), std::declval<std::size_t>()));
JSON_TEST_DETECT(from_ubjson_ptr_len, J::from_ubjson(std::declval<ptr_t>(), std::declval<std::size_t>()));
JSON_TEST_DETECT(from_bjdata_ptr_len, J::from_bjdata(std::declval<ptr_t>(), std::declval<std::size_t>()));
JSON_TEST_DETECT(from_bon8_ptr_len, J::from_bon8(std::declval<ptr_t>(), std::declval<std::size_t>()));
JSON_TEST_DETECT(from_bson_ptr_len, J::from_bson(std::declval<ptr_t>(), std::declval<std::size_t>()));
JSON_TEST_DETECT(from_cbor_ptr_ptr, J::from_cbor(std::declval<ptr_t>(), std::declval<ptr_t>()));
JSON_TEST_DETECT(from_msgpack_ptr_ptr, J::from_msgpack(std::declval<ptr_t>(), std::declval<ptr_t>()));
JSON_TEST_DETECT(from_ubjson_ptr_ptr, J::from_ubjson(std::declval<ptr_t>(), std::declval<ptr_t>()));
JSON_TEST_DETECT(from_bjdata_ptr_ptr, J::from_bjdata(std::declval<ptr_t>(), std::declval<ptr_t>()));
JSON_TEST_DETECT(from_bon8_ptr_ptr, J::from_bon8(std::declval<ptr_t>(), std::declval<ptr_t>()));
JSON_TEST_DETECT(from_bson_ptr_ptr, J::from_bson(std::declval<ptr_t>(), std::declval<ptr_t>()));
JSON_TEST_DETECT(from_cbor_init_list, J::from_cbor({std::declval<ptr_t>(), std::declval<std::size_t>()}));
JSON_TEST_DETECT(from_msgpack_init_list, J::from_msgpack({std::declval<ptr_t>(), std::declval<std::size_t>()}));
JSON_TEST_DETECT(from_ubjson_init_list, J::from_ubjson({std::declval<ptr_t>(), std::declval<std::size_t>()}));
JSON_TEST_DETECT(from_bson_init_list, J::from_bson({std::declval<ptr_t>(), std::declval<std::size_t>()}));
JSON_TEST_DETECT(parse_init_list, J::parse({std::declval<ptr_t>(), std::declval<std::size_t>()}));
JSON_TEST_DETECT(accept_init_list, J::accept({std::declval<ptr_t>(), std::declval<std::size_t>()}));
JSON_TEST_DETECT(sax_parse_init_list, J::sax_parse({std::declval<ptr_t>(), std::declval<std::size_t>()}, std::declval<nlohmann::json_sax<J>*>()));
JSON_TEST_DETECT(parse_ptr_ptr, J::parse(std::declval<ptr_t>(), std::declval<ptr_t>()));
JSON_TEST_DETECT(iterator_wrapper, J::iterator_wrapper(std::declval<J&>()));
JSON_TEST_DETECT(iterator_wrapper_const, J::iterator_wrapper(std::declval<const J&>()));
JSON_TEST_DETECT(items, std::declval<J&>().items());
JSON_TEST_DETECT(json_ltlt_istream, std::declval<J&>() << std::declval<std::istream&>());
JSON_TEST_DETECT(istream_gtgt_json, std::declval<std::istream&>() >> std::declval<J&>());
JSON_TEST_DETECT(json_gtgt_ostream, std::declval<const J&>() >> std::declval<std::ostream&>());
JSON_TEST_DETECT(ostream_ltlt_json, std::declval<std::ostream&>() << std::declval<const J&>());
JSON_TEST_DETECT(ptr_eq_ptr, std::declval<const typename J::json_pointer&>() == std::declval<const typename J::json_pointer&>());
JSON_TEST_DETECT(ptr_ne_ptr, std::declval<const typename J::json_pointer&>() != std::declval<const typename J::json_pointer&>());
JSON_TEST_DETECT(ptr_eq_string, std::declval<const typename J::json_pointer&>() == std::declval<const std::string&>());
JSON_TEST_DETECT(string_eq_ptr, std::declval<const std::string&>() == std::declval<const typename J::json_pointer&>());
JSON_TEST_DETECT(ptr_eq_c_string, std::declval<const typename J::json_pointer&>() == std::declval<const char*>());
JSON_TEST_DETECT(c_string_eq_ptr, std::declval<const char*>() == std::declval<const typename J::json_pointer&>());
JSON_TEST_DETECT(ptr_ne_string, std::declval<const typename J::json_pointer&>() != std::declval<const std::string&>());
JSON_TEST_DETECT(string_ne_ptr, std::declval<const std::string&>() != std::declval<const typename J::json_pointer&>());
JSON_TEST_DETECT(ptr_to_string, std::declval<const typename J::json_pointer&>().to_string());
// json_pointer with a basic_json type as template argument
template<typename J>
using legacy_ptr_t = const nlohmann::json_pointer<J>& ;
JSON_TEST_DETECT(legacy_ptr_value, std::declval<const J&>().value(std::declval<legacy_ptr_t<J>>(), 0));
JSON_TEST_DETECT(legacy_ptr_value_rvalue, std::declval<const J&>().value(std::declval<legacy_ptr_t<J>>(), std::string()));
JSON_TEST_DETECT(legacy_ptr_contains, std::declval<const J&>().contains(std::declval<legacy_ptr_t<J>>()));
JSON_TEST_DETECT(legacy_ptr_subscript, std::declval<J&>()[std::declval<legacy_ptr_t<J>>()]);
JSON_TEST_DETECT(legacy_ptr_subscript_const, std::declval<const J&>()[std::declval<legacy_ptr_t<J>>()]);
JSON_TEST_DETECT(legacy_ptr_at, std::declval<J&>().at(std::declval<legacy_ptr_t<J>>()));
JSON_TEST_DETECT(legacy_ptr_at_const, std::declval<const J&>().at(std::declval<legacy_ptr_t<J>>()));
JSON_TEST_DETECT(ptr_value, std::declval<const J&>().value(std::declval<const typename J::json_pointer&>(), 0));
JSON_TEST_DETECT(ptr_at, std::declval<J&>().at(std::declval<const typename J::json_pointer&>()));
#undef JSON_TEST_DETECT
} // namespace
TEST_CASE("JSON_DELETE_DEPRECATED_FUNCTIONS")
{
// MSVC 2015 does not treat selecting a deleted function in decltype as a
// substitution failure, so the traits cannot tell deleted functions apart
// there; calling them still fails to compile
#if !(defined(_MSC_VER) && _MSC_VER < 1910)
SECTION("from_* with a pointer and a length")
{
// the overloads are deleted rather than removed, so the length cannot
// silently bind to the strict parameter of from_*(InputType&&, bool)
CHECK_FALSE(from_cbor_ptr_len<json>::value);
CHECK_FALSE(from_msgpack_ptr_len<json>::value);
CHECK_FALSE(from_ubjson_ptr_len<json>::value);
CHECK_FALSE(from_bjdata_ptr_len<json>::value);
CHECK_FALSE(from_bon8_ptr_len<json>::value);
CHECK_FALSE(from_bson_ptr_len<json>::value);
// the replacement
CHECK(from_cbor_ptr_ptr<json>::value);
CHECK(from_msgpack_ptr_ptr<json>::value);
CHECK(from_ubjson_ptr_ptr<json>::value);
CHECK(from_bjdata_ptr_ptr<json>::value);
CHECK(from_bon8_ptr_ptr<json>::value);
CHECK(from_bson_ptr_ptr<json>::value);
}
SECTION("initializer lists of a pointer and a length")
{
CHECK_FALSE(from_cbor_init_list<json>::value);
CHECK_FALSE(from_msgpack_init_list<json>::value);
CHECK_FALSE(from_ubjson_init_list<json>::value);
CHECK_FALSE(from_bson_init_list<json>::value);
CHECK_FALSE(parse_init_list<json>::value);
CHECK_FALSE(accept_init_list<json>::value);
CHECK_FALSE(sax_parse_init_list<json>::value);
// the replacement
CHECK(parse_ptr_ptr<json>::value);
}
SECTION("iterator_wrapper")
{
CHECK_FALSE(iterator_wrapper<json>::value);
CHECK_FALSE(iterator_wrapper_const<json>::value);
// the replacement
CHECK(items<json>::value);
}
SECTION("stream operators with reversed operands")
{
CHECK_FALSE(json_ltlt_istream<json>::value);
CHECK_FALSE(json_gtgt_ostream<json>::value);
// the replacement
CHECK(istream_gtgt_json<json>::value);
CHECK(ostream_ltlt_json<json>::value);
}
SECTION("json_pointer")
{
// conversion to a string
CHECK_FALSE(std::is_constructible<std::string, json::json_pointer>::value);
CHECK_FALSE(std::is_convertible<json::json_pointer, std::string>::value);
// comparison with a string
CHECK_FALSE(ptr_eq_string<json>::value);
CHECK_FALSE(string_eq_ptr<json>::value);
CHECK_FALSE(ptr_eq_c_string<json>::value);
CHECK_FALSE(c_string_eq_ptr<json>::value);
CHECK_FALSE(ptr_ne_string<json>::value);
CHECK_FALSE(string_ne_ptr<json>::value);
// the replacement
CHECK(ptr_to_string<json>::value);
CHECK(ptr_eq_ptr<json>::value);
CHECK(ptr_ne_ptr<json>::value);
}
SECTION("json_pointer with a basic_json type as template argument")
{
CHECK_FALSE(legacy_ptr_value<json>::value);
CHECK_FALSE(legacy_ptr_value_rvalue<json>::value);
CHECK_FALSE(legacy_ptr_contains<json>::value);
CHECK_FALSE(legacy_ptr_subscript<json>::value);
CHECK_FALSE(legacy_ptr_subscript_const<json>::value);
CHECK_FALSE(legacy_ptr_at<json>::value);
CHECK_FALSE(legacy_ptr_at_const<json>::value);
// the replacement
CHECK(ptr_value<json>::value);
CHECK(ptr_at<json>::value);
}
#endif
SECTION("the non-deprecated functions still work")
{
const json j = {{"a", {1, 2}}};
const auto cbor = json::to_cbor(j);
CHECK(json::from_cbor(cbor.data(), cbor.data() + cbor.size()) == j);
const json::json_pointer ptr("/a/1");
CHECK(ptr == json::json_pointer("/a/1"));
CHECK(ptr.to_string() == "/a/1");
CHECK(j[ptr] == 2);
CHECK(j.at(ptr) == 2);
CHECK(j.value(ptr, 0) == 2);
CHECK(j.contains(ptr));
std::ostringstream os;
os << j;
CHECK(os.str() == R"({"a":[1,2]})");
std::istringstream is(os.str());
json parsed;
is >> parsed;
CHECK(parsed == j);
}
}
+14 -2
View File
@@ -16,6 +16,14 @@
#define JSON_TEST_STRICT_NUL_HANDLING_ENABLED 1
#endif
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
@@ -291,6 +299,7 @@ TEST_CASE("deserialization")
}));
}
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
SECTION("operator<<")
{
std::stringstream ss;
@@ -299,6 +308,7 @@ TEST_CASE("deserialization")
j << ss;
CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}}));
}
#endif
SECTION("operator>>")
{
@@ -423,6 +433,7 @@ TEST_CASE("deserialization")
CHECK_THROWS_WITH_AS(_ = json::parse(nullptr), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
}
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
SECTION("operator<<")
{
std::stringstream ss;
@@ -430,6 +441,7 @@ TEST_CASE("deserialization")
json j;
CHECK_THROWS_WITH_AS(j << ss, "[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&);
}
#endif
SECTION("operator>>")
{
@@ -1148,9 +1160,9 @@ TEST_CASE("deserialization")
{
std::istringstream s(bom + "123 456");
json j;
j << s;
s >> j;
CHECK(j == 123);
j << s;
s >> j;
CHECK(j == 456);
}
}
+5
View File
@@ -1551,6 +1551,10 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
// count(0) used to compile and then crash instead of failing to compile
using nlohmann::detail::is_detected;
// MSVC 2015 does not treat selecting a deleted function in decltype as
// a substitution failure, so it detects the deleted overloads as
// callable; calling them still fails to compile
#if !(defined(_MSC_VER) && _MSC_VER < 1910)
CHECK_FALSE(is_detected<can_call_find_with_0, Json&>::value);
CHECK_FALSE(is_detected<can_call_find_with_0, const Json&>::value);
CHECK_FALSE(is_detected<can_call_count_with_0, Json&>::value);
@@ -1562,6 +1566,7 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
// another integral literal type must be rejected as well, not just int
CHECK_FALSE(is_detected<can_call_contains_with_0L, Json&>::value);
#endif
// the valid overloads must remain callable
CHECK(is_detected<can_call_find, Json&, const char*>::value);
+246
View File
@@ -0,0 +1,246 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
// skip tests if JSON_DisableEnumSerialization=ON (#4384)
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
#endif
// This file tests the opt-in JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, so it defines
// the macro itself rather than relying on a -D flag, and runs in every build.
// The default behavior is tested in unit-enum_keyed_maps_default.cpp.
#ifdef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 1
#include <nlohmann/json.hpp>
using nlohmann::json;
using nlohmann::ordered_json;
#include <cstddef>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#define STRINGIZE_EX(x) #x
#define STRINGIZE(x) STRINGIZE_EX(x)
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
namespace
{
// std::hash is only required for enums since C++14
struct enum_hash
{
template<typename T>
std::size_t operator()(T t) const noexcept
{
return static_cast<std::size_t>(t);
}
};
} // namespace
// the example from #4378
enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
{
TS_STOPPED,
TS_RUNNING,
TS_COMPLETED,
TS_INVALID = -1,
};
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
{
{TS_INVALID, nullptr},
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
enum class color {red, green, blue}; // blue is not mapped and falls back to "red"
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(color,
{
{color::red, "red"},
{color::green, "green"},
})
enum class strict_color {red, green, blue}; // blue is not mapped
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_color,
{
{strict_color::red, "red"},
{strict_color::green, "green"},
})
enum class digit {zero, one};
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(digit,
{
{digit::zero, 0},
{digit::one, 1},
})
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
enum class plain {zero, one}; // serialized as integer
#endif
TEST_CASE("JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS")
{
SECTION("the macro is part of the ABI tag")
{
const std::string ns = STRINGIZE(NLOHMANN_JSON_NAMESPACE);
CHECK(ns.find("_ekmo") != std::string::npos);
}
SECTION("std::map (#4378)")
{
using task_map = std::map<TaskState, std::string>;
const task_map m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
const json j = m;
CHECK(j == json::parse(R"({"stopped":"aa","completed":"bb"})"));
CHECK(j.get<task_map>() == m);
json j2;
j2["x"] = m;
CHECK(j2.dump() == R"({"x":{"completed":"bb","stopped":"aa"}})");
}
SECTION("std::map with custom comparator")
{
using task_map = std::map<TaskState, int, std::greater<TaskState>>;
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}};
const json j = m;
CHECK(j == json::parse(R"({"stopped":1,"running":2})"));
CHECK(j.get<task_map>() == m);
}
SECTION("std::unordered_map")
{
using task_map = std::unordered_map<TaskState, int, enum_hash>;
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}};
const json j = m;
CHECK(j == json::parse(R"({"stopped":1,"running":2})"));
CHECK(j.get<task_map>() == m);
}
SECTION("nested maps")
{
using nested_map = std::map<color, std::map<TaskState, int>>;
const nested_map m = {{color::green, {{TS_RUNNING, 1}}}, {color::red, {}}};
const json j = m;
CHECK(j == json::parse(R"({"green":{"running":1},"red":{}})"));
CHECK(j.get<nested_map>() == m);
}
SECTION("ordered_json keeps the order of the map")
{
using task_map = std::map<TaskState, int>;
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}, {TS_COMPLETED, 3}};
const ordered_json j = m;
CHECK(j.dump() == R"({"stopped":1,"running":2,"completed":3})");
CHECK(j.get<task_map>() == m);
}
SECTION("empty map")
{
const json j = std::map<TaskState, int>();
CHECK(j.is_object());
CHECK(j.empty());
}
SECTION("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT")
{
using color_map = std::map<strict_color, int>;
const color_map m = {{strict_color::red, 1}, {strict_color::green, 2}};
const json j = m;
CHECK(j == json::parse(R"({"red":1,"green":2})"));
CHECK(j.get<color_map>() == m);
const color_map unmapped = {{strict_color::blue, 1}};
json _;
CHECK_THROWS_WITH_AS(_ = unmapped,
"[json.exception.out_of_range.410] enum value out of range for strict_color", json::out_of_range&);
}
SECTION("arrays of [key, value] pairs are still read")
{
using task_map = std::map<TaskState, int>;
const task_map m = {{TS_STOPPED, 1}};
CHECK(json::parse(R"([["stopped",1]])").get<task_map>() == m);
}
SECTION("other containers are not affected")
{
const std::vector<std::pair<TaskState, int>> pairs = {{TS_STOPPED, 1}};
const std::map<std::string, TaskState> string_keys = {{"a", TS_STOPPED}};
const std::map<int, int> int_keys = {{1, 2}};
CHECK(json(pairs) == json::parse(R"([["stopped",1]])"));
CHECK(json(string_keys) == json::parse(R"({"a":"stopped"})"));
CHECK(json(int_keys) == json::parse("[[1,2]]"));
}
SECTION("maps with non-unique keys are still stored as arrays of pairs")
{
const std::multimap<TaskState, int> mm = {{TS_STOPPED, 1}, {TS_STOPPED, 2}};
const std::unordered_multimap<TaskState, int, enum_hash> umm = {{TS_RUNNING, 3}, {TS_RUNNING, 3}};
CHECK(json(mm) == json::parse(R"([["stopped",1],["stopped",2]])"));
CHECK(json(umm) == json::parse(R"([["running",3],["running",3]])"));
}
SECTION("keys that do not serialize to strings")
{
const std::map<TaskState, int> null_key = {{TS_INVALID, 1}};
const std::map<digit, int> number_key = {{digit::zero, 1}};
json j = "unchanged";
// mapped to null
CHECK_THROWS_WITH_AS(j = null_key,
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
// mapped to a number
CHECK_THROWS_WITH_AS(j = number_key,
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
// enum without NLOHMANN_JSON_SERIALIZE_ENUM
const std::map<plain, int> plain_key = {{plain::zero, 1}};
CHECK_THROWS_WITH_AS(j = plain_key,
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
#endif
CHECK(j == "unchanged");
}
SECTION("keys that serialize to the same string")
{
const std::map<color, int> m = {{color::red, 1}, {color::blue, 2}};
json j = "unchanged";
// color::blue is not mapped and falls back to "red"
CHECK_THROWS_WITH_AS(j = m,
"[json.exception.type_error.318] duplicate object key 'red'", json::type_error&);
CHECK(j == "unchanged");
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP
+144
View File
@@ -0,0 +1,144 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
// This file tests maps with enum keys with the default setting of
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS (or whatever a -D flag sets it to).
// unit-enum_keyed_maps.cpp tests JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1.
// These tests are not part of unit-conversions.cpp, because that object file
// is already too big for the MinGW linker of some compilers.
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cstddef>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
enum class cards {kreuz, pik, herz, karo};
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(cards,
{
{cards::kreuz, "kreuz"},
{cards::pik, "pik"},
{cards::herz, "herz"},
{cards::karo, "karo"}
})
enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
{
TS_STOPPED,
TS_RUNNING,
TS_COMPLETED,
TS_INVALID = -1,
};
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
{
{TS_INVALID, nullptr},
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
{
{strict_cards::kreuz, "kreuz"},
{strict_cards::pik, "pik"},
{strict_cards::herz, "herz"},
{strict_cards::karo, "karo"}
})
namespace
{
// std::hash is only required for enums since C++14
struct enum_hash
{
template<typename T>
std::size_t operator()(T t) const noexcept
{
return static_cast<std::size_t>(t);
}
};
} // namespace
// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1
TEST_CASE("maps with enum keys")
{
using task_map = std::map<TaskState, std::string>;
using task_umap = std::unordered_map<TaskState, std::string, enum_hash>;
using task_gmap = std::map<TaskState, std::string, std::greater<TaskState>>;
using nested_map = std::map<cards, std::map<TaskState, int>>;
using strict_map = std::map<strict_cards, int>;
using int_map = std::map<int, int>;
using int_umap = std::unordered_map<int, int>;
const task_map m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
#if !JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
SECTION("stored as array of pairs")
{
CHECK(json(m) == json::parse(R"([["stopped","aa"],["completed","bb"]])"));
CHECK(json(task_umap {{TS_RUNNING, "cc"}}) == json::parse(R"([["running","cc"]])"));
}
#endif
SECTION("read from array of pairs")
{
CHECK(json::parse(R"([["stopped","aa"],["completed","bb"]])").get<task_map>() == m);
}
SECTION("read from object (#4378)")
{
const json j = json::parse(R"({"stopped":"aa","completed":"bb"})");
CHECK(j.get<task_map>() == m);
CHECK(j.get<task_umap>() == task_umap(m.begin(), m.end()));
CHECK(j.get<task_gmap>() == task_gmap(m.begin(), m.end()));
CHECK(json::parse(R"({"kreuz":{"stopped":1}})").get<nested_map>() == nested_map {{cards::kreuz, {{TS_STOPPED, 1}}}});
CHECK(nlohmann::ordered_json::parse(R"({"stopped":"aa","completed":"bb"})").get<task_map>() == m);
// object keys go through the enum's from_json
strict_map sm;
CHECK_THROWS_WITH_AS(json::parse(R"({"what?":1})").get_to(sm),
"[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&);
}
SECTION("objects are only read for enum keys")
{
// built rather than parsed, so that the messages do not gain a byte
// range with JSON_DIAGNOSTIC_POSITIONS
const json j = {{"1", 2}};
int_map im;
int_umap ium;
CHECK_THROWS_WITH_AS(j.get_to(im),
"[json.exception.type_error.302] type must be array, but is object", json::type_error&);
CHECK_THROWS_WITH_AS(j.get_to(ium),
"[json.exception.type_error.302] type must be array, but is object", json::type_error&);
}
SECTION("other types are rejected")
{
task_map tm;
CHECK_THROWS_WITH_AS(json("stopped").get_to(tm),
"[json.exception.type_error.302] type must be array, but is string", json::type_error&);
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP
+10
View File
@@ -8,6 +8,14 @@
#include "doctest_compatibility.h"
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -19,6 +27,7 @@ DOCTEST_GCC_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wrange-loop-construct")
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
TEST_CASE("iterator_wrapper")
{
SECTION("object")
@@ -715,6 +724,7 @@ TEST_CASE("iterator_wrapper")
}
}
}
#endif
TEST_CASE("items()")
{
+19 -4
View File
@@ -9,6 +9,14 @@
#include "doctest_compatibility.h"
#define JSON_TESTS_PRIVATE
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
@@ -564,7 +572,9 @@ TEST_CASE("JSON pointers")
std::stringstream ss;
ss << ptr;
CHECK(ptr.to_string() == ptr_str);
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
CHECK(std::string(ptr) == ptr_str);
#endif
CHECK(ss.str() == ptr_str);
}
}
@@ -744,8 +754,6 @@ TEST_CASE("JSON pointers")
SECTION("equality comparison")
{
const char* ptr_cpstring = "/foo/bar";
const char ptr_castring[] = "/foo/bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
std::string ptr_string{"/foo/bar"};
auto ptr1 = json::json_pointer(ptr_string);
auto ptr2 = json::json_pointer(ptr_string);
@@ -755,6 +763,12 @@ TEST_CASE("JSON pointers")
CHECK(ptr1 == ptr2);
CHECK_FALSE(ptr1 != ptr2);
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
const char* ptr_cpstring = "/foo/bar";
const char ptr_castring[] = "/foo/bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
CHECK(ptr1 == "/foo/bar");
CHECK(ptr1 == ptr_cpstring);
CHECK(ptr1 == ptr_castring);
@@ -765,8 +779,6 @@ TEST_CASE("JSON pointers")
CHECK(ptr_castring == ptr1);
CHECK(ptr_string == ptr1);
CHECK_FALSE(ptr1 != ptr2);
CHECK_FALSE(ptr1 != "/foo/bar");
CHECK_FALSE(ptr1 != ptr_cpstring);
CHECK_FALSE(ptr1 != ptr_castring);
@@ -788,6 +800,7 @@ TEST_CASE("JSON pointers")
CHECK_THROWS_WITH_AS("/~~" == ptr1,
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
}
#endif
}
SECTION("less-than comparison")
@@ -839,6 +852,7 @@ TEST_CASE("JSON pointers")
json_ptr_j ptr_j{ptr_string};
json_ptr_oj ptr_oj{ptr_string};
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
CHECK(j.contains(ptr));
CHECK(j.contains(ptr_j));
CHECK(j.contains(ptr_oj));
@@ -851,6 +865,7 @@ TEST_CASE("JSON pointers")
CHECK(j.value(ptr, "x") == j.value(ptr_j, "x"));
CHECK(j.value(ptr, "x") == j.value(ptr_oj, "x"));
#endif
CHECK(ptr == ptr_j);
CHECK(ptr == ptr_oj);
+17 -68
View File
@@ -134,7 +134,7 @@ TEST_CASE("json_view")
"\"\"", "\"text\"", "[]", "[1,2,3]", "{}", "{\"a\":1,\"b\":2}" // NOLINT(modernize-raw-string-literal)
})
{
CAPTURE(text);
CAPTURE(text)
const json j = json::parse(text);
const json_document d = json_document::parse(text);
const json_view v = d.root();
@@ -175,7 +175,7 @@ TEST_CASE("json_view")
{
std::string text;
g.value(text, 0);
CAPTURE(text);
CAPTURE(text)
CHECK(json_document::parse(text).root().materialize() == json::parse(text));
// member order as ordered_json::parse keeps it
CHECK(ordered_json_document::parse(text).root().materialize().dump() == ordered_json::parse(text).dump());
@@ -202,7 +202,7 @@ TEST_CASE("json_view")
"1e400", "-1e400", "[1.7976931348623159e308]", "{\"a\":\n{\"b\": [1, 2,\n 3 x]}}"
})
{
CAPTURE(text);
CAPTURE(text)
#if !defined(JSON_NOEXCEPTION)
const std::string expected = parse_exception(text);
REQUIRE(!expected.empty());
@@ -226,7 +226,7 @@ TEST_CASE("json_view")
{"// c\n[1]", "[1, /* c */ 2]", "[1,]", "{\"a\":1,}", "[1,/* c */]", "/", "/* ", "[1,,]"
})
{
CAPTURE(text);
CAPTURE(text)
for (int options = 0; options < 4; ++options)
{
const bool comments = (options & 1) != 0;
@@ -254,7 +254,7 @@ TEST_CASE("json_view")
"1e39", "-1e39", "[3.4028235e38]", "[3.4028236e38]", "{\"a\": [1e38, -3.4028235e38]}", "{\"a\": [1e-50, 1e39]}"
})
{
CAPTURE(text);
CAPTURE(text)
const bool accepted = json_float::accept(text);
CHECK(float_document::accept(text) == accepted);
CHECK(float_document::parse(text, false).is_discarded() == !accepted);
@@ -540,7 +540,7 @@ TEST_CASE("json_view element access and iteration")
{
std::string text;
g.value(text, 0);
CAPTURE(text);
CAPTURE(text)
const ordered_json_document d = ordered_json_document::parse(text);
check_access(d.root(), ordered_json::parse(text));
}
@@ -566,7 +566,7 @@ TEST_CASE("json_view element access and iteration")
const json_view root = d.root();
for (std::size_t i = 0; i < keys.size(); ++i)
{
CAPTURE(keys[i]);
CAPTURE(keys[i])
CHECK(root[keys[i]].materialize() == i);
CHECK(root.at(keys[i]).materialize() == i);
CHECK(root.find(keys[i]).key() == keys[i]);
@@ -604,7 +604,7 @@ TEST_CASE("json_view element access and iteration")
{"null", "true", "42", "-1", "1.5", "\"s\"", "[]", "[1,2]", "{}", "{\"a\":1}"
})
{
CAPTURE(text);
CAPTURE(text)
const json_document d = json_document::parse(text);
const json_view v = d.root();
const json j = v.materialize();
@@ -862,7 +862,7 @@ TEST_CASE("json_view values")
{
std::string text;
g.value(text, 0);
CAPTURE(text);
CAPTURE(text)
const ordered_json_document d = ordered_json_document::parse(text);
check_values(d.root(), ordered_json::parse(text), text);
}
@@ -916,7 +916,7 @@ TEST_CASE("json_view values")
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
for (const auto& token : tokens)
{
CAPTURE(token);
CAPTURE(token)
const std::string text = "[" + token + "]";
const double b = json::parse(text)[0].get<double>();
CHECK(bits(json_document::parse(text).root()[0].get<double>()) == bits(b));
@@ -1025,7 +1025,7 @@ TEST_CASE("json_view JSON pointers")
{
continue;
}
CAPTURE(text);
CAPTURE(text)
const ordered_json j = ordered_json::parse(text);
const ordered_json flat = j.flatten();
for (const auto& leaf : flat.items())
@@ -1033,7 +1033,7 @@ TEST_CASE("json_view JSON pointers")
// the leaf and each of its parents
for (ordered_json::json_pointer p(leaf.key());; p = p.parent_pointer())
{
CAPTURE(p.to_string());
CAPTURE(p.to_string())
CHECK(d.root()[p].materialize() == j[p]);
CHECK(d.root().at(p).materialize() == j.at(p));
CHECK(d.root().contains(p));
@@ -1058,7 +1058,7 @@ TEST_CASE("json_view JSON pointers")
"/a/18446744073709551615", "/a/-1", "/a/+1", "/a/ 1", "/x", "/c/x", "/a/0/x", "/a/1/b/c", "/a~0b", "/c~1d", "/c~1d/x", "/a/1/-"
})
{
CAPTURE(pointer);
CAPTURE(pointer)
const json::json_pointer p(pointer);
const std::string at_error = without_path(exception_of([&] { static_cast<void>(j.at(p)); }));
CHECK(exception_of([&] { static_cast<void>(v.at(p)); }) == at_error);
@@ -1105,7 +1105,7 @@ TEST_CASE("json_view dump")
{
continue;
}
CAPTURE(text);
CAPTURE(text)
const ordered_json j = ordered_json::parse(text);
for (const int indent :
{
@@ -1147,57 +1147,6 @@ TEST_CASE("json_view dump")
CHECK(d.root().dump() == json::parse(text).dump());
CHECK(d.root().dump() == "[1.5,100.0,0,-0.0,1.2345678901234568e+29,18446744073709551615,-9223372036854775808,0.1,1e-07,5e-324]");
CHECK(d.root().dump(-1, ' ', false, json_view::number_format::source) == "[1.50,1E2,-0,-0.0,123456789012345678901234567890,18446744073709551615,-9223372036854775808,0.1,1e-7,5e-324]");
// also indented, and with ensure_ascii
CHECK(d.root().dump(0, ' ', false, json_view::number_format::source) == "[\n1.50,\n1E2,\n-0,\n-0.0,\n123456789012345678901234567890,\n18446744073709551615,\n-9223372036854775808,\n0.1,\n1e-7,\n5e-324\n]");
CHECK(d.root().dump(-1, ' ', true, json_view::number_format::source) == "[1.50,1E2,-0,-0.0,123456789012345678901234567890,18446744073709551615,-9223372036854775808,0.1,1e-7,5e-324]");
// float tokens of up to 17 significant digits in every spelling: those
// of at most 15 digits are written from their digits, the others
// through the conversion; both as dump() writes them
{
std::mt19937_64 tokens(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
// a number below n; the remainder is a std::uint64_t, which is
// std::size_t on some platforms and wider on others
const auto draw = [&tokens](std::size_t n)
{
const std::uint64_t r = tokens() % n;
return static_cast<std::size_t>(r);
};
std::string many_tokens = "[";
for (int i = 0; i < 20000; ++i)
{
const std::size_t length = 1 + draw(17);
std::string digits(1, static_cast<char>('1' + draw(9)));
for (std::size_t k = 1; k < length; ++k)
{
digits += static_cast<char>('0' + draw(10));
}
digits += std::string(draw(4), '0'); // trailing zeros
std::string token = draw(3) == 0 ? "-" : "";
const std::size_t point = draw(digits.size() + 1);
if (point == 0)
{
token += "0." + std::string(draw(5), '0') + digits;
}
else
{
token += digits.substr(0, point) + (point < digits.size() ? "." + digits.substr(point) : "");
}
// an exponent that keeps the value between about 1e-320 and 1e300
const int exponent = static_cast<int>(draw(600)) - 300 - static_cast<int>(point);
if (draw(4) != 0)
{
token += (draw(2) == 0 ? "e" : "E") + std::string(exponent >= 0 && draw(2) == 0 ? "+" : "") + std::to_string(exponent);
}
else if (point == digits.size())
{
token += ".0"; // (a float, not an integer)
}
many_tokens += (i != 0 ? "," : "") + token;
}
many_tokens += ']';
CHECK(json_document::parse(many_tokens).root().dump() == json::parse(many_tokens).dump());
}
// random doubles, written as parse() and dump() would
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
@@ -1263,8 +1212,8 @@ TEST_CASE("json_view comparison")
{
for (std::size_t k = i; k < i + 3; ++k)
{
CAPTURE(texts[i]);
CAPTURE(texts[k]);
CAPTURE(texts[i])
CAPTURE(texts[k])
const json_document a = json_document::parse(texts[i]);
const json_document b = json_document::parse(texts[k]);
const json ja = json::parse(texts[i]);
@@ -1337,7 +1286,7 @@ TEST_CASE("json_view large objects")
127u, 128u, 129u, 10000u
})
{
CAPTURE(members);
CAPTURE(members)
std::string text = "{";
for (std::size_t i = 0; i < members; ++i)
{
+4 -4
View File
@@ -122,7 +122,7 @@ json value_of(const built& b)
// with and without a NUL after the text
void check_same(const std::string& text)
{
CAPTURE(text);
CAPTURE(text)
for (int options = 0; options < 4; ++options)
{
const bool comments = (options & 1) != 0;
@@ -161,7 +161,7 @@ void check_string(const std::string& content)
const built b = build(text, false, false, sentinel);
if (b.ok != accepted || (accepted && value_of(b) != json::parse(text)))
{
CAPTURE(text);
CAPTURE(text)
CHECK(b.ok == accepted);
CHECK((b.ok && accepted ? value_of(b) == json::parse(text) : true));
}
@@ -303,7 +303,7 @@ TEST_CASE("json_view builder")
"[1.5e38, 3.5e38]", "{\"a\": 1e-50, \"b\": 1e39}"
})
{
CAPTURE(text);
CAPTURE(text)
const bool float_accepted = float_json::accept(text);
for (const bool sentinel :
{
@@ -416,7 +416,7 @@ TEST_CASE("json_view builder")
"/nativejson-benchmark/twitter.json", "/json_tests/pass1.json", "/json_tests/pass2.json", "/json_tests/pass3.json"
})
{
CAPTURE(name);
CAPTURE(name)
std::ifstream f(std::string(TEST_DATA_DIRECTORY) + name, std::ios::binary);
std::stringstream ss;
ss << f.rdbuf();
+4 -18
View File
@@ -26,7 +26,6 @@ using ptr_t = ordered_json::json_pointer;
#include <functional>
#include <iterator>
#include <limits>
#include <map>
#include <random>
#include <string>
#include <vector>
@@ -212,7 +211,7 @@ TEST_CASE("json_view edits: differential")
j = ordered_json::object({{"a", random_value(1)}, {"b", random_value(1)}});
}
const std::string text = j.dump(r(2) == 0 ? -1 : 2);
CAPTURE(text);
CAPTURE(text)
ordered_json_editable_document d = ordered_json_editable_document::parse(text);
j = ordered_json::parse(text);
const ordered_json_document other = ordered_json_document::parse(random_value(0).dump());
@@ -338,8 +337,8 @@ TEST_CASE("json_view edits: differential")
continue;
}
}
CAPTURE(p.to_string());
CAPTURE(op);
CAPTURE(p.to_string())
CAPTURE(op)
check_all(d, j, e % 8 == 7 || e == edits - 1);
}
}
@@ -402,7 +401,7 @@ TEST_CASE("json_view edits: errors")
{"\xC3\x28", "a\xE2\x28\xA1", "\xF0\x28\x8C\xBC", "\xE2\x82", "x\xF0\x9F\x98", "\xFF", "\xED\xA0\x80", "\xC0\xAF"
})
{
CAPTURE(bad);
CAPTURE(bad)
const std::string expected = exception_of_call([&]
{
const std::string text = json(bad).dump();
@@ -482,19 +481,6 @@ TEST_CASE("json_view edits: views and values")
CHECK(d.root().materialize().dump() == json::parse(R"([1.5, 100.0, 0.1, null, null, 18446744073709551615, -9223372036854775808])").dump());
}
SECTION("numbers of other float types")
{
// doubles have their own path to the output; other float types are
// written as basic_json writes them, non-finite values as null
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
using document_float = nlohmann::basic_json_document<json_float, true>;
document_float d = document_float::parse("[1.5]");
d.push_back(d.root(), std::numeric_limits<float>::quiet_NaN());
d.push_back(d.root(), -std::numeric_limits<float>::infinity());
CHECK(d.root().dump() == "[1.5,null,null]");
CHECK(d.root().dump(2) == json_float::parse("[1.5, null, null]").dump(2));
}
SECTION("nulls become containers, and the root can be replaced")
{
json_editable_document d = json_editable_document::parse("[null, null]");
+3 -3
View File
@@ -168,7 +168,7 @@ TEST_CASE("json_view images: round trips")
" [1 , 2 ] "
})
{
CAPTURE(text);
CAPTURE(text)
// false positive: parse() returns a document with a root
// @infer-ignore NULLPTR_DEREFERENCE
check_round_trip(json_document::parse(text));
@@ -186,7 +186,7 @@ TEST_CASE("json_view images: round trips")
"/nativejson-benchmark/twitter.json", "/json_tests/pass1.json", "/json_tests/pass2.json", "/json_tests/pass3.json"
})
{
CAPTURE(name);
CAPTURE(name)
const std::string text = read_file(name);
const json_document d = json_document::parse(text);
check_round_trip(d);
@@ -698,7 +698,7 @@ TEST_CASE("json_view images: check")
"x.5e300", "01.5e30", "1.xe300", "1.5ex00", "1.5e+x0", "1.5e30x", "-.5e300", "1.5E300"
})
{
CAPTURE(token);
CAPTURE(token)
std::vector<std::uint8_t> b = img;
node n = node_at(b, 1);
n.extra = 0xFFFFu;
+54 -3
View File
@@ -8,6 +8,14 @@
#include "doctest_compatibility.h"
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
#ifdef JSON_TEST_NO_GLOBAL_UDLS
@@ -33,10 +41,49 @@ TEST_CASE("MessagePack")
{
SECTION("discarded")
{
// discarded values are not serialized
// a discarded value cannot be serialized to MessagePack
json const j = json::value_t::discarded;
const auto result = json::to_msgpack(j);
CHECK(result.empty());
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
}
SECTION("discarded values nested in a container")
{
json const discarded = json::value_t::discarded;
SECTION("in an array")
{
json const j = {1, discarded, 2};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to MessagePack", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
#endif
}
SECTION("as an object value")
{
json j;
j["a"] = 1;
j["b"] = discarded;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to MessagePack", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
#endif
}
SECTION("nested deeper (array in object in array)")
{
json inner_array = {1, discarded};
json middle_object;
middle_object["x"] = inner_array;
json const j = {middle_object};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to MessagePack", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
#endif
}
}
SECTION("null")
@@ -1795,8 +1842,10 @@ TEST_CASE("MessagePack input that cannot be read is discarded by every overload"
CHECK_THROWS_AS(_ = json::from_msgpack(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_msgpack(input, true, false).is_discarded());
CHECK(json::from_msgpack(input.begin(), input.end(), true, false).is_discarded());
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
CHECK(json::from_msgpack(input.data(), input.size(), true, false).is_discarded());
CHECK(json::from_msgpack({input.data(), input.size()}, true, false).is_discarded());
#endif
}
TEST_CASE("MessagePack SAX parsing stops at every event")
@@ -2099,12 +2148,14 @@ TEST_CASE("MessagePack roundtrips" * doctest::skip())
CHECK(j1 == j2);
}
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
{
INFO_WITH_TEMP(filename + ": uint8_t* and size");
json j2;
CHECK_NOTHROW(j2 = json::from_msgpack({packed.data(), packed.size()}));
CHECK(j1 == j2);
}
#endif
{
INFO_WITH_TEMP(filename + ": output to output adapters");
+288 -1
View File
@@ -40,7 +40,9 @@ using ordered_json = nlohmann::ordered_json;
#endif
#include <cstdio>
#include <cstdlib>
#include <list>
#include <new>
#include <tuple>
#include <type_traits>
#include <utility>
@@ -107,6 +109,84 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
namespace
{
// An allocator whose allocate() can be told to fail on demand, so tests can
// check that ~basic_json() tolerates - in fact, after #5135, never even
// triggers - an allocation failure. This replaces an earlier version of
// this test that overrode the process-wide ::operator new/::operator
// delete, which affected every allocation in the whole unit-regression2
// binary rather than just the values under test.
std::size_t failing_allocator_allocations = 0;
std::size_t failing_allocator_deallocations = 0;
bool fail_next_allocation = false;
template<class T>
struct failing_allocator : std::allocator<T>
{
using std::allocator<T>::allocator;
failing_allocator() noexcept = default;
template<class U>
failing_allocator(const failing_allocator<U>& /*unused*/) noexcept {} // NOLINT(google-explicit-constructor)
T* allocate(std::size_t n)
{
if (fail_next_allocation)
{
fail_next_allocation = false;
throw std::bad_alloc();
}
++failing_allocator_allocations;
return std::allocator<T>::allocate(n);
}
void deallocate(T* p, std::size_t n)
{
++failing_allocator_deallocations;
std::allocator<T>::deallocate(p, n);
}
template<class U>
struct rebind
{
using other = failing_allocator<U>;
};
};
using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
std::int64_t, std::uint64_t, double, failing_allocator>;
using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool,
std::int64_t, std::uint64_t, double, failing_allocator>;
// builds `depth` levels of nesting around a scalar, iteratively (never
// recursing: each wrap only moves the previous, already-built value, which
// is O(1)), each level an array or an object depending on `nest_objects`
template<class BasicJsonType>
BasicJsonType make_deep_nest(std::size_t depth, bool nest_objects)
{
BasicJsonType v = 0;
for (std::size_t i = 0; i < depth; ++i)
{
if (nest_objects)
{
BasicJsonType wrapper = BasicJsonType::object();
wrapper["x"] = std::move(v);
v = std::move(wrapper);
}
else
{
BasicJsonType wrapper = BasicJsonType::array();
wrapper.push_back(std::move(v));
v = std::move(wrapper);
}
}
return v;
}
} // namespace
#endif
/////////////////////////////////////////////////////////////////////
// for #1647
/////////////////////////////////////////////////////////////////////
@@ -569,7 +649,7 @@ TEST_CASE("regression tests 2")
SECTION("issue #2067 - cannot serialize binary data to text JSON")
{
const std::array<unsigned char, 23> data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}};
const json j = json::from_msgpack(data.data(), data.size());
const json j = json::from_msgpack(data.begin(), data.end());
// dump() is nodiscard; this only checks that dumping does not throw
CHECK_NOTHROW(
utils::ignore_return_value(
@@ -940,4 +1020,211 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
}
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
TEST_CASE("regression test #5135 - destructor never allocates, even under memory pressure")
{
// Before the fix, ~basic_json() flattened a nested array/object into a
// heap-allocated std::vector to avoid recursing; that allocation could
// itself throw bad_alloc, which escapes a noexcept destructor and
// terminates the program. destroy() no longer allocates anything, so
// none of the sections below ever observe fail_next_allocation being
// consumed: CHECK(fail_next_allocation) confirms it was never touched.
SECTION("the original report: a small, mixed array/object nest")
{
failing_allocator_allocations = 0;
failing_allocator_deallocations = 0;
{
failing_json j = failing_json::array(
{
failing_json::array({1, 2}),
failing_json::object({{"key", failing_json::array({3})}})
});
fail_next_allocation = true;
} // j is destroyed here, with every further allocation set to fail
CHECK(fail_next_allocation);
fail_next_allocation = false;
CHECK(failing_allocator_deallocations > 0);
}
SECTION("100000-deep nested array")
{
std::size_t allocations_before = 0;
{
failing_json j = make_deep_nest<failing_json>(100000, false);
allocations_before = failing_allocator_allocations;
fail_next_allocation = true;
}
CHECK(fail_next_allocation);
fail_next_allocation = false;
CHECK(failing_allocator_allocations == allocations_before);
}
SECTION("100000-deep nested object")
{
std::size_t allocations_before = 0;
{
failing_json j = make_deep_nest<failing_json>(100000, true);
allocations_before = failing_allocator_allocations;
fail_next_allocation = true;
}
CHECK(fail_next_allocation);
fail_next_allocation = false;
CHECK(failing_allocator_allocations == allocations_before);
}
SECTION("100000-deep nested ordered_json")
{
std::size_t allocations_before = 0;
{
failing_ordered_json j = make_deep_nest<failing_ordered_json>(100000, true);
allocations_before = failing_allocator_allocations;
fail_next_allocation = true;
}
CHECK(fail_next_allocation);
fail_next_allocation = false;
CHECK(failing_allocator_allocations == allocations_before);
}
SECTION("wide and deep: 1000 arrays of 1000 elements, each a small nested object")
{
std::size_t allocations_before = 0;
{
failing_json wide = failing_json::array();
for (std::size_t i = 0; i < 1000; ++i)
{
failing_json inner = failing_json::array();
for (std::size_t k = 0; k < 1000; ++k)
{
inner.push_back(failing_json::object({{"a", 1}, {"b", failing_json::array({1, 2, 3})}}));
}
wide.push_back(std::move(inner));
}
allocations_before = failing_allocator_allocations;
fail_next_allocation = true;
}
CHECK(fail_next_allocation);
fail_next_allocation = false;
CHECK(failing_allocator_allocations == allocations_before);
}
}
#endif
namespace
{
// a single-element chain of `depth` arrays, built iteratively (never
// recursing: each wrap only moves the previous, already-built value)
template<class BasicJsonType>
BasicJsonType make_single_chain(std::size_t depth)
{
BasicJsonType v = 1;
for (std::size_t i = 0; i < depth; ++i)
{
BasicJsonType wrapper = BasicJsonType::array();
wrapper.push_back(std::move(v));
v = std::move(wrapper);
}
return v;
}
// copies value first, to make sure nothing was corrupted by building it,
// then lets both the copy and the original destruct via normal scope exit
template<class BasicJsonType>
void check_destroy_edge_case(const BasicJsonType& value)
{
const BasicJsonType copy = value;
CHECK(copy == value);
}
} // namespace
TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType, json, ordered_json)
{
using binary_t = typename BasicJsonType::binary_t;
SECTION("mix of empty objects, empty arrays, non-empty containers, and scalars")
{
BasicJsonType root = BasicJsonType::array();
root.push_back(BasicJsonType::object());
root.push_back(BasicJsonType::array());
root.push_back(BasicJsonType::object({{"k", 1}}));
root.push_back(BasicJsonType::array({1, 2, 3}));
root.push_back(nullptr);
root.push_back(true);
root.push_back(42);
root.push_back(3.14);
root.push_back("a string");
root.push_back(BasicJsonType(binary_t({1, 2, 3})));
check_destroy_edge_case(root);
}
SECTION("container child in first position only")
{
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1, 2}), 3, 4, 5});
check_destroy_edge_case(root);
}
SECTION("container child in last position only")
{
BasicJsonType root = BasicJsonType::array({1, 2, 3, BasicJsonType::array({4, 5})});
check_destroy_edge_case(root);
}
SECTION("container children in first and last position")
{
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1}), 2, 3, BasicJsonType::array({4})});
check_destroy_edge_case(root);
}
SECTION("single-element chain, 1000 levels deep")
{
BasicJsonType root = make_single_chain<BasicJsonType>(1000);
check_destroy_edge_case(root);
}
SECTION("top-level empty array")
{
BasicJsonType root = BasicJsonType::array();
check_destroy_edge_case(root);
}
SECTION("top-level empty object")
{
BasicJsonType root = BasicJsonType::object();
check_destroy_edge_case(root);
}
SECTION("object whose last child is a non-empty array whose last child is an empty object")
{
BasicJsonType inner_array = BasicJsonType::array({1, 2, BasicJsonType::object()});
BasicJsonType root = BasicJsonType::object({{"a", 1}, {"b", inner_array}});
check_destroy_edge_case(root);
}
SECTION("destruction via erase() on a deeply nested child")
{
BasicJsonType root = BasicJsonType::array();
root.push_back(make_single_chain<BasicJsonType>(500));
root.push_back(BasicJsonType::object({{"k", BasicJsonType::array({1, 2, 3})}}));
// erase() must destroy the removed subtree without recursing or
// allocating beyond what erase() itself needs
root.erase(0);
CAPTURE(root.size())
CHECK(root.size() == 1);
}
SECTION("destruction via assignment on a deep tree")
{
BasicJsonType root = make_single_chain<BasicJsonType>(2000);
// assigning a new value destroys the old one in place
root = nullptr;
CHECK(root.is_null());
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP
+10
View File
@@ -8,6 +8,14 @@
#include "doctest_compatibility.h"
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -48,6 +56,7 @@ TEST_CASE("serialization")
}
}
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
SECTION("operator>>")
{
SECTION("no given width")
@@ -79,6 +88,7 @@ TEST_CASE("serialization")
"[\n\t\"foo\",\n\t1,\n\t2,\n\t3,\n\tfalse,\n\t{\n\t\t\"one\": 1\n\t}\n]");
}
}
#endif
SECTION("dump")
{
+6 -5
View File
@@ -643,7 +643,8 @@ std::pair<std::string, int> digits_and_exponent(const std::string& s)
bool reads_back(const std::string& digits, int e, double v)
{
const std::string text = digits + "e" + std::to_string(e);
return std::strtod(text.c_str(), nullptr) == v;
// (compared bit for bit: v is positive and finite, and -Wfloat-equal)
return reinterpret_bits<std::uint64_t>(std::strtod(text.c_str(), nullptr)) == reinterpret_bits<std::uint64_t>(v);
}
/// Check the representation of a positive finite double: it reads back as
@@ -653,7 +654,7 @@ void check_shortest(double v)
std::array<char, 33> buf{};
char* end = nlohmann::detail::to_chars(buf.data(), buf.data() + 32, v);
const std::string text(buf.data(), end);
CAPTURE(text);
CAPTURE(text)
CHECK(std::strtod(text.c_str(), nullptr) == v);
// the layout is that of format_buffer() for the same digits
std::array<char, 64> reference{};
@@ -686,7 +687,7 @@ void check_shortest(double v)
mid - 1, mid, mid + 1
})
{
CAPTURE(candidate);
CAPTURE(candidate)
CHECK(!reads_back(std::to_string(candidate), e, v));
}
}
@@ -706,7 +707,7 @@ TEST_CASE("shortest digits of doubles")
// the 128-bit significands of 10^k, rounded down, recomputed
for (int k = -342; k <= 341; ++k)
{
CAPTURE(k);
CAPTURE(k)
big x{1};
if (k >= 0)
{
@@ -766,7 +767,7 @@ TEST_CASE("shortest digits of doubles")
{
const std::uint64_t bits = rng() & 0x7FFFFFFFFFFFFFFFu;
const auto v = reinterpret_bits<double>(bits);
if (std::isfinite(v) && v != 0)
if (std::isfinite(v) && bits != 0)
{
check_shortest(v);
}
+71 -5
View File
@@ -8,6 +8,14 @@
#include "doctest_compatibility.h"
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -27,10 +35,59 @@ TEST_CASE("UBJSON")
{
SECTION("discarded")
{
// discarded values are not serialized
// a discarded value cannot be serialized to UBJSON
json const j = json::value_t::discarded;
const auto result = json::to_ubjson(j);
CHECK(result.empty());
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
}
SECTION("discarded values nested in a container")
{
json const discarded = json::value_t::discarded;
SECTION("in an array")
{
json const j = {1, discarded, 2};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to UBJSON", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
#endif
}
SECTION("as an object value")
{
json j;
j["a"] = 1;
j["b"] = discarded;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to UBJSON", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
#endif
}
SECTION("nested deeper (array in object in array)")
{
json inner_array = {1, discarded};
json middle_object;
middle_object["x"] = inner_array;
json const j = {middle_object};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to UBJSON", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
#endif
}
SECTION("optimized array of all-discarded elements")
{
json const j = {discarded, discarded};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
#endif
}
}
SECTION("null")
@@ -2091,9 +2148,14 @@ TEST_CASE("UBJSON")
SECTION("discarded")
{
// a discarded value cannot be serialized to UBJSON, even as part
// of an optimized array of a single (here: valueless) type
json const j = {json::value_t::discarded, json::value_t::discarded};
std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
CHECK(json::to_ubjson(j, true, true) == expected);
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
#endif
}
}
}
@@ -2212,8 +2274,10 @@ TEST_CASE("UBJSON input that cannot be read is discarded by every overload")
CHECK_THROWS_AS(_ = json::from_ubjson(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_ubjson(input, true, false).is_discarded());
CHECK(json::from_ubjson(input.begin(), input.end(), true, false).is_discarded());
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
CHECK(json::from_ubjson(input.data(), input.size(), true, false).is_discarded());
CHECK(json::from_ubjson({input.data(), input.size()}, true, false).is_discarded());
#endif
}
TEST_CASE("UBJSON SAX parsing stops at every event")
@@ -2961,12 +3025,14 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip())
CHECK(j1 == j2);
}
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
{
INFO_WITH_TEMP(filename + ": uint8_t* and size");
json j2;
CHECK_NOTHROW(j2 = json::from_ubjson({packed.data(), packed.size()}));
CHECK(j1 == j2);
}
#endif
{
INFO_WITH_TEMP(filename + ": output to output adapters");