Compare commits

...
Author SHA1 Message Date
Niels Lohmann 42a22d2d5e Merge remote-tracking branch 'origin/develop' into fix/develop-ci-after-merges
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 15:01:16 +02:00
Niels Lohmann 45db371915 Regenerate BUILD.bazel and nlohmann_json.natvis
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

#5746 added detail/output/error_handler.hpp and #5741 the json_abi_sbu8 ABI tag.
2026-10-04 14:50:19 +02:00
Niels Lohmann 5d47284e6c Title the macro examples and add JSON_STRICT_BINARY_UTF8 to the docset
The documentation style check requires "Example: ..." titles on pages with several examples (#5741, #5591) and a docset entry for every macro page.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 14:50:19 +02:00
Niels Lohmann 3173c28dac Fix the tests added by the merged PRs for all CI configurations
- discard the results of dump() and from_*() in CHECK_THROWS with
  utils::ignore_return_value (GCC -Werror=unused-result)
- give unit-bson's huge_string_t a default constructor (MSVC C2512,
  GCC 5, clang 3.5)
- unit-disabled_exceptions: use the literals namespace when the global
  UDLs are off (ci_test_noglobaludls; #5700)
- unit-binary_utf8_strict: expect the JSON pointer prefix with
  JSON_DIAGNOSTICS (#5741)
- skip the tests that rely on exceptions under JSON_NOEXCEPTION
  (#5678, #5732)
- clang-tidy and clang -Werror: static test data, CAPTURE(...);,
  const-correctness, use-after-move alias, unused conversion operator,
  a missing <iterator> include

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 14:50:18 +02:00
Niels Lohmann e8239afff1 Split unit-conversions.cpp so MinGW can link it
clang 18 with the MinGW linker failed to link test-conversions_cpp17
("relocation truncated to fit: IMAGE_REL_AMD64_REL32"). As windows.yml
recommends, keep the objects small by splitting the test file.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 14:49:52 +02:00
Niels Lohmann fd0261d909 Fix the library warnings and noexcept specifications from the merged PRs
- binary_reader: rename the error_handler constructor parameter, which
  shadowed the member (-Wshadow, -Wshadow-field-in-constructor; #5746)
- basic_json(copy_construct_tag, ...): declare it noexcept when copying
  the base class is (GCC 16 -Wnoexcept; #5690)
- the scalar-on-left legacy comparison operators: noexcept only when
  converting the scalar is, like their member counterparts (#5682, #5751)
- compare_leaves: use std::is_eq/is_lt/is_gt instead of comparing a
  std::partial_ordering with 0 (-Wzero-as-null-pointer-constant; #5686)
- serializer: silence MSVC C4127 for the EnsureAscii template parameter
  (#5741, #5746)
- clang-tidy: return the sanitized reference in binary_writer, take the
  key of ordered_map::find_impl by const reference (#5727), and mark the
  switches over parse_array_index (#5728)
- ordered_map: keep <memory> for std::allocator (IWYU)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 14:49:51 +02:00
Niels Lohmann 0c4462676d Document options to reduce compile times (#5611)
* Document options to reduce compile times

Add an integration page that collects the ways to reduce compile times
with measurements: json_fwd.hpp in headers, JSON_NO_AUTOMATIC_UDLS,
explicit instantiation with extern template, modules, and precompiled
headers, and notes that JSON_NO_IO and JSON_USE_GLOBAL_UDLS have no
measurable effect.

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

* Include only json_literals.hpp in the JSON_NO_AUTOMATIC_UDLS examples

json_literals.hpp includes json.hpp itself, so including both is redundant.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 12:23:53 +02:00
Niels Lohmann fa2465b325 Fix the remaining CI failures on develop
- unit-wstring: with a 16-bit wchar_t (Windows), a lone surrogate is
  reported as the ill-formed byte 0xFF since #5704; the std::wstring
  expectations still had the previous <U+0000>.
- ci_single_binaries: json_literals.hpp (#5610) and json.hpp include each
  other on purpose, and IWYU, not following the cycle, asks to replace
  json.hpp with json_fwd.hpp. Report its findings without failing the
  build, as already done for json.hpp.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 12:23:10 +02:00
Niels Lohmann ee7c0ce71c Merge remote-tracking branch 'origin/develop' into fix/develop-ci-after-merges 2026-10-04 12:17:08 +02:00
Niels Lohmann 73e9eae3c1 Add an error_handler parameter for UTF-8 to the binary readers and writers (#5746)
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 12:13:49 +02:00
Niels Lohmann f56b418c56 Follow each binary format's UTF-8 rule: strict writers (CBOR/UBJSON/BJData/BSON), lenient readers (#5741)
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 12:13:48 +02:00
Niels Lohmann 2b29ca1812 Keep the serializer conversion for objects whose keys cannot be converted
#5591 added a test converting nlohmann::json into a basic_json whose
string type cannot be constructed from std::string. That instantiates
convert_iteratively(), whose members.emplace_back(next.key(), ...) needs
exactly that key conversion, and broke the build of unit-alt-string.
Dispatch on the key's constructibility and leave such conversions to the
serializers, as the levels above the nesting bound already do (#3425).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 12:04:35 +02:00
Niels LohmannandRaphael Grimm 40021f38fb Add basic_json::as_base_class and document name conflicts with custom base classes (#5589)
* Add basic_json::as_base_class and document name conflicts with custom base classes

Members of basic_json hide members of a custom base class with the same
name, and future releases may add members that hide ones accessible
today. Document this in json_base_class_t and add as_base_class() to
reach hidden members without spelling out the cast.

Also make json_base_class_t a public member type. It was documented
since 3.12.0, but declared private, so users could not name it.

Supersedes #3899.

Co-authored-by: Raphael Grimm <1005058+barcode@users.noreply.github.com>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Add as_base_class to the docset search index

New public members get an entry in docs/docset/docSet.sql (as done for
to_bon8/from_bon8 in #2998). Without it, the Dash/Zeal docset built from
the documentation cannot find basic_json::as_base_class.

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

* Silence clang-tidy for the hidden type_name() in the base class test

ci_clang_tidy failed with readability-convert-member-functions-to-static
on base_class_with_hidden_members::type_name(). It must stay a
non-static member: the test shows that it is hidden by the non-static
basic_json::type_name() and reachable through as_base_class().

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Raphael Grimm <1005058+barcode@users.noreply.github.com>
2026-10-04 12:00:31 +02:00
Niels Lohmann 1a77948c25 Document the macros that preview version 4.0 in the roadmap (#5593)
* Document the macros that preview version 4.0 in the roadmap

List the macros that guard breaking changes planned to become the
default in version 4.0.0, and explain that 4.0.0 will be the sum of
these opt-in flags, which can be tried on the 3.x release train.

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

* List the deprecated functions removed in 4.0 in the roadmap

The roadmap lists what will be removed; the migration guide keeps the
examples for how to replace each item. Also mention the deprecated
(ptr, len) overloads of the from_* functions in the migration guide.

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

* Wrap overlong line in cbor_tag_handler_t documentation

The line added in #5559 exceeds the 160-character limit enforced by the
documentation style check.

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

* Add JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON to the 4.0 example

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

* Add JSON_STRICT_BINARY_UTF8 to the 4.0 roadmap

The macro comes from #5741: the CBOR, UBJSON, BJData, and BSON writers keep writing ill-formed UTF-8 unchanged in 3.x and are planned to check it by default in 4.0.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:59:34 +02:00
Niels Lohmann c5650eaa3c Reject malformed UTF-16/UTF-32 units in wide-string input (#5704)
The wide-string input adapter (used for std::u16string, std::u32string,
std::wstring, and iterators over 2- or 4-byte character types) passed
some malformed code units on to the lexer as values that are neither a
byte (0x00..0xFF) nor char_traits<char>::eof(). As a result:

- A lone UTF-16 surrogate inside true/false/null was accepted if its low
  byte matched the expected letter, or ended the input silently if it
  was the last unit.
- A high surrogate followed by a unit that is not its low surrogate
  swallowed that unit; if the swallowed unit was the newline ending a
  // comment, the comment silently extended over the next line.
- Where wint_t is a signed int (macOS, the BSDs), a negative wchar_t
  collided with char_traits<char>::eof() (ending the input early) or was
  truncated to its low byte, depending on its value.

The UTF-32 helper now converts the code unit to std::uint32_t before the
range checks, so a negative unit reaches the same "emit 0xFF" branch
already used for code points above U+10FFFF. The UTF-16 helper now
peeks at the next unit before consuming it, and emits 0xFF instead of
the raw surrogate when no valid pair is found, matching how ill-formed
UTF-8 bytes are rejected elsewhere in the lexer.

Fixes #5645.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:59:03 +02:00
Niels Lohmann a0b71e2720 Deduplicate basic_json internals; make insert(pos, json&&) move (#5727)
* Remove unused private aliases from basic_json

The private aliases primitive_iterator_t, internal_iterator and
output_adapter_t are not used anywhere: iter_impl, binary_writer and
the tests refer to the detail:: names directly. As the aliases are
private, no user or derived class can depend on them. The
internal_iterator.hpp include stays because iter_impl needs it.

Part of #5724

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

* Fix meta()'s dead, syntactically invalid HP aCC branch

The HP aCC branch of basic_json::meta() was missing a semicolon and
has therefore never compiled; adding only a semicolon would also make
it throw type_error.305, since it assigned a plain string to
result["compiler"] and then indexed into it like the other branches
do into an object. Make the branch consistent with the others by
assigning an object with "family" and "version" keys, narrow the
condition to __HP_aCC (a C compiler cannot build this header-only
library), and fix meta.md, which documented the old (impossible)
plain-string behavior.

Part of #5724

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

* Stop the noexcept null constructor delegating to a throwing one

basic_json(std::nullptr_t) delegated to basic_json(value_t), whose
underlying json_value(value_t) constructor allocates for other types
and can therefore throw, which is why the noexcept had a
NOLINT(bugprone-exception-escape). The delegated-to constructor also
called assert_invariant() a second time. The default member
initializers of data already produce the same null state (a
value-initialized, i.e. zeroed, union with object == nullptr), so the
delegation and its NOLINT can simply be dropped.

Part of #5724

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

* Remove four cppcheck accessForwarded suppressions in the move constructor

basic_json(basic_json&&) built its base subobject with
std::forward<json_base_class_t>(other), so cppcheck saw the whole of
other as forwarded and flagged every subsequent access to it as
accessForwarded, three of them still marked "TODO check". Only the
base subobject is actually moved from; cast explicitly to the base
type instead, the way ordered_map already does, so cppcheck can tell
the two are unrelated. Behavior is unchanged: for a non-reference T,
std::forward<T>(x) is defined as static_cast<T&&>(x).

Part of #5724

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

* Remove stale cppcheck suppressions and name the local parser in parse()

Running the pinned cppcheck (ci_cppcheck's invocation) without
--inline-suppr across all configurations reports no syntaxError, no
ignoredReturnValue and no assertWithSideEffect, so the corresponding
suppressions in json_fwd.hpp, string_concat.hpp and
assert_invariant() no longer match anything (json_fwd.hpp's is kept,
since downstream users who run an older cppcheck against it could
still hit the warning it once silenced).

The three basic_json::parse() overloads still trigger a false-positive
accessMoved/accessForwarded because they build a temporary parser and
call .parse() on it in the same expression; giving that parser a name
makes the warning go away without changing behavior, and removes the
last of the inline suppressions on these functions.

Part of #5724

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

* Deduplicate the string/binary cleanup in the two erase() overloads

erase(pos) and erase(first, last) each carried a byte-identical
14-line block that destroys and deallocates a string or binary
value before resetting the type to null. That reimplements the
string/binary cases of json_value::destroy(), so any future change to
how those values are freed would have to be made in three places
instead of one. Both overloads now just call destroy() and reset the
union; for the other primitive types (boolean, numbers) destroy() is
a no-op, so behavior is unchanged.

Also fix erase(first, last)'s error-path branch hint, which used
JSON_HEDLEY_LIKELY where erase(pos), the iterator-range constructor,
and every other error path in the class use JSON_HEDLEY_UNLIKELY.
This only affects code layout, not semantics.

Part of #5724

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

* Fix stale and copy-pasted comments in basic_json

Several comments no longer match the code: the class invariant and
assert_invariant()'s doc still named the members m_value/m_type
(now m_data.m_value/m_data.m_type) and did not mention the binary
invariant that assert_invariant() already checks; the json_value note
and the get<PointerType>() @tparam list omitted binary_t even though
binary is a variable-length, pointer-stored type like the others; the
key-based value() overload's brief said "via JSON Pointer", which is
the other overload; and swap(binary_t&)/swap(binary_t::container_type&)
both carried "swap only works for strings", copied from swap(string_t&).

Comment-only change; behavior, the public API and the ABI are
unchanged. The private get_impl() doxygen and the emplace() comments
that border #5585's hunk are intentionally left alone.

Part of #5724

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

* Deduplicate the 16 copied from_cbor/msgpack/ubjson/bjdata/bon8/bson bodies

Each of the 16 binary deserialization overloads (from_cbor,
from_msgpack, from_ubjson, from_bjdata, from_bon8, from_bson, each in
an InputType&& and an iterator/sentinel version, plus the deprecated
span overloads of from_cbor/from_msgpack/from_ubjson/from_bson) had
the same body, differing only in the input_format_t value. Every copy
built a temporary binary_reader from std::move(ia) and called
sax_parse on it in the same expression, which also produced a
false-positive cppcheck accessMoved on all 16 lines and needed a
NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) on
the four span overloads.

Add a private from_binary_impl() helper that builds the reader as a
named local instead, and make each of the 16 overloads a one-line
forward to it. All public signatures, default arguments,
JSON_HEDLEY_WARN_UNUSED_RESULT and JSON_HEDLEY_DEPRECATED_FOR
attributes are unchanged, tag_handler keeps defaulting to
cbor_tag_handler_t::error for the non-CBOR formats (matching
binary_reader::sax_parse's own default), and the helper is placed in
the existing private section before the binary section banner rather
than between the from_* overloads, so from_binary_impl() itself does
not collide with #5688's insertion point. Collapsing the
from_bjdata/from_bon8 bodies into one-line forwards does rewrite the
"return result; }" context lines that #5688 inserts its two
deprecated overloads after, so that PR will need a small manual
rebase (reinserting its overloads after the new one-line bodies)
rather than applying cleanly.

Part of #5724

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

* Make insert(pos, basic_json&&) move its argument instead of copying it

insert(const_iterator pos, basic_json&& val) delegated to
insert(pos, val), but val is a named rvalue reference, so inside the
function it is an lvalue: the call always resolved to
insert(const_iterator, const basic_json&) and deep-copied the value.
This has been the case since the overload was introduced, in every
release. push_back(basic_json&&), by contrast, already moves.

Give the rvalue overload its own body with the same two checks
(type_error.309, invalid_iterator.202), then move the argument into a
local before inserting it. Moving into a local first, rather than
inserting std::move(val) directly, keeps this safe even when val
aliases an element of the same array (e.g.
arr.insert(arr.begin(), std::move(arr[1]))), since
std::vector::insert(pos, T&&) is not guaranteed to handle an argument
that aliases one of its own elements.

This is a deliberate, small behavior change: the moved-from argument
now ends up null afterwards, the same as after push_back(&&), instead
of keeping its old value unchanged. No signature changes, so the
public API and ABI are unaffected. Add unit-modifiers coverage for
the moved-from state and for self-aliasing insertion, both with and
without reallocation of the underlying array.

Part of #5724

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

* Deduplicate object key lookup and checked at() access

at()/find()/count()/contains()/const operator[]/erase_internal() each
repeated the raw object lookup (m_value.object->find(key)), and the
six at() overloads additionally repeated the type_error.304 check and
out_of_range.401/403 throw. Route them all through two new private
helpers, object_lookup()/object_at() (plus array_at() for the index
overloads of at()), templated on the constness of the receiver so one
body serves both the const and non-const overload. count() is left
untouched, since it already goes through object_t::count() rather
than a second find().

The at(KeyType&&) overloads used to forward the same key twice: once
into object->find() and again, on the not-found path, into the
string_t() conversion for the exception message. object_at() now
forwards it only into the lookup and reuses the (unmoved) key for the
message. clang-tidy 22 (Docker silkeh/clang:22) still flags that reuse
under bugprone-use-after-move/hicpp-invalid-access-moved even with the
single forward, since it cannot see that object_t::find() (a plain
std::map or ordered_map) never actually moves from its argument; add a
NOLINTNEXTLINE with that reasoning rather than avoid the pattern.

No signature, exception id/message, or set_parent() behavior changes.

Overlaps #5689, #5705, #5606, #5687 and #5585, which touch the same
hunks; whichever of this commit and those PRs lands second will need
a small rebase.

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

* Deduplicate the lookup/default/throw body of value()

The six non-deprecated value() overloads each held a full copy of the
same body: the four key-based overloads looked up the key and either
returned the found element converted to the requested type or the
default value (throwing type_error.306 if this is not an object), and
the two json_pointer overloads did the same via
ptr.get_checked_or_null(), throwing type_error.306 unless
is_structured(). Replace the duplicated bodies with two private
helpers, value_member() and value_pointee(), that return a
const basic_json* (null when not found) and do the type check/throw
once each. Every value() overload now just picks between the found
pointer's get<T>() and the default.

Same signatures, template parameters, SFINAE conditions, exception id,
message and this context on every overload.

Overlaps #5689 (routes find() through lookup_key()) and #5705 (adds a
deleted integral-key value() next to these overloads); whichever of
this commit and those PRs lands second will need a small rebase.

#5724 item 4

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

* Deduplicate the null-to-container conversion into convert_null_to()

Nine sites wrote out the same "turn a null value into an empty array
or object" logic with two different idioms: operator[](size_type),
operator[](key_type), operator[](KeyType&&) and update() set m_type
then assigned m_value.array/object directly via create<T>(), while the
three push_back() overloads, emplace_back() and emplace() set m_type
then assigned m_value = value_t::array/object (going through
json_value's converting constructor and a temporary). Both idioms end
up calling create<T>() and produce the same state, just via a
different path; both also share a latent exception-safety bug, since
m_type is written before the (possibly throwing) allocation, so a
throwing allocator leaves m_type == array/object with a null pointer
behind it, violating the class invariant and crashing on the next
access to, or destruction of, the value.

Add a private convert_null_to(value_t) helper and call it from all
nine sites. Unlike the idioms it replaces, it allocates the container
first and only then writes m_type, so a throwing allocation leaves the
value as a valid null instead of a mistyped, half-constructed one;
verified with a throwing allocator (see unit-allocator.cpp's
bad_allocator) that j["x"] = ... on a null j now stays null, and no
longer trips assert_invariant()/crashes, when create<object_t>()
throws. Same allocator usage and assert_invariant() call as before,
otherwise.

Overlaps #5585, which reorders these same nine blocks for exception
safety; whichever of this commit and that PR lands second will need a
small rebase.

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

* Deduplicate the linear key search in ordered_map

emplace, at, erase(key), count and find each repeated the same
"for (auto it = begin(); it != end(); ++it) if (m_compare(it->first,
key)) ..." loop (15 copies across their key_type and transparent
KeyType&& overloads), and both erase(key) overloads additionally
repeated the exception-sensitive in-place reconstruction (destroy,
placement-new, pop_back) used to remove an element while keeping the
const Key non-movable.

Add two private helpers: find_impl(Self&, KeyType&&), a static member
template that runs the search once for either constness of the
receiver, and erase_at(iterator), which keeps the existing
pop_back-based reconstruction instead of switching to erase()/resize()
(which would add a DefaultInsertable requirement). Route find, at,
count, emplace, insert(const value_type&) and both erase(key)
overloads through them.

Same signatures, is_usable_as_key_type constraints and exception
messages/types.

Overlaps #5609 and #5685, which both rewrite emplace (#5609 also
touches insert and adds private members at the end of the class);
whichever of this commit and those PRs lands second will need a
rebase.

#5724 item 6

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

* Re-enable bugprone-use-after-move/hicpp-invalid-access-moved

These two checks (and portability-template-virtual-member-function)
were disabled in #4489 (November 2024) "only removed to get the CI
going". portability-template-virtual-member-function is a separate,
still-open cleanup (#5725 item 3 on its own branch) and stays
disabled here; this commit only re-enables the move/forward checks
and cleans up what they flag on this branch.

The move constructor (json.hpp) already casts to the base type
instead of forwarding the whole object (#5724 item 9), so it no
longer trips either check. The at(KeyType&&) double-forward this
check used to flag was reduced to a single forward with the
now-unforwarded reuse annotated by a NOLINTNEXTLINE in #5724 item 3's
object_at() helper (clang-tidy 22 still flags that reuse even after a
single forward; see that commit's message). What is left here:

- from_json_inplace_array_impl(), from_json_tuple_impl_base() and the
  std::pair overload of from_json_tuple_impl() forwarded j into every
  j.at(...) call in a pack expansion or a pair of calls. at() has no
  ref-qualified overloads, so the forward was a no-op; call j.at(...)
  directly.
- container_input_adapter_factory::create() forwards container twice
  on purpose, into begin() and end(), so both see the same value
  category and produce matching iterator types. Annotate it with
  NOLINTNEXTLINE and a comment instead of changing it.
- unit-class_parser.cpp's "move constructor resets the moved-from
  value to npos" test still pointed at the pre-static_cast move
  constructor by line number and mentioned the cppcheck-suppress
  annotation that #5724 item 9 already removed; update the comment.

No behavior change anywhere in include/. Verified with clang-tidy 22.1.8
(Docker silkeh/clang:22, --platform linux/amd64) against a TU including
json.hpp with the repo's .clang-tidy: bugprone-use-after-move and
hicpp-invalid-access-moved report nothing unsuppressed.

Overlaps #5737 (open PR for the rest of #5725 item 3: the
from_json.hpp/input_adapters.hpp cleanup above, and
portability-template-virtual-member-function), which currently keeps
both checks disabled pending this move-constructor change; whichever
of this commit and that PR lands second will need a small rebase of
.clang-tidy.

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

* Make convert_null_to() take the container type as a template argument

Passing array_t or object_t instead of a value_t makes an invalid target
a compile error instead of a runtime assertion, and removes the branch.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:57:41 +02:00
Niels Lohmann 38a2db260c Remove dead metaprogramming and duplicated code in traits and pointers (#5728)
* Remove unused is_sax and is_detected_convertible

detail::is_sax had no user: the parser and the binary reader only use
is_sax_static_asserts, so is_sax was a second, unchecked copy of the
SAX event list. is_sax_static_asserts asserted boolean(bool) twice in
a row, and detail::is_detected_convertible was never used anywhere.

Remove all three and include <cstddef> for size_t instead of <cstdint>.
Only names in nlohmann::detail are removed; behavior, public API and ABI
are unchanged. The diagnostics for an incomplete SAX handler are the
same, apart from the duplicated boolean() message.

Part of #5708

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

* Replace meta/logic.hpp with a disjunction trait

meta/logic.hpp added a second set of type-level boolean helpers
(cxpr_and, cxpr_or, cxpr_not, ...) next to the existing conjunction
and negation in type_traits.hpp. It was used only by one static_assert
in from_json_tuple_impl, two of its templates were never used, and it
was the only header without the license banner and relied on
transitive includes for <type_traits>.

Add the missing disjunction next to conjunction and negation, use the
three in the static_assert, and delete logic.hpp together with its
BUILD.bazel entry. same_sign now uses disjunction as well, which
resolves the 2022 TODO waiting for such a trait.

The static_assert accepts and rejects the same types as before. Only
names in nlohmann::detail change; behavior, public API and ABI are
unchanged.

Part of #5708

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

* Remove unused would_call_std_* from NLOHMANN_CAN_CALL_STD_FUNC_IMPL

Besides detail::result_of_begin/end, which is_range and iterator_t use,
the macro defined a namespace detail2 with a tag type, a catch-all
overload and would_call_std_begin/end, plus would_call_std_begin/end
structs directly in namespace nlohmann. Nothing has used them since
they were added in #3020.

Reduce the macro to its detail part. Without the trailing struct the
';' after the two invocations would be an empty declaration that
-Wextra-semi flags, so drop it. macro_scope.hpp included
meta/detected.hpp only for this macro; all users of detected.hpp
include it (or type_traits.hpp) themselves, so remove the include.

Behavior and ABI are unchanged. The undocumented, untested and unused
names nlohmann::would_call_std_begin, nlohmann::would_call_std_end and
namespace nlohmann::detail2 are no longer declared.

Part of #5708

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

* Simplify is_ordered_map to reuse has_capacity

is_ordered_map re-detected capacity() with a C++03 sizeof/vararg
trick right after has_capacity did the same detection through
is_detected. For ordered_map, the old trick took the address of
std::vector::capacity, which [namespace.std]/6 makes unspecified.
Reuse has_capacity instead, which removes the unspecified-behavior
pointer-to-std-member and two NOLINT suppressions.

Part of #5708

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

* Remove duplicate const overload of json_pointer::get_checked

The const and non-const get_checked() overloads had byte-identical
50-line bodies, differing only in the signature. The remaining
template deduces a const-qualified BasicJsonType for const callers,
so at(), the out_of_range::create() calls and the bounds check all
still work.

Part of #5708

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

* Fix tautological clause in iter_impl's iterator category assertion

The static_assert meant to check the LegacyBidirectionalIterator
named requirement had a first clause comparing
std::bidirectional_iterator_tag to itself, which is always true and
checks nothing; only array_t::iterator was actually being checked,
despite the message claiming object iterators were checked too.
Drop the tautological clause, reword the message to describe what
is actually checked, and note that object_t may use a forward-only
iterator as long as reverse iteration and operator-- are unused.
The check is intentionally not extended to object_t::iterator, since
that would reject object types with forward-only iterators that
compile and work correctly today.

Part of #5708

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

* Fix misplaced and stale comments in JSON_HAS_RANGES and conversions

The JSON_HAS_RANGES feature-detection block had its libc++ comment
sitting above the clang+libstdc++ branch it does not describe,
leaving the libc++ branch uncommented and the clang+libstdc++ branch
without its own rationale. Move each comment to sit under its own
branch, and give the clang+libstdc++ branch (added in issue 5161) its
own one-line reason referencing that issue instead of reusing the
libc++ branch's comment. Also fix a duplicated-word typo ("in large
in large cpp files") in from_json.hpp, drop two unanswered 2017
design questions left as comments in type_traits.hpp and
from_json.hpp that no longer reflect open questions, and correct
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's @since tag from 3.12.0 to
3.13.0, the release it was actually introduced in.

Part of #5708

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

* Support any-rank C arrays in from_json, not just rank 1-4

from_json() for C arrays had four hand-unrolled overloads (rank 1-4,
added incrementally in #4262), each with its own nested loops. to_json()
already handles any rank recursively, so a rank-5+ C array could be
serialized but not read back with get_to()/get<>().

Replace the four overloads with one from_json() SFINAE-constrained on
get<remove_all_extents<T>::type>() existing, forwarding to a pair of
mutually recursive from_json_c_array_element() helpers: one assigns a
non-array element via get<T>(), the other loops over a array element and
recurses one dimension at a time. Each dimension still goes through at(),
so type_error.304/out_of_range.401 stay unchanged; ranks 1-4 keep their
existing behavior and semantics.

Adds rank-5 round-trip and mismatched-shape tests to unit-conversions.cpp.

Public API: additive only (rank 5+ C arrays become readable).

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

#5708 item 1

* Move templated_json_throw into nlohmann::detail

templated_json_throw() was defined in macro_scope.hpp, which is included
outside NLOHMANN_JSON_NAMESPACE_BEGIN, so the helper leaked into the
global namespace as ::templated_json_throw with no ABI tag. Unqualified
lookup in NLOHMANN_JSON_SERIALIZE_ENUM_STRICT could then bind to a
same-named function declared in the user's own namespace instead, which
fails to compile with Clang ("does not name a template").

Move the helper next to the exception classes in exceptions.hpp, inside
nlohmann::detail, and call it qualified as
::nlohmann::detail::templated_json_throw<...>(...) from both macro
expansion sites. Rewrite the doc comment to give the real reason for the
helper (JSON_THROW may expand to code that discards its argument, e.g.
when exceptions are disabled) and fix the "supress" typo.

templated_json_throw was never released (added by #5151 after v3.12.0),
so it can be moved freely.

Adds a regression test that expands NLOHMANN_JSON_SERIALIZE_ENUM_STRICT
inside a namespace declaring its own templated_json_throw.

Public API: no change (::templated_json_throw was an unreleased,
unintentional global-namespace leak with no callers relying on its
location).

Overlaps #5698, which rewrites the same two macro call lines; the
overlapping hunks are small and should be trivial to reconcile on
rebase.

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

#5708 item 2

* Factor the repeated JSON_HAS_RANGES/MinGW guard into one macro

The std::ranges view conversion (excluded on MinGW because of its
incomplete C++20 ranges support, #4916) was gated by the same
#if JSON_HAS_RANGES && !defined(__MINGW32__) condition at seven
independent sites in to_json.hpp and type_traits.hpp, with the MinGW
rationale duplicated in two of them and missing from the rest. Since the
sites come in matching pairs (one enables is_compatible_range_view and a
view-based overload, the other adds the exclusion to the
plain-array-type overload), a drift between any pair would produce an
ambiguous or missing overload on exactly one platform.

Add JSON_HAS_RANGE_VIEW_CONVERSION next to JSON_HAS_RANGES in
macro_scope.hpp, combining both conditions with the #4916 reasoning in
one place, #undef it in macro_unscope.hpp, and use it at all seven
sites. This does not fold the MinGW check into JSON_HAS_RANGES itself:
JSON_HAS_RANGES is user-overridable and also gates the
enable_borrowed_range specialization in iteration_proxy.hpp, which is
not excluded on MinGW.

No behavior or public API change: JSON_HAS_RANGE_VIEW_CONVERSION expands
to exactly the condition that was previously written out at each site.

Overlaps #5585, #5600 and #3575, which touch the same to_json.hpp and
type_traits.hpp lines; the change here is a mechanical
search-and-replace of the guard condition and should rebase cleanly.

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

#5708 item 11

* De-duplicate from_json.hpp's map and array-fallback bodies

Several from_json() overload pairs in from_json.hpp were copies of each
other, so a fix has to be applied twice (as #5681 already does):

- from_json(..., std::map&) and from_json(..., std::unordered_map&) for
  non-string keys had identical 16-line bodies: array check, m.clear(),
  pair check loop, m.emplace(...). Route both through a new
  from_json_pair_array_to_map(j, m) helper.
- The from_json_array_impl priority_tag<1> and priority_tag<0> fallbacks
  ran the same std::transform/std::inserter loop, differing only in
  ret.reserve(j.size()). Merge them into one body and, modeled on the
  existing from_json_object_reserve, add a from_json_array_reserve pair
  so the reserve() call is only made for ConstructibleArrayType that
  support it.

Error ids (type_error.302), messages, diagnostic paths ((at(0)/at(1))
and behavior for types with/without reserve() are unchanged; only the
duplication is removed.

Public API: no change.

Overlaps #5681, which changes the "&j" to "&p" line in both map bodies;
the shared helper here should make that a one-line change instead of two
on rebase.

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

#5708 item 5

* Unify json_pointer's three array-index parsers

array_index(), contains() and get_checked_or_null() each re-implemented
the RFC 6901 array-index rules and the size_type range check: array_index()
does the canonical parse and throws; contains() (which must not throw,
#5395) re-validates every digit by hand and runs its own strtoull/ERANGE
check before calling array_index() anyway, parsing every array token
twice; get_checked_or_null() wraps array_index() in JSON_TRY/
JSON_INTERNAL_CATCH (detail::out_of_range&) to turn an unrepresentable
index into "not found".

Add a single private, noexcept parse_array_index(s, idx) returning an
array_index_status (ok / leading_zero / not_a_number / unresolved /
exceeds_size_type). array_index() becomes a thin wrapper mapping each
status to the existing parse_error.106/109 or out_of_range.404/410;
contains() and get_checked_or_null() switch on the status directly. This
removes contains()'s digit-validation loop and its second strtoull call,
and get_checked_or_null()'s JSON_TRY/JSON_INTERNAL_CATCH.

Bugfix as a consequence: get_checked_or_null()'s JSON_TRY/
JSON_INTERNAL_CATCH was dead code under JSON_NOEXCEPTION (JSON_TRY
expands to "if(true)" and the catch to "if(false)", so JSON_THROW's
std::abort() ran unconditionally), meaning value() and contains() would
abort instead of returning the default/false for an out-of-range-sized
or oversized array index when exceptions are disabled (#5672). Switching
on parse_array_index()'s return value instead of relying on an actual
throw/catch fixes this: get_checked_or_null() now returns nullptr for
array_index_status::unresolved/exceeds_size_type in every build
configuration, and still calls JSON_THROW (aborting under
JSON_NOEXCEPTION, as before) only for a malformed index
(leading_zero/not_a_number), matching its documented @throw list.

All existing error ids, messages and diagnostic paths are unchanged; a
few reference tokens that used to fail contains()'s manual per-character
validation (e.g. "1a") now fail via array_index_status::unresolved
instead, with no observable difference since contains() only returns
bool.

Adds regression tests to unit-element_access2.cpp's "access on array
type" section covering value() with an index that exceeds size_type and
one with a trailing non-digit, both of which must yield the default
value rather than abort/throw.

Public API: no change.

Overlaps #5700, #5614 and #5692, which touch the contains() and
get_checked_or_null() array hunks; this change replaces those hunks with
calls into the new shared parser, so a rebase will need to re-apply
their token-handling changes (e.g. the empty-token case) on top of the
switch statements here.

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

#5708 item 4

* Regenerate single_include after merging develop

The merge commit kept develop's single_include/nlohmann/json.hpp because
make amalgamate saw it as up to date.

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

* Address review: switch in array_index, drop redundant inline

- json_pointer::array_index() dispatches on array_index_status with a
  switch, matching the other parse_array_index() caller
- drop `inline` from the function templates this PR adds or moves in
  from_json.hpp
- reword a comment that described the change rather than the code

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:50:42 +02:00
Niels Lohmann b9850740b9 Add regression test for converting json to std::variant<json> (#5595)
* Add regression test for converting json to std::variant<json> (#5066)

With 3.10.5, get<std::variant<json>>() was well-formed through the string
from_json overload, so the implicit conversion operator was a candidate
when converting json to std::variant<json>, and MSVC picked it over the
variant's converting constructor. The tightened constraints from #3427 and
#3604 (3.11.0) removed that path; this test guards against regressions.

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

* Fix clang-tidy and clang 6 in the #5066 regression test

ci_clang_tidy asked for emplace_back instead of push_back. The
push_back is the point of the test: #5066 is about the implicit
conversion from json to the vector's value type, which emplace_back
would bypass. Silence the check on that line.

clang 5 and 6 cannot instantiate std::variant<json> from libstdc++ 10's
<variant> ("cannot cast private base class"), which broke
ci_test_compilers_clang (6). Tested with the CI images: clang 7 to 11
compile and pass, including clang 11 with libstdc++ 10. Skip the runtime
check for clang before 7; the static_assert still runs.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:48:47 +02:00
Niels Lohmann 1df1e8a845 Make cross-string-type basic_json conversion explicit without implicit conversions (#5591)
* Make cross-string-type basic_json conversion explicit without implicit conversions

The converting constructor from another basic_json specialization was
always implicit, so a value with a different string_t (std::wstring, a
string with a custom allocator, ...) silently converted into a temporary,
e.g. when passed to a function taking const nlohmann::json&. Such
conversions do not produce correct values (#3425), and
JSON_USE_IMPLICIT_CONVERSIONS=0 did not catch them.

When JSON_USE_IMPLICIT_CONVERSIONS is 0, the constructor is now explicit
if the string types differ. Specializations sharing a string type (json
and ordered_json, different serializers or object maps) stay implicitly
convertible, so the NLOHMANN_DEFINE_TYPE_* macros keep working with
nested json members. get<BasicJsonType>() constructs explicitly and
works in both modes.

Fixes #2649.

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

* Construct explicitly in get_to() and to_json(std::optional)

With JSON_USE_IMPLICIT_CONVERSIONS=0 the conversion from a basic_json
with a different string type is now explicit, but two library paths
still assigned such a value implicitly and failed to compile inside the
library:

- get_to() with a basic_json target (the #2175 overload) did
  `v = *this`, so json(42).get_to(alt_json&) broke although
  get<alt_json>() works.
- to_json(BasicJsonType&, const std::optional<T>&) is constrained on
  std::is_constructible (which accepts the explicit constructor) but did
  `j = *opt`, so converting a std::optional<alt_json> into a json broke.

Both now construct the value explicitly, as get_impl() already does.

Also replace static_cast<bool>(JSON_USE_IMPLICIT_CONVERSIONS) with a
comparison: clang-tidy's modernize-use-bool-literals rejected the cast
of the integer literal the macro expands to, failing ci_clang_tidy.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:52 +02:00
Niels Lohmann a212d3b2e4 Preserve the object comparator's state in a deep copy past the nesting bound (#5722)
* Preserve the object comparator's state in a deep copy past the nesting bound

copy_object_level(), used by the copy constructor and copy assignment once a
value is nested deeper than the iterative deep copy's bound (128 levels, or
every copy under JSON_NO_THREAD_LOCAL), built each object's copy with the
object type's plain range constructor. That default-constructs the object's
comparator instead of copying the original's. For an object type whose
comparator carries state, such as a std::map that compares keys
case-sensitively only when constructed that way, the copy then ordered - and
could even deduplicate - its keys differently from the original.

Add detail::is_comparator_constructible_object_type, a detection trait for
object types that provide a key_comp() and a constructor taking a range and a
comparator, the way std::map does. copy_object_level now dispatches on it: an
object type that qualifies gets its copy built with src_object.key_comp()
passed along; other object types, such as nlohmann::ordered_map (which has a
key_compare for its std::map-like interface, but no key_comp()), keep using
the plain range constructor exactly as before.

merge_patch and update() were checked for the same pattern; neither is
affected, since both only ever add members one at a time to an object that
already has its own comparator (or start a brand new default-constructed one),
rather than rebuilding an object_t from a range copied out of an existing,
possibly custom-comparator object.

Fixes #5649.

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

* Keep astyle from padding the create_object_with_comparator templates

Spell the negated condition as detail::negation<...> instead of a leading
'!', which made astyle spread the template header out.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:47 +02:00
Niels Lohmann 1edf0ef041 Fix value(json_pointer, default) aborting under JSON_NOEXCEPTION (#5700)
With exceptions disabled (JSON_NOEXCEPTION or -fno-exceptions),
value(const json_pointer&, default) called std::abort() for array
reference tokens that array_index() rejects with out_of_range.404/410:
indices too large to fit size_type, the empty token ("/"), and tokens
like "/1a". With exceptions enabled, the same tokens correctly yielded
the default value, because get_checked_or_null() relied on
JSON_TRY/JSON_INTERNAL_CATCH (detail::out_of_range&) to turn the
exception into nullptr; under JSON_NOEXCEPTION, JSON_THROW aborts
before that catch is ever reached.

get_checked_or_null() now detects those out-of-range tokens itself,
the same way contains(json_pointer) already does (#5495), and only
calls array_index() for tokens that must still raise parse_error.106
or parse_error.109 (e.g. "/01", "/+1"), matching the documented
behavior of value().

Added regression tests to tests/src/unit-disabled_exceptions.cpp
(built with JSON_NOEXCEPTION and -fno-exceptions) and the matching
checks to tests/src/unit-element_access2.cpp for normal exception
mode.

Fixes #5672.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:43 +02:00
Niels Lohmann 756b28c2b8 Keep a NUL byte ending a // comment as the end of input (#5696)
With the default NUL handling (JSON_STRICT_NUL_HANDLING not set), a NUL
byte in the input is treated as the real end of input everywhere -
except when it immediately ends a `//` comment: scan_comment() matched
'\0' as a comment terminator like '\n', so the NUL was consumed as
part of the comment and scan() never saw it as end of input; the next
get() then kept reading past it. Multi-line comments and
JSON_STRICT_NUL_HANDLING=1 were unaffected, since there the NUL is
just part of the comment text.

Fix scan_comment() to leave the NUL unconsumed (unget()) instead of
returning it as part of the comment, so the following scan() reports
it as end of input, exactly as for a NUL anywhere else.

Fixes #5659.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:39 +02:00
Niels Lohmann 49cd427196 Copy-construct the base class of a deep copy's elements, not assign it (#5690)
The bounded-descent copy added by #5389 built the elements of a deep copy
(nested past the 128-level bound) by default-constructing them and then
having copy_metadata() assign their base class afterwards. That assignment
is only instantiated for values nested past the bound, but being called
from copy_structured() at all meant it was compiled for every copy, so a
CustomBaseClass that is copy-constructible but not move-assignable (for
example one with a const data member) no longer let its basic_json be
copy-constructed, at any depth.

copy_array_level() and copy_object_level() now build each element with a
private-tag-selected constructor that copy-constructs the base class (and,
under JSON_DIAGNOSTIC_POSITIONS, copies the positions) directly, the same
way the copy constructor already builds elements within the 128-level
bound. Copying a basic_json is therefore back to requiring only a
copy-constructible base class, as documented and as it was before #5389;
copy assignment is unchanged and still requires an assignable one.

Fixes #5674.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:36 +02:00
Niels Lohmann b730946432 Fix key types convertible to std::string_view breaking lookups (#5689)
Since #4958, a key type implicitly convertible to std::string_view was
accepted by is_usable_as_basic_json_key_type without checking that the
object's comparator can actually compare object_t::key_type with that
key type. The key was then forwarded unchanged to the underlying map,
so const operator[], at, find, count, contains, erase and value failed
to compile (a hard error inside <map>) for a key convertible only to
std::string_view, and value() rejected such keys outright. For keys
convertible to both std::string and std::string_view, the KeyType&&
templates now won overload resolution over the object_t::key_type
overloads and then failed the same way, a regression from 3.12.0. Only
the non-const operator[] worked, because it uses emplace(), which
constructs a std::string from the key explicitly. ordered_json was not
affected, since ordered_map checks comparability itself.

Add a trait, is_string_view_convertible_key_type, that recognizes a key
type that is convertible to std::string_view but not directly
comparable with the object's key type, provided std::string_view itself
is comparable with it. at(), operator[], find(), count(), contains(),
erase() and value() now route such keys through a new lookup_key()
helper that converts them to std::string_view before they reach the
object, matching how the object's transparent comparator already
supports std::string_view lookups.

Fixes #5663.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:32 +02:00
Niels Lohmann 0d01d6ae90 Classify leaves with operator<=> itself past the nesting bound (#5686)
* Classify leaves with operator<=> itself past the nesting bound

In C++20, an ordered comparison past the nesting bound classified a pair of
leaves by asking == first and then order_leaves(), which calls < and > -
both derived from <=>. For a pair of binary values with the same bytes but a
different subtype, == reports them unequal, while <=> (through
std::vector<std::uint8_t>::operator<=>) reports them equivalent, so the pair
ended the comparison as unordered instead of letting the next element
decide - unlike an array or object within the bound, which compares such a
pair with its own operator<=> and gets equivalent. So operator<=>, and the
<, <=, >, >= derived from it, could give a different result for the same two
values depending on how deeply the values were nested, or unordered at every
depth with JSON_NO_THREAD_LOCAL defined.

compare_leaves() now classifies such a pair in C++20 with operator<=> itself
instead, matching how a value within the bound is compared; the equality-only
and pre-C++20 ordered cases are unchanged. Which of the three runs is chosen
by overloading on std::integral_constant<bool, Ordered>, the same tag
dispatch order_leaves() already uses, rather than a runtime "if (Ordered)" on
a template parameter, which MSVC would flag as a constant condition (C4127).

Added a regression test to unit-comparison.cpp that nests such a pair 0, 127,
128 and 200 levels deep (127 stays within the 128-level bound, 128 and 200
do not) and checks that operator<=> and operator< agree at every depth.

Fixes #5654.

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

* Drop the version history note for a bug that was never released

The regression came from #5390, which is not in any release. Addresses review comment by @gregmarr.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:29 +02:00
Niels Lohmann 6f2048cd5d Accept lvalues in ordered_map::emplace's value parameter (#5685)
* Accept lvalues in ordered_map::emplace's value parameter

ordered_map::emplace(key, value) took the mapped value only by T&&, an
rvalue reference rather than a forwarding reference, so
ordered_json::emplace("a", value) failed to compile whenever value was
an lvalue or a const lvalue, even though the same call compiles for
json (whose object_t is std::map, with a variadic emplace). Turn the
value parameter into a separately-deduced forwarding reference,
constrained with std::is_constructible so the overloads still only
accept something convertible to the mapped type. std::map-compatible
semantics are unchanged: emplace still does nothing if the key already
exists.

Open PR #5609 also touches ordered_map.hpp (moving values on vector
growth); this change only touches the two emplace() overloads and
should not conflict.

Fixes #5673.

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

* Avoid astyle's padding in ordered_map::emplace's template headers

Use detail::conjunction instead of && and drop the redundant V&& in detail::is_constructible, so astyle keeps the usual template formatting. Addresses review comment by @gregmarr.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:25 +02:00
Niels Lohmann df27cc3d4c Add scalar-on-left overloads for legacy discarded comparisons in C++20 (#5682)
With JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 and C++20, a scalar on
the left-hand side of <= or >= (e.g., `1 <= discarded`) yielded false
instead of the documented true. The C++20 legacy block only had member
operators, which are only candidates when the basic_json is the left
operand; for a scalar on the left, overload resolution picked the
candidate rewritten from operator<=>, which does not emulate the legacy
behavior. The C++17 branch already has scalar-on-the-left friend
overloads for <= and >=; add the equivalent pair to the C++20 legacy
block.

Added a regression test to tests/src/unit-comparison.cpp covering all
four operand orders for both operators.

Fixes #5665.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:21 +02:00
Niels Lohmann d11e89471b Assert on missing array indices in const operator[] and document the JSON pointer case (#5606)
The const operator[] overloads are unchecked by design, and a missing key
or index is undefined behavior. The key overload guards this with a
runtime assertion, but the index overload did not, although the element
access documentation says an assertion fires in both cases. The const
JSON pointer overload inherits both through json_pointer::get_unchecked(),
so a pointer to a missing array index read out of bounds even in debug
builds, and its documentation promised out_of_range.404 for any pointer
that cannot be resolved.

Add JSON_ASSERT(idx < size()) to const operator[](size_type), which also
covers the index leg of the const JSON pointer overload. Document the
undefined behavior for the const JSON pointer overload in operator[].md
and in the runtime assertions page. Release builds are unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:18 +02:00
Niels Lohmann 78ddd95794 Document GCC < 11 incomplete-type error with optional members (#3669) (#5596)
* Document GCC < 11 incomplete-type error with optional members (#3669)

With GCC 10 and older in C++11/14 mode, a free to_json() for a type
holding an optional<Dummy> (Dummy constructible from json) fails with
"invalid use of incomplete type detector<...to_json_function...>".
ADL for Dummy finds the unrelated to_json and closes an instantiation
cycle through optional's converting constructor. The same error
reproduces without the library, so it can't be fixed here.

Add a FAQ entry explaining the cause and the hidden-friend workaround,
recommend hidden friends in the arbitrary types docs, and add a
regression test that keeps the workaround compiling on GCC 7-10.

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

* Use the #3669 fixture's optional member in to_json

Issue3669Holder::d is never read, so clang's -Weverything -Werror build
(ci_test_clang) fails with -Wunused-private-field. Reference the member
in the hidden-friend to_json; this does not affect the instantiation
cycle the fixture reproduces.

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 11:46:13 +02:00
Niels Lohmann 6d0867a85f Make comparisons with scalars noexcept only when the conversion is (#5751)
The comparison operators taking a scalar (==, !=, <, <=, >, >=, and
C++20's <=>) convert the scalar to a basic_json and compare, but were
unconditionally noexcept. When that conversion throws, the program
called std::terminate instead of propagating the exception, e.g. when
comparing a json with a string literal under memory pressure
(std::bad_alloc) or with an enum value not mapped by
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT (out_of_range.410). clang-tidy
22.1 reports the latter as bugprone-exception-escape.

Declare the 16 scalar overloads
noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value):
they stay noexcept for numbers, Booleans, nullptr, and plain enums,
and are noexcept(false) for strings and enums whose to_json may throw.
The comparisons of two basic_json values are unchanged.

Restore the strict-enum comparisons removed from unit-conversions.cpp
in the previous PR, check that comparing an unmapped strict enum now
throws, and pin the new exception specifications in unit-noexcept.cpp.
Document the exception safety of overload (2) on all seven operator
pages. Ran make amalgamate.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 17:25:17 +02:00
117 changed files with 10334 additions and 3274 deletions
-9
View File
@@ -1,18 +1,9 @@
# bugprone-use-after-move (hicpp-invalid-access-moved is its alias) still flags
# the basic_json move constructor, which forwards the whole object to its base
# class (#5724), and two forwards in the error-message construction of
# at(KeyType&&) (json.hpp, both overloads: find(std::forward<KeyType>(key))
# followed by string_t(std::forward<KeyType>(key)) in the throw), which #5689
# rewrites. Re-enable both checks once those changes have landed.
# portability-avoid-pragma-once: kept disabled on purpose. #pragma once is accepted # portability-avoid-pragma-once: kept disabled on purpose. #pragma once is accepted
# by every supported compiler, and tools/amalgamate/amalgamate.py strips it from # by every supported compiler, and tools/amalgamate/amalgamate.py strips it from
# single_include, so there is nothing left to fix here. # single_include, so there is nothing left to fix here.
Checks: '*, Checks: '*,
-bugprone-use-after-move,
-hicpp-invalid-access-moved,
-altera-id-dependent-backward-branch, -altera-id-dependent-backward-branch,
-altera-struct-pack-align, -altera-struct-pack-align,
-altera-unroll-loops, -altera-unroll-loops,
+1 -1
View File
@@ -53,11 +53,11 @@ cc_library(
"include/nlohmann/detail/meta/detected.hpp", "include/nlohmann/detail/meta/detected.hpp",
"include/nlohmann/detail/meta/identity_tag.hpp", "include/nlohmann/detail/meta/identity_tag.hpp",
"include/nlohmann/detail/meta/is_sax.hpp", "include/nlohmann/detail/meta/is_sax.hpp",
"include/nlohmann/detail/meta/logic.hpp",
"include/nlohmann/detail/meta/std_fs.hpp", "include/nlohmann/detail/meta/std_fs.hpp",
"include/nlohmann/detail/meta/type_traits.hpp", "include/nlohmann/detail/meta/type_traits.hpp",
"include/nlohmann/detail/meta/void_t.hpp", "include/nlohmann/detail/meta/void_t.hpp",
"include/nlohmann/detail/output/binary_writer.hpp", "include/nlohmann/detail/output/binary_writer.hpp",
"include/nlohmann/detail/output/error_handler.hpp",
"include/nlohmann/detail/output/output_adapters.hpp", "include/nlohmann/detail/output/output_adapters.hpp",
"include/nlohmann/detail/output/serializer.hpp", "include/nlohmann/detail/output/serializer.hpp",
"include/nlohmann/detail/recursion_depth_limit.hpp", "include/nlohmann/detail/recursion_depth_limit.hpp",
+6
View File
@@ -61,6 +61,7 @@ option(JSON_Install "Install CMake targets during install
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON) option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) 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_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)
if (JSON_CI) if (JSON_CI)
include(ci) include(ci)
@@ -118,6 +119,10 @@ if (JSON_StrictNulHandling)
message(STATUS "Strict NUL-byte handling enabled (JSON_STRICT_NUL_HANDLING=1)") message(STATUS "Strict NUL-byte handling enabled (JSON_STRICT_NUL_HANDLING=1)")
endif() endif()
if (JSON_StrictBinaryUTF8)
message(STATUS "Strict UTF-8 checks in binary writers enabled (JSON_STRICT_BINARY_UTF8=1)")
endif()
if (JSON_Diagnostic_Positions) if (JSON_Diagnostic_Positions)
message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)") message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)")
endif() endif()
@@ -153,6 +158,7 @@ target_compile_definitions(
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1> $<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1> $<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
$<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1> $<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1>
$<$<BOOL:${JSON_StrictBinaryUTF8}>:JSON_STRICT_BINARY_UTF8=1>
) )
target_include_directories( target_include_directories(
+8 -4
View File
@@ -596,8 +596,9 @@ foreach(SRC_FILE ${SRC_FILES})
add_executable(single_${RELATIVE_SRC_FILE} EXCLUDE_FROM_ALL ${PROJECT_BINARY_DIR}/src_single/${RELATIVE_SRC_FILE}.cpp) add_executable(single_${RELATIVE_SRC_FILE} EXCLUDE_FROM_ALL ${PROJECT_BINARY_DIR}/src_single/${RELATIVE_SRC_FILE}.cpp)
target_include_directories(single_${RELATIVE_SRC_FILE} PRIVATE ${PROJECT_SOURCE_DIR}/include) target_include_directories(single_${RELATIVE_SRC_FILE} PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11) target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11)
if(RELATIVE_SRC_FILE STREQUAL "json") if(RELATIVE_SRC_FILE STREQUAL "json" OR RELATIVE_SRC_FILE STREQUAL "json_literals")
# see below: report json.hpp's diagnostics without --error, so they do not fail the build # see below: report the diagnostics of json.hpp and json_literals.hpp without --error, so they
# do not fail the build
set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${IWYU_TOOL} -Xiwyu --max_line_length=300) set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${IWYU_TOOL} -Xiwyu --max_line_length=300)
else() else()
set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}")
@@ -611,7 +612,10 @@ foreach(SRC_FILE ${SRC_FILES})
# reporting its diagnostics (informational, via CXX_INCLUDE_WHAT_YOU_USE above) but exclude it # reporting its diagnostics (informational, via CXX_INCLUDE_WHAT_YOU_USE above) but exclude it
# from the hard gate below so a fresh IWYU/compiler combination does not fail this target on a # from the hard gate below so a fresh IWYU/compiler combination does not fail this target on a
# nondeterministic suggestion for a header that already re-exports everything on purpose. # nondeterministic suggestion for a header that already re-exports everything on purpose.
if(NOT RELATIVE_SRC_FILE STREQUAL "json") # json_literals.hpp and json.hpp include each other on purpose (json.hpp includes it at its end
# unless JSON_NO_AUTOMATIC_UDLS is defined), and IWYU, not following the cycle, suggests replacing
# json.hpp with json_fwd.hpp although the literals need the complete basic_json; exclude it, too.
if(NOT RELATIVE_SRC_FILE STREQUAL "json" AND NOT RELATIVE_SRC_FILE STREQUAL "json_literals")
list(APPEND single_binaries_tus src_single/${RELATIVE_SRC_FILE}.cpp) list(APPEND single_binaries_tus src_single/${RELATIVE_SRC_FILE}.cpp)
endif() endif()
endforeach() endforeach()
@@ -701,7 +705,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 # 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 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_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind
JSON_StrictNulHandling) JSON_StrictNulHandling JSON_StrictBinaryUTF8)
set(JSON_CMAKE_FLAGS_3_31_6 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0}) 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}) set(JSON_CMAKE_FLAGS_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
+2
View File
@@ -19,6 +19,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('format_as', 'Function', 'api/
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::as_base_class', 'Method', 'api/basic_json/as_base_class/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::at', 'Method', 'api/basic_json/at/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::at', 'Method', 'api/basic_json/at/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::back', 'Method', 'api/basic_json/back/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::back', 'Method', 'api/basic_json/back/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::basic_json', 'Constructor', 'api/basic_json/basic_json/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::basic_json', 'Constructor', 'api/basic_json/basic_json/index.html');
@@ -241,6 +242,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_THREAD_LOCAL', 'Macro
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_PRECISE_STREAM_POSITION', 'Macro', 'api/macros/json_precise_stream_position/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_PRECISE_STREAM_POSITION', 'Macro', 'api/macros/json_precise_stream_position/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_LIBRARY_VERSION_CHECK', 'Macro', 'api/macros/json_skip_library_version_check/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_LIBRARY_VERSION_CHECK', 'Macro', 'api/macros/json_skip_library_version_check/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_UNSUPPORTED_COMPILER_CHECK', 'Macro', 'api/macros/json_skip_unsupported_compiler_check/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_UNSUPPORTED_COMPILER_CHECK', 'Macro', 'api/macros/json_skip_unsupported_compiler_check/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_BINARY_UTF8', 'Macro', 'api/macros/json_strict_binary_utf8/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_NUL_HANDLING', 'Macro', 'api/macros/json_strict_nul_handling/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_NUL_HANDLING', 'Macro', 'api/macros/json_strict_nul_handling/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_THROW_USER', 'Macro', 'api/macros/json_throw_user/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_THROW_USER', 'Macro', 'api/macros/json_throw_user/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api/macros/json_throw_user/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api/macros/json_throw_user/index.html');
@@ -0,0 +1,53 @@
# <small>nlohmann::basic_json::</small>as_base_class
```cpp
json_base_class_t& as_base_class() noexcept;
const json_base_class_t& as_base_class() const noexcept;
```
Returns a reference to this object as its custom base class [`json_base_class_t`](json_base_class_t.md). No copy is
made.
Since `basic_json` derives from `json_base_class_t`, a member of `basic_json` hides any member of the custom base class
with the same name. This function makes such hidden members accessible again.
## Return value
reference to this object as [`json_base_class_t`](json_base_class_t.md)
## Exception safety
No-throw guarantee: this function never throws exceptions.
## Complexity
Constant.
## Notes
The function is equivalent to `static_cast<json_base_class_t&>(j)` (or `static_cast<const json_base_class_t&>(j)`).
## Examples
??? example
The example shows how to use `as_base_class` to access members of the custom base class that are hidden by members
of `basic_json`.
```cpp
--8<-- "examples/as_base_class.cpp"
```
Output:
```json
--8<-- "examples/as_base_class.output"
```
## See also
- [json_base_class_t](json_base_class_t.md) - type of the custom base class
## Version history
- Added in version 3.13.0.
+3 -1
View File
@@ -238,5 +238,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
1. Added in version 1.0.0. 1. Added in version 1.0.0.
2. Added in version 1.0.0. 2. Added in version 1.0.0.
3. Added in version 3.11.0. 3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as
already supported by [`operator[]`](operator[].md), [`value`](value.md), [`find`](find.md), and other lookup
functions.
4. Added in version 2.0.0. 4. Added in version 2.0.0.
+11 -1
View File
@@ -293,6 +293,15 @@ basic_json(basic_json&& other) noexcept;
When used without parentheses around an empty initializer list, `basic_json()` is called instead of this When used without parentheses around an empty initializer list, `basic_json()` is called instead of this
function, yielding the JSON `#!json null` value. function, yielding the JSON `#!json null` value.
- Overload 4:
!!! info "Implicit conversion"
The conversion is implicit unless [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md)
is defined to `0` and `BasicJsonType::string_t` differs from `string_t`. In that case, the constructor is
`explicit`, so a JSON value with a different string type is no longer silently converted, for example when it is
passed to a function taking `#!cpp const json&`. Write `#!cpp json(other)` or `#!cpp other.get<json>()` instead.
- Overload 7: - Overload 7:
!!! info "Preconditions" !!! info "Preconditions"
@@ -466,7 +475,8 @@ basic_json(basic_json&& other) noexcept;
1. Since version 1.0.0. 1. Since version 1.0.0.
2. Since version 1.0.0. 2. Since version 1.0.0.
3. Since version 2.1.0. 3. Since version 2.1.0.
4. Since version 3.2.0. 4. Since version 3.2.0. Explicit for different string types if `JSON_USE_IMPLICIT_CONVERSIONS` is `0` since
version 3.13.0.
5. Since version 1.0.0. 5. Since version 1.0.0.
6. Since version 1.0.0. 6. Since version 1.0.0.
7. Since version 1.0.0. Fixed in version 3.13.0 to also check the iterator range for binary values; before, a range 7. Since version 1.0.0. Fixed in version 3.13.0 to also check the iterator range for binary values; before, a range
+3 -1
View File
@@ -131,7 +131,9 @@ Logarithmic in the size of the JSON object.
## Version history ## Version history
1. Added in version 3.11.0. 1. Added in version 3.11.0.
2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0. 2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0. Fixed in
version 3.13.0 to consistently accept `std::string_view`-convertible keys, as already supported by
[`operator[]`](operator[].md), [`at`](at.md), [`value`](value.md), and other lookup functions.
3. Added in version 3.7.0. 3. Added in version 3.7.0.
4. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of 4. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of
causing undefined behavior at runtime. causing undefined behavior at runtime.
+3 -1
View File
@@ -84,6 +84,8 @@ Logarithmic in the size of the JSON object.
## Version history ## Version history
1. Added in version 3.11.0. 1. Added in version 3.11.0.
2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0. 2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0. Fixed in version 3.13.0 to
consistently accept `std::string_view`-convertible keys, as already supported by [`operator[]`](operator[].md),
[`at`](at.md), [`value`](value.md), and other lookup functions.
3. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time instead of 3. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time instead of
causing undefined behavior at runtime. causing undefined behavior at runtime.
+6 -3
View File
@@ -25,10 +25,12 @@ and `ensure_ascii` parameters.
result consists of ASCII characters only. result consists of ASCII characters only.
`error_handler` (in) `error_handler` (in)
: how to react on decoding errors; there are three possible values (see [`error_handler_t`](error_handler_t.md): : how to react on decoding errors; there are four possible values (see [`error_handler_t`](error_handler_t.md):
`strict` (throws an exception in case a decoding error occurs; default), `replace` (replace invalid UTF-8 sequences `strict` (throws an exception in case a decoding error occurs; default), `replace` (replace invalid UTF-8 sequences
with U+FFFD), and `ignore` (ignore invalid UTF-8 sequences during serialization; all valid bytes are copied to the with U+FFFD), `ignore` (ignore invalid UTF-8 sequences during serialization; all valid bytes are copied to the
output unchanged, and invalid bytes are dropped)). output unchanged, and invalid bytes are dropped), and `keep` (write the ill-formed bytes to the output as is,
without escaping them, even if `ensure_ascii` is `#!cpp true`; the result is then not valid UTF-8, but equals the
input bytes exactly, and well-formed characters around the ill-formed bytes are still escaped as usual)).
## Return value ## Return value
@@ -94,3 +96,4 @@ Binary values are serialized as an object containing two keys:
- Indentation character `indent_char`, option `ensure_ascii` and exceptions added in version 3.0.0. - Indentation character `indent_char`, option `ensure_ascii` and exceptions added in version 3.0.0.
- Error handlers added in version 3.4.0. - Error handlers added in version 3.4.0.
- Serialization of binary values added in version 3.8.0. - Serialization of binary values added in version 3.8.0.
- Error handler `keep` added in version 3.13.0.
@@ -70,3 +70,5 @@ Logarithmic in the size of the container, O(log(`size()`)).
## Version history ## Version history
- Since version 2.0.8. - Since version 2.0.8.
- Fixed in version 3.13.0: for [`ordered_json`](../ordered_json.md), the value could previously only be passed as an
rvalue; it can now also be passed as an lvalue or a `#!cpp const` lvalue, matching the behavior of `json`.
+3 -1
View File
@@ -213,5 +213,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
1. Added in version 1.0.0. Added support for binary types in version 3.8.0. 1. Added in version 1.0.0. Added support for binary types in version 3.8.0.
2. Added in version 1.0.0. Added support for binary types in version 3.8.0. 2. Added in version 1.0.0. Added support for binary types in version 3.8.0.
3. Added in version 1.0.0. 3. Added in version 1.0.0.
4. Added in version 3.11.0. 4. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as
already supported by [`operator[]`](operator[].md), [`at`](at.md), [`value`](value.md), and other lookup
functions.
5. Added in version 1.0.0. 5. Added in version 1.0.0.
@@ -4,15 +4,31 @@
enum class error_handler_t { enum class error_handler_t {
strict, strict,
replace, replace,
ignore ignore,
keep
}; };
``` ```
This enumeration is used in the [`dump`](dump.md) function to choose how to treat decoding errors while serializing a This enumeration is used to choose how to treat ill-formed UTF-8 in a string value or object key:
`basic_json` value. Three values are differentiated:
- [`dump`](dump.md) uses it while serializing a `basic_json` value to text.
- [`to_cbor`](to_cbor.md), [`to_msgpack`](to_msgpack.md), [`to_ubjson`](to_ubjson.md), [`to_bjdata`](to_bjdata.md),
and [`to_bson`](to_bson.md) use it while serializing a `basic_json` value to that binary format. Their default is
`keep`, as no binary writer checked before this parameter was added. CBOR, UBJSON, BJData, and BSON require valid
UTF-8, so for these four the default is `strict` if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md)
is enabled; MessagePack's specification explicitly allows a string to contain ill-formed UTF-8, so `to_msgpack`
stays at `keep`. `to_bon8` does not take this parameter: BON8 always validates, since UTF-8 lead bytes are
structural to that format.
- [`from_cbor`](from_cbor.md), [`from_msgpack`](from_msgpack.md), [`from_ubjson`](from_ubjson.md),
[`from_bjdata`](from_bjdata.md), and [`from_bson`](from_bson.md) use it while parsing that binary format, to decide
whether to check a string value or object key for well-formed UTF-8 at all; by default (`keep`) they do not, as no
binary reader did before this parameter was added. `from_bon8` does not take this parameter, for the same reason
`to_bon8` does not.
Four values are differentiated:
strict strict
: throw a `type_error` exception in case of invalid UTF-8 : throw a `type_error`/`parse_error` exception in case of invalid UTF-8
replace replace
: replace invalid UTF-8 sequences with U+FFFD (� REPLACEMENT CHARACTER) : replace invalid UTF-8 sequences with U+FFFD (� REPLACEMENT CHARACTER)
@@ -20,6 +36,12 @@ replace
ignore ignore
: ignore invalid UTF-8 sequences; all valid bytes are copied to the output unchanged, and invalid bytes are dropped : ignore invalid UTF-8 sequences; all valid bytes are copied to the output unchanged, and invalid bytes are dropped
keep
: keep invalid UTF-8 sequences unchanged; only meaningful for the binary formats mentioned above, since [`dump`]
(dump.md) itself must produce text, and `keep` there writes the ill-formed bytes to the output as is, so the
result is then not valid UTF-8 (but still equals the input bytes exactly, including around any well-formed
characters, which are still escaped as usual)
## Examples ## Examples
??? example ??? example
@@ -45,3 +67,5 @@ ignore
## Version history ## Version history
- Added in version 3.4.0. - Added in version 3.4.0.
- Added `keep`, and made this enumeration apply to the binary readers and writers in addition to `dump`, in version
3.13.0.
+3 -1
View File
@@ -88,6 +88,8 @@ Logarithmic in the size of the JSON object.
## Version history ## Version history
1. Added in version 3.11.0. 1. Added in version 3.11.0.
2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0. 2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0. Fixed in version 3.13.0 to
consistently accept `std::string_view`-convertible keys, as already supported by [`operator[]`](operator[].md),
[`at`](at.md), [`value`](value.md), and other lookup functions.
3. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of 3. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of
causing undefined behavior at runtime. causing undefined behavior at runtime.
+12 -3
View File
@@ -5,12 +5,14 @@
template<typename InputType> template<typename InputType>
static basic_json from_bjdata(InputType&& i, static basic_json from_bjdata(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_bjdata(IteratorType first, SentinelType last, static basic_json from_bjdata(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format. Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format.
@@ -58,6 +60,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`allow_exceptions` (in) `allow_exceptions` (in)
: whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) : whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default)
`error_handler` (in)
: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
BJData does not require a decoder to reject ill-formed UTF-8, so checking is opt-in: the default, `keep`, does not
check at all, as every binary reader did before this parameter was added; `strict` checks and throws;
`replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would
## Return value ## Return value
deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be
@@ -73,7 +81,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
the end of the file was not reached when `strict` was set to true the end of the file was not reached when `strict` was set to true
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed
successfully successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict`
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container - Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container
or n-dimensional array cannot be represented by `std::size_t` or n-dimensional array cannot be represented by `std::size_t`
@@ -111,3 +119,4 @@ Linear in the size of the input.
- Added in version 3.11.0. - Added in version 3.11.0.
- 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 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. - 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.
+13 -2
View File
@@ -5,12 +5,14 @@
template<typename InputType> template<typename InputType>
static basic_json from_bson(InputType&& i, static basic_json from_bson(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_bson(IteratorType first, SentinelType last, static basic_json from_bson(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format. Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format.
@@ -58,6 +60,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`allow_exceptions` (in) `allow_exceptions` (in)
: whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) : whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default)
`error_handler` (in)
: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
BSON does not require a decoder to reject ill-formed UTF-8, so checking is opt-in: the default, `keep`, does not
check at all, as every binary reader did before this parameter was added; `strict` checks and throws;
`replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would
## Return value ## Return value
deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be
@@ -75,6 +83,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
invalid string or byte array length) invalid string or byte array length)
- Throws [`parse_error.114`](../../home/exceptions.md#jsonexceptionparse_error114) if an unsupported BSON record type is - Throws [`parse_error.114`](../../home/exceptions.md#jsonexceptionparse_error114) if an unsupported BSON record type is
encountered encountered
- Throws [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) if a string value or object key is
not valid UTF-8 and `error_handler` is `strict`
## Complexity ## Complexity
@@ -111,6 +121,7 @@ Linear in the size of the input.
- Added in version 3.4.0. - Added in version 3.4.0.
- 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 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. - 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" !!! warning "Deprecation"
+14 -4
View File
@@ -6,14 +6,16 @@ template<typename InputType>
static basic_json from_cbor(InputType&& i, static basic_json from_cbor(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_cbor(IteratorType first, SentinelType last, static basic_json from_cbor(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format. Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format.
@@ -65,6 +67,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: how to treat CBOR tags (optional, `error` by default); see [`cbor_tag_handler_t`](cbor_tag_handler_t.md) for more : how to treat CBOR tags (optional, `error` by default); see [`cbor_tag_handler_t`](cbor_tag_handler_t.md) for more
information information
`error_handler` (in)
: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
CBOR does not require a decoder to reject ill-formed UTF-8, so checking is opt-in: the default, `keep`, does not
check at all, as every binary reader did before this parameter was added; `strict` checks and throws;
`replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would
## Return value ## Return value
deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be
@@ -80,8 +88,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
the end of the file was not reached when `strict` was set to true the end of the file was not reached when `strict` was set to true
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
used in the given input or if the input is not valid CBOR used in the given input or if the input is not valid CBOR
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of
types are not supported, as JSON object keys are always strings) or a string is malformed other types are not supported, as JSON object keys are always strings), or if a string value or object key is not
valid UTF-8 and `error_handler` is `strict`
## Complexity ## Complexity
@@ -121,6 +130,7 @@ Linear in the size of the input.
- Added `tag_handler` parameter in version 3.9.0. - Added `tag_handler` parameter in version 3.9.0.
- 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 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. - 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" !!! warning "Deprecation"
@@ -5,12 +5,14 @@
template<typename InputType> template<typename InputType>
static basic_json from_msgpack(InputType&& i, static basic_json from_msgpack(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_msgpack(IteratorType first, SentinelType last, static basic_json from_msgpack(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Deserializes a given input to a JSON value using the MessagePack serialization format. Deserializes a given input to a JSON value using the MessagePack serialization format.
@@ -58,6 +60,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`allow_exceptions` (in) `allow_exceptions` (in)
: whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) : whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default)
`error_handler` (in)
: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
MessagePack's specification explicitly allows ill-formed UTF-8, so checking is opt-in: the default, `keep`, does
not check at all, as every binary reader did before this parameter was added; `strict` checks and throws;
`replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would
## Return value ## Return value
deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be
@@ -73,8 +81,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
the end of the file was not reached when `strict` was set to true the end of the file was not reached when `strict` was set to true
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from
MessagePack were used in the given input or if the input is not valid MessagePack MessagePack were used in the given input or if the input is not valid MessagePack
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of
types are not supported, as JSON object keys are always strings) or a string is malformed other types are not supported, as JSON object keys are always strings), or if a string value or object key is not
valid UTF-8 and `error_handler` is `strict`
## Complexity ## Complexity
@@ -113,6 +122,7 @@ Linear in the size of the input.
- Added `allow_exceptions` parameter in version 3.2.0. - Added `allow_exceptions` parameter in version 3.2.0.
- 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 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. - 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" !!! warning "Deprecation"
+12 -3
View File
@@ -5,12 +5,14 @@
template<typename InputType> template<typename InputType>
static basic_json from_ubjson(InputType&& i, static basic_json from_ubjson(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType, typename SentinelType = IteratorType>
static basic_json from_ubjson(IteratorType first, SentinelType last, static basic_json from_ubjson(IteratorType first, SentinelType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format. Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format.
@@ -58,6 +60,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`allow_exceptions` (in) `allow_exceptions` (in)
: whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) : whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default)
`error_handler` (in)
: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
UBJSON does not require a decoder to reject ill-formed UTF-8, so checking is opt-in: the default, `keep`, does not
check at all, as every binary reader did before this parameter was added; `strict` checks and throws;
`replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would
## Return value ## Return value
deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be
@@ -73,7 +81,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
the end of the file was not reached when `strict` was set to true the end of the file was not reached when `strict` was set to true
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed
successfully successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict`
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container - Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container
or n-dimensional array cannot be represented by `std::size_t` or n-dimensional array cannot be represented by `std::size_t`
@@ -112,6 +120,7 @@ Linear in the size of the input.
- Added `allow_exceptions` parameter in version 3.2.0. - Added `allow_exceptions` parameter in version 3.2.0.
- 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 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. - 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" !!! warning "Deprecation"
+1
View File
@@ -200,6 +200,7 @@ Direct access to the stored value of a JSON value.
- [**get_ref**](get_ref.md) - get a reference value - [**get_ref**](get_ref.md) - get a reference value
- [**operator ValueType**](operator_ValueType.md) - get a value - [**operator ValueType**](operator_ValueType.md) - get a value
- [**get_binary**](get_binary.md) - get a binary value - [**get_binary**](get_binary.md) - get a binary value
- [**as_base_class**](as_base_class.md) - access the custom base class
### Element access ### Element access
@@ -27,6 +27,18 @@ A `CustomBaseClass` with non-static data members forfeits `basic_json`'s
[standard layout](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType) guarantee. See [standard layout](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType) guarantee. See
[Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass). [Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass).
#### Name conflicts
Since `basic_json` derives from `CustomBaseClass`, members of `basic_json` hide members of `CustomBaseClass` with the
same name. Hidden members remain accessible via [`as_base_class`](as_base_class.md) or by casting the value to
`json_base_class_t`.
!!! warning "Avoid generic member names"
Future versions of the library may add members to `basic_json` that hide members of `CustomBaseClass` that are
accessible today. To reduce the risk of such conflicts, avoid generic names for the members of `CustomBaseClass`,
for instance by using a distinctive prefix.
## Examples ## Examples
??? example ??? example
@@ -45,8 +57,10 @@ A `CustomBaseClass` with non-static data members forfeits `basic_json`'s
## See also ## See also
- [as_base_class](as_base_class.md) - access the custom base class
- [Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass) - the requirements for `CustomBaseClass` - [Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass) - the requirements for `CustomBaseClass`
## Version history ## Version history
- Added in version 3.12.0. - Added in version 3.12.0.
- Made a public member type in version 3.13.0; it was private before, so it could not be named outside the class.
+1 -1
View File
@@ -13,7 +13,7 @@ JSON object holding version information
| key | description | | key | description |
|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). On HP aCC compilers, `compiler` is instead the plain string `hp`. | | `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `hp`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). |
| `copyright` | The copyright line for the library as string. | | `copyright` | The copyright line for the library as string. |
| `name` | The name of the library as string. | | `name` | The name of the library as string. |
| `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. | | `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. |
+11 -3
View File
@@ -89,6 +89,9 @@ Strong exception safety: if an exception occurs, the original value stays intact
- Throws [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) if an array index in the passed - Throws [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) if an array index in the passed
JSON pointer `ptr` exceeds the range of `size_type` (e.g., on 32-bit platforms). JSON pointer `ptr` exceeds the range of `size_type` (e.g., on 32-bit platforms).
For the **const** version, an object key or array index in `ptr` that does not exist is not reported by an
exception, but is undefined behavior (see the notes below). Use [`at`](at.md) for checked access.
## Complexity ## Complexity
1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`. 1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`.
@@ -103,9 +106,12 @@ Strong exception safety: if an exception occurs, the original value stays intact
The following cases apply to the **const** overloads; the non-const overloads instead insert the missing element The following cases apply to the **const** overloads; the non-const overloads instead insert the missing element
(see the notes below). (see the notes below).
1. If the element at index `idx` does not exist, the behavior is undefined. 1. If the element at index `idx` does not exist, the behavior is undefined and is **guarded by a
[runtime assertion](../../features/assertions.md)**!
2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a 2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a
[runtime assertion](../../features/assertions.md)**! [runtime assertion](../../features/assertions.md)**!
3. If the JSON pointer `ptr` refers to an object key or an array index that does not exist, the behavior is
undefined and is **guarded by a [runtime assertion](../../features/assertions.md)**!
1. The non-const version may add values: If `idx` is beyond the range of the array (i.e., `idx >= size()`), then the 1. The non-const version may add values: If `idx` is beyond the range of the array (i.e., `idx >= size()`), then the
array is silently filled up with `#!json null` values to make `idx` a valid reference to the last stored element. In array is silently filled up with `#!json null` values to make `idx` a valid reference to the last stored element. In
@@ -273,9 +279,11 @@ Strong exception safety: if an exception occurs, the original value stays intact
## Version history ## Version history
1. Added in version 1.0.0. Fixed in version 3.13.0 to throw `#!cpp std::length_error` instead of emptying the array and 1. Added in version 1.0.0. Fixed in version 3.13.0 to throw `#!cpp std::length_error` instead of emptying the array and
accessing it out of bounds when `idx` equals the maximum value of `size_type`. accessing it out of bounds when `idx` equals the maximum value of `size_type`. A missing index in the const version
is guarded by a runtime assertion since version 3.13.0.
2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3) 2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3)
in version 3.11.0. in version 3.11.0.
3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as 3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as
already supported by [`at`](at.md), [`value`](value.md), [`find`](find.md), and other lookup functions. already supported by [`at`](at.md), [`value`](value.md), [`find`](find.md), and other lookup functions.
4. Added in version 2.0.0. 4. Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since
version 3.13.0.
+10 -4
View File
@@ -5,17 +5,17 @@
bool operator==(const_reference lhs, const_reference rhs) noexcept; // (1) bool operator==(const_reference lhs, const_reference rhs) noexcept; // (1)
template<typename ScalarType> template<typename ScalarType>
bool operator==(const_reference lhs, const ScalarType rhs) noexcept; // (2) bool operator==(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2)
template<typename ScalarType> template<typename ScalarType>
bool operator==(ScalarType lhs, const const_reference rhs) noexcept; // (2) bool operator==(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
// since C++20 // since C++20
class basic_json { class basic_json {
bool operator==(const_reference rhs) const noexcept; // (1) bool operator==(const_reference rhs) const noexcept; // (1)
template<typename ScalarType> template<typename ScalarType>
bool operator==(ScalarType rhs) const noexcept; // (2) bool operator==(ScalarType rhs) const noexcept(/* see below */); // (2)
}; };
``` ```
@@ -46,7 +46,12 @@ whether the values `lhs`/`*this` and `rhs` are equal
## Exception safety ## Exception safety
No-throw guarantee: this function never throws exceptions. 1. No-throw guarantee: this function never throws exceptions.
2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and
`#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion
throws, for example `std::bad_alloc` when converting a string, or
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md).
## Complexity ## Complexity
@@ -171,3 +176,4 @@ Linear.
1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. 1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0.
2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. 2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0.
Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`.
@@ -5,10 +5,10 @@
bool operator>=(const_reference lhs, const_reference rhs) noexcept; // (1) bool operator>=(const_reference lhs, const_reference rhs) noexcept; // (1)
template<typename ScalarType> template<typename ScalarType>
bool operator>=(const_reference lhs, const ScalarType rhs) noexcept; // (2) bool operator>=(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2)
template<typename ScalarType> template<typename ScalarType>
bool operator>=(ScalarType lhs, const const_reference rhs) noexcept; // (2) bool operator>=(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
``` ```
1. Compares whether one JSON value `lhs` is greater than or equal to another JSON value `rhs` according to the following 1. Compares whether one JSON value `lhs` is greater than or equal to another JSON value `rhs` according to the following
@@ -39,7 +39,12 @@ whether `lhs` is greater than or equal to `rhs`
## Exception safety ## Exception safety
No-throw guarantee: this function never throws exceptions. 1. No-throw guarantee: this function never throws exceptions.
2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and
`#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion
throws, for example `std::bad_alloc` when converting a string, or
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md).
## Complexity ## Complexity
@@ -94,3 +99,4 @@ Linear.
1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0.
2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0.
Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`.
@@ -5,10 +5,10 @@
bool operator>(const_reference lhs, const_reference rhs) noexcept; // (1) bool operator>(const_reference lhs, const_reference rhs) noexcept; // (1)
template<typename ScalarType> template<typename ScalarType>
bool operator>(const_reference lhs, const ScalarType rhs) noexcept; // (2) bool operator>(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2)
template<typename ScalarType> template<typename ScalarType>
bool operator>(ScalarType lhs, const const_reference rhs) noexcept; // (2) bool operator>(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
``` ```
1. Compares whether one JSON value `lhs` is greater than another JSON value `rhs` according to the 1. Compares whether one JSON value `lhs` is greater than another JSON value `rhs` according to the
@@ -39,7 +39,12 @@ whether `lhs` is greater than `rhs`
## Exception safety ## Exception safety
No-throw guarantee: this function never throws exceptions. 1. No-throw guarantee: this function never throws exceptions.
2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and
`#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion
throws, for example `std::bad_alloc` when converting a string, or
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md).
## Complexity ## Complexity
@@ -84,3 +89,4 @@ Linear.
1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0.
2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0.
Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`.
@@ -5,10 +5,10 @@
bool operator<=(const_reference lhs, const_reference rhs) noexcept; // (1) bool operator<=(const_reference lhs, const_reference rhs) noexcept; // (1)
template<typename ScalarType> template<typename ScalarType>
bool operator<=(const_reference lhs, const ScalarType rhs) noexcept; // (2) bool operator<=(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2)
template<typename ScalarType> template<typename ScalarType>
bool operator<=(ScalarType lhs, const const_reference rhs) noexcept; // (2) bool operator<=(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
``` ```
1. Compares whether one JSON value `lhs` is less than or equal to another JSON value `rhs` 1. Compares whether one JSON value `lhs` is less than or equal to another JSON value `rhs`
@@ -40,7 +40,12 @@ whether `lhs` is less than or equal to `rhs`
## Exception safety ## Exception safety
No-throw guarantee: this function never throws exceptions. 1. No-throw guarantee: this function never throws exceptions.
2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and
`#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion
throws, for example `std::bad_alloc` when converting a string, or
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md).
## Complexity ## Complexity
@@ -95,3 +100,4 @@ Linear.
1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0.
2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0.
Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`.
@@ -5,10 +5,10 @@
bool operator<(const_reference lhs, const_reference rhs) noexcept; // (1) bool operator<(const_reference lhs, const_reference rhs) noexcept; // (1)
template<typename ScalarType> template<typename ScalarType>
bool operator<(const_reference lhs, const ScalarType rhs) noexcept; // (2) bool operator<(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2)
template<typename ScalarType> template<typename ScalarType>
bool operator<(ScalarType lhs, const const_reference rhs) noexcept; // (2) bool operator<(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
``` ```
1. Compares whether one JSON value `lhs` is less than another JSON value `rhs` according to the 1. Compares whether one JSON value `lhs` is less than another JSON value `rhs` according to the
@@ -49,7 +49,12 @@ whether `lhs` is less than `rhs`
## Exception safety ## Exception safety
No-throw guarantee: this function never throws exceptions. 1. No-throw guarantee: this function never throws exceptions.
2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and
`#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion
throws, for example `std::bad_alloc` when converting a string, or
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md).
## Complexity ## Complexity
@@ -94,3 +99,4 @@ Linear.
1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0.
2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0.
Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`.
@@ -5,10 +5,10 @@
bool operator!=(const_reference lhs, const_reference rhs) noexcept; // (1) bool operator!=(const_reference lhs, const_reference rhs) noexcept; // (1)
template<typename ScalarType> template<typename ScalarType>
bool operator!=(const_reference lhs, const ScalarType rhs) noexcept; // (2) bool operator!=(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2)
template<typename ScalarType> template<typename ScalarType>
bool operator!=(ScalarType lhs, const const_reference rhs) noexcept; // (2) bool operator!=(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
``` ```
1. Compares two JSON values for inequality. Returns `#!cpp !(lhs == rhs)`. 1. Compares two JSON values for inequality. Returns `#!cpp !(lhs == rhs)`.
@@ -36,7 +36,12 @@ whether the values `lhs`/`*this` and `rhs` are not equal
## Exception safety ## Exception safety
No-throw guarantee: this function never throws exceptions. 1. No-throw guarantee: this function never throws exceptions.
2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and
`#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion
throws, for example `std::bad_alloc` when converting a string, or
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md).
## Complexity ## Complexity
@@ -98,3 +103,4 @@ Linear.
member function in version 3.13.0; since C++20, the compiler rewrites `a != b` using `operator==`. member function in version 3.13.0; since C++20, the compiler rewrites `a != b` using `operator==`.
2. Added in version 1.0.0. Changed in version 3.13.0 to remove special-casing for `NaN` and `discarded` values; 2. Added in version 1.0.0. Changed in version 3.13.0 to remove special-casing for `NaN` and `discarded` values;
`operator!=` now consistently means `!(a == b)`. Since C++20, the compiler rewrites `a != b` using `operator==`. `operator!=` now consistently means `!(a == b)`. Since C++20, the compiler rewrites `a != b` using `operator==`.
Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`.
@@ -6,7 +6,7 @@ class basic_json {
std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1) std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1)
template<typename ScalarType> template<typename ScalarType>
std::partial_ordering operator<=>(const ScalarType rhs) const noexcept; // (2) std::partial_ordering operator<=>(const ScalarType rhs) const noexcept(/* see below */); // (2)
}; };
``` ```
@@ -39,7 +39,12 @@ the `std::partial_ordering` of the 3-way comparison of `*this` and `rhs`
## Exception safety ## Exception safety
No-throw guarantee: this function never throws exceptions. 1. No-throw guarantee: this function never throws exceptions.
2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and
`#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion
throws, for example `std::bad_alloc` when converting a string, or
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by
[`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md).
## Complexity ## Complexity
@@ -98,3 +103,4 @@ Linear.
1. Added in version 3.11.0. 1. Added in version 3.11.0.
2. Added in version 3.11.0. 2. Added in version 3.11.0.
Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`.
+18 -3
View File
@@ -5,15 +5,18 @@
static std::vector<std::uint8_t> to_bjdata(const basic_json& j, static std::vector<std::uint8_t> to_bjdata(const basic_json& j,
const bool use_size = false, const bool use_size = false,
const bool use_type = false, const bool use_type = false,
const bjdata_version_t version = bjdata_version_t::draft2); const bjdata_version_t version = bjdata_version_t::draft2,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
static void to_bjdata(const basic_json& j, detail::output_adapter<std::uint8_t> o, static void to_bjdata(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const bool use_size = false, const bool use_type = false, const bool use_size = false, const bool use_type = false,
const bjdata_version_t version = bjdata_version_t::draft2); const bjdata_version_t version = bjdata_version_t::draft2,
const error_handler_t error_handler = error_handler_t::keep);
static void to_bjdata(const basic_json& j, detail::output_adapter<char> o, static void to_bjdata(const basic_json& j, detail::output_adapter<char> o,
const bool use_size = false, const bool use_type = false, const bool use_size = false, const bool use_type = false,
const bjdata_version_t version = bjdata_version_t::draft2); const bjdata_version_t version = bjdata_version_t::draft2,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Serializes a given JSON value `j` to a byte vector using the BJData (Binary JData) serialization format. BJData aims to Serializes a given JSON value `j` to a byte vector using the BJData (Binary JData) serialization format. BJData aims to
@@ -43,6 +46,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: which version of BJData to use (see note on "Binary values" on [BJData](../../features/binary_formats/bjdata.md)); : which version of BJData to use (see note on "Binary values" on [BJData](../../features/binary_formats/bjdata.md));
optional, `#!cpp bjdata_version_t::draft2` by default. optional, `#!cpp bjdata_version_t::draft2` by default.
`error_handler` (in)
: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_bjdata` did before
this parameter was added; `strict` throws; `replace`/`ignore` sanitize it the same way [`dump`](dump.md) would.
If [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, the default is `strict` instead.
## Return value ## Return value
1. BJData serialization as byte vector 1. BJData serialization as byte vector
@@ -56,6 +65,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
is false, and `j` contains a non-empty array, object, or binary value. is false, and `j` contains a non-empty array, object, or binary value.
- 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)
## Complexity ## Complexity
@@ -105,3 +117,6 @@ Linear in the size of the JSON value `j`.
- Added in version 3.11.0. - Added in version 3.11.0.
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0. - 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`.
+19 -3
View File
@@ -2,11 +2,14 @@
```cpp ```cpp
// (1) // (1)
static std::vector<std::uint8_t> to_bson(const basic_json& j); static std::vector<std::uint8_t> to_bson(const basic_json& j,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o); static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
static void to_bson(const basic_json& j, detail::output_adapter<char> o); const error_handler_t error_handler = error_handler_t::keep);
static void to_bson(const basic_json& j, detail::output_adapter<char> o,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are stored as a single entity (a BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are stored as a single entity (a
@@ -25,6 +28,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`o` (in) `o` (in)
: output adapter to write serialization to : output adapter to write serialization to
`error_handler` (in)
: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_bson` did before
this parameter was added; `strict` throws; `replace`/`ignore` sanitize it the same way [`dump`](dump.md) would.
If [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, the default is `strict` instead.
## Return value ## Return value
1. BSON serialization as a byte vector 1. BSON serialization as a byte vector
@@ -46,6 +55,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value - Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
exceeds 255, the maximum of the BSON binary subtype; example: exceeds 255, the maximum of the BSON binary subtype; example:
`"subtype 70000 is too large for the BSON binary subtype (max 255)"` `"subtype 70000 is too large for the BSON binary subtype (max 255)"`
- 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)
## Complexity ## Complexity
@@ -98,3 +110,7 @@ pass before anything is written.
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0. - 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. - 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. - `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.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` before anything
is written.
+21 -3
View File
@@ -2,11 +2,14 @@
```cpp ```cpp
// (1) // (1)
static std::vector<std::uint8_t> to_cbor(const basic_json& j); static std::vector<std::uint8_t> to_cbor(const basic_json& j,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o); static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o,
static void to_cbor(const basic_json& j, detail::output_adapter<char> o); const error_handler_t error_handler = error_handler_t::keep);
static void to_cbor(const basic_json& j, detail::output_adapter<char> o,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Serializes a given JSON value `j` to a byte vector using the CBOR (Concise Binary Object Representation) serialization Serializes a given JSON value `j` to a byte vector using the CBOR (Concise Binary Object Representation) serialization
@@ -26,6 +29,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`o` (in) `o` (in)
: output adapter to write serialization to : output adapter to write serialization to
`error_handler` (in)
: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_cbor` did before
this parameter was added; `strict` throws; `replace`/`ignore` sanitize it the same way [`dump`](dump.md) would.
If [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, the default is `strict` instead.
## Return value ## Return value
1. CBOR serialization as a byte vector 1. CBOR serialization as a byte vector
@@ -35,6 +44,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
Strong guarantee: if an exception is thrown, there are no changes in the JSON value. Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
## Exceptions
- 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)
## Complexity ## Complexity
Linear in the size of the JSON value `j`. Linear in the size of the JSON value `j`.
@@ -68,3 +83,6 @@ Linear in the size of the JSON value `j`.
- Added in version 2.0.9. - Added in version 2.0.9.
- Compact representation of floating-point numbers added in version 3.8.0. - Compact representation of floating-point numbers added in version 3.8.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`.
+17 -3
View File
@@ -2,11 +2,14 @@
```cpp ```cpp
// (1) // (1)
static std::vector<std::uint8_t> to_msgpack(const basic_json& j); static std::vector<std::uint8_t> to_msgpack(const basic_json& j,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o); static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o,
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o); const error_handler_t error_handler = error_handler_t::keep);
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Serializes a given JSON value `j` to a byte vector using the MessagePack serialization format. MessagePack is a binary Serializes a given JSON value `j` to a byte vector using the MessagePack serialization format. MessagePack is a binary
@@ -25,6 +28,13 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`o` (in) `o` (in)
: output adapter to write serialization to : output adapter to write serialization to
`error_handler` (in)
: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_msgpack` did before
this parameter was added and as the MessagePack specification allows; `strict` throws; `replace`/`ignore` sanitize
it the same way [`dump`](dump.md) would. Unlike the other binary writers, the default stays `keep` even if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled.
## Return value ## Return value
1. MessagePack serialization as a byte vector 1. MessagePack serialization as a byte vector
@@ -42,6 +52,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value - Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
exceeds 255, the maximum of the MessagePack ext type; example: exceeds 255, the maximum of the MessagePack ext type; example:
`"subtype 70000 is too large for the MessagePack ext type (max 255)"` `"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`
## Complexity ## Complexity
@@ -91,6 +103,8 @@ Linear in the size of the JSON value `j`.
- Added in version 2.0.9. - Added in version 2.0.9.
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0. - Throws `out_of_range.412` and `out_of_range.415` since version 3.13.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.
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly; - 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 before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
`number_unsigned_t`. `number_unsigned_t`.
+18 -3
View File
@@ -4,13 +4,16 @@
// (1) // (1)
static std::vector<std::uint8_t> to_ubjson(const basic_json& j, static std::vector<std::uint8_t> to_ubjson(const basic_json& j,
const bool use_size = false, const bool use_size = false,
const bool use_type = false); const bool use_type = false,
const error_handler_t error_handler = error_handler_t::keep);
// (2) // (2)
static void to_ubjson(const basic_json& j, detail::output_adapter<std::uint8_t> o, static void to_ubjson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
const bool use_size = false, const bool use_type = false); const bool use_size = false, const bool use_type = false,
const error_handler_t error_handler = error_handler_t::keep);
static void to_ubjson(const basic_json& j, detail::output_adapter<char> o, static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,
const bool use_size = false, const bool use_type = false); const bool use_size = false, const bool use_type = false,
const error_handler_t error_handler = error_handler_t::keep);
``` ```
Serializes a given JSON value `j` to a byte vector using the UBJSON (Universal Binary JSON) serialization format. UBJSON Serializes a given JSON value `j` to a byte vector using the UBJSON (Universal Binary JSON) serialization format. UBJSON
@@ -36,6 +39,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: whether to add type annotations to container types (must be combined with `#!cpp use_size = true`); optional, : whether to add type annotations to container types (must be combined with `#!cpp use_size = true`); optional,
`#!cpp false` by default. `#!cpp false` by default.
`error_handler` (in)
: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md).
The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_ubjson` did before
this parameter was added; `strict` throws; `replace`/`ignore` sanitize it the same way [`dump`](dump.md) would.
If [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, the default is `strict` instead.
## Return value ## Return value
1. UBJSON serialization as a byte vector 1. UBJSON serialization as a byte vector
@@ -49,6 +58,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
is false, and `j` contains a non-empty array, object, or binary value. is false, and `j` contains a non-empty array, object, or binary value.
- 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)
## Complexity ## Complexity
@@ -97,3 +109,6 @@ Linear in the size of the JSON value `j`.
## Version history ## Version history
- Added in version 3.1.0. - Added in version 3.1.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`.
+3 -1
View File
@@ -222,7 +222,9 @@ changes to any JSON value.
1. Added in version 1.0.0. Changed parameter `default_value` type from `const ValueType&` to `ValueType&&` in version 1. Added in version 1.0.0. Changed parameter `default_value` type from `const ValueType&` to `ValueType&&` in version
3.11.0. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time 3.11.0. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time
instead of causing undefined behavior at runtime. instead of causing undefined behavior at runtime.
2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2. 2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2. Fixed in version 3.13.0
to consistently accept `std::string_view`-convertible keys, as already supported by
[`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), and other lookup functions.
3. Added in version 2.0.2. Extended to work with arrays in version 3.13.0, including fixing an issue where resolving 3. Added in version 2.0.2. Extended to work with arrays in version 3.13.0, including fixing an issue where resolving
`ptr` through an array unexpectedly threw `out_of_range` instead of returning the resolved element (or `ptr` through an array unexpectedly threw `out_of_range` instead of returning the resolved element (or
`default_value`, as documented). `default_value`, as documented).
+2
View File
@@ -18,6 +18,8 @@ header. See also the [macro overview page](../../features/macros.md).
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned - [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned
right after a parsed number right after a parsed number
- [**JSON_STRICT_BINARY_UTF8**](json_strict_binary_utf8.md) - opt in to checking strings for valid UTF-8 in the CBOR,
UBJSON, BJData, and BSON writers
- [**JSON_STRICT_NUL_HANDLING**](json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input instead of - [**JSON_STRICT_NUL_HANDLING**](json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input instead of
treating it as end of input treating it as end of input
@@ -115,3 +115,4 @@ The default value is `0` (disabled — existing behavior is preserved).
## Version history ## Version history
- Added in version 3.13.0. - Added in version 3.13.0.
- Planned to become the default (with the macro removed) in version 4.0.0.
@@ -43,9 +43,9 @@ By default, `#!cpp JSON_NO_AUTOMATIC_UDLS` is not defined, and `<nlohmann/json.h
```cpp ```cpp
// compiled with -DJSON_NO_AUTOMATIC_UDLS for the whole project // compiled with -DJSON_NO_AUTOMATIC_UDLS for the whole project
#include <nlohmann/json.hpp>
// this file uses the literals, so it includes them explicitly // this file uses the literals, so it includes them explicitly
// (the header includes <nlohmann/json.hpp> itself)
#include <nlohmann/json_literals.hpp> #include <nlohmann/json_literals.hpp>
int main() int main()
@@ -62,6 +62,7 @@ By default, `#!cpp JSON_NO_AUTOMATIC_UDLS` is not defined, and `<nlohmann/json.h
- [`operator""_json`](../operator_literal_json.md) - [`operator""_json`](../operator_literal_json.md)
- [`operator""_json_pointer`](../operator_literal_json_pointer.md) - [`operator""_json_pointer`](../operator_literal_json_pointer.md)
- [`JSON_USE_GLOBAL_UDLS`](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace - [`JSON_USE_GLOBAL_UDLS`](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
- [Compile times](../../integration/compile_times.md) - options to reduce compile times
## Version history ## Version history
@@ -0,0 +1,101 @@
# JSON_STRICT_BINARY_UTF8
```cpp
#define JSON_STRICT_BINARY_UTF8 /* value */
```
When defined to `1`, the `error_handler` parameter of the binary writers [`to_cbor`](../basic_json/to_cbor.md),
[`to_ubjson`](../basic_json/to_ubjson.md), [`to_bjdata`](../basic_json/to_bjdata.md), and
[`to_bson`](../basic_json/to_bson.md) defaults to [`error_handler_t::strict`](../basic_json/error_handler_t.md) instead
of `error_handler_t::keep`. These writers then check every string value and object key for valid UTF-8 and throw
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8, like
[`dump`](../basic_json/dump.md) does. Without it, they write the bytes unchanged. An `error_handler` passed explicitly
always takes precedence.
The macro does not affect:
- [`to_msgpack`](../basic_json/to_msgpack.md): the MessagePack specification allows a `str` value to contain bytes that
are not valid UTF-8, so its `error_handler` always defaults to `keep`.
- [`to_bon8`](../basic_json/to_bon8.md): BON8 always checks, because the UTF-8 lead bytes mark where a string ends.
- The binary readers ([`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_bson`](../basic_json/from_bson.md)): none of these formats requires a decoder to reject ill-formed UTF-8, so
they always return the bytes unchanged.
## Default definition
The default value is `0` (disabled, the behavior of version 3.12.0 and earlier is preserved).
```cpp
#define JSON_STRICT_BINARY_UTF8 0
```
## Notes
!!! note "Background"
CBOR, UBJSON, BJData, and BSON all require strings to be UTF-8. Up to version 3.12.0, the writers did not check
this, so they could produce output that other decoders reject. Checking by default would break code that stores
other encodings (for instance ISO 8859-1) in a string and only ever writes it to a binary format. You can pass
`error_handler_t::strict` to each call, or use this macro to check by default ahead of version 4.0.0, where
`strict` is planned to become the default (see
[#5529](https://github.com/nlohmann/json/issues/5529) and [#5651](https://github.com/nlohmann/json/issues/5651)).
!!! 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 `_sbu8`), 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, the bytes are written unchanged:
```cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
auto v = json::to_cbor(json("\xFF"));
// v is {0x61, 0xFF}
}
```
??? example "Example: opt-in check (macro defined to 1)"
With the macro, ill-formed UTF-8 is rejected:
```cpp
#define JSON_STRICT_BINARY_UTF8 1
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
auto v = json::to_cbor(json("\xFF"));
// throws type_error.316: invalid UTF-8 byte at index 0: 0xFF
}
```
## See also
- [**to_cbor**](../basic_json/to_cbor.md) - create a CBOR serialization of a JSON value
- [**to_ubjson**](../basic_json/to_ubjson.md) - create a UBJSON serialization of a JSON value
- [**to_bjdata**](../basic_json/to_bjdata.md) - create a BJData serialization of a JSON value
- [**to_bson**](../basic_json/to_bson.md) - create a BSON serialization of a JSON value
- [**error_handler_t**](../basic_json/error_handler_t.md) - how [`dump`](../basic_json/dump.md) treats ill-formed UTF-8
## Version history
- Added in version 3.13.0.
- Planned to become the default (with the macro removed) in version 4.0.0.
@@ -5,7 +5,9 @@
``` ```
When defined to `0`, implicit conversions are switched off. By default, implicit conversions are switched on. The When defined to `0`, implicit conversions are switched off. By default, implicit conversions are switched on. The
value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md). value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md) and the
[converting constructor](../basic_json/basic_json.md) from a `basic_json` specialization with a different string
type (overload 4).
## Default definition ## Default definition
@@ -42,7 +44,7 @@ By default, implicit conversions are enabled.
## Examples ## Examples
??? example ??? example "Example: implicit conversion"
This is an example for an implicit conversion: This is an example for an implicit conversion:
@@ -59,6 +61,25 @@ By default, implicit conversions are enabled.
auto s = j.get<std::string>(); auto s = j.get<std::string>();
``` ```
??? example "Example: conversion between `basic_json` specializations"
A `basic_json` specialization with a different string type is also no longer converted implicitly when
`JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`:
```cpp
using wjson = nlohmann::basic_json<std::map, std::vector, std::wstring>;
void load(const nlohmann::json& j);
wjson wj = /* ... */;
load(wj); // error: no implicit conversion
load(nlohmann::json(wj)); // OK: explicit conversion
load(wj.get<nlohmann::json>()); // OK: explicit conversion
```
Specializations that share the same string type, such as `json` and `ordered_json`, remain implicitly
convertible.
## See also ## See also
- [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit) - [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit)
@@ -68,3 +89,4 @@ By default, implicit conversions are enabled.
## Version history ## Version history
- Added in version 3.9.0. - Added in version 3.9.0.
- Also affects the conversion between `basic_json` specializations with different string types since version 3.13.0.
@@ -81,3 +81,6 @@ When the macro is not defined, the library will define it to its default value.
## Version history ## Version history
- Added in version 3.11.0. - Added in version 3.11.0.
- Fixed in version 3.13.0 so `<=` and `>=` also emulate the legacy behavior in C++20 when the JSON value is the
right-hand operand of a scalar comparison; before, only the 3-way-comparison-rewritten candidate was found, which
yielded `#!cpp false` instead of `#!cpp true`.
+65 -5
View File
@@ -14,7 +14,7 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js
opt-in. opt-in.
- **Keep the 3.x public API stable.** Releases follow [semantic versioning](https://semver.org). Changes that would - **Keep the 3.x public API stable.** Releases follow [semantic versioning](https://semver.org). Changes that would
break existing code are only added behind a feature macro, so users can opt in and test their code before a next break existing code are only added behind a feature macro, so users can opt in and test their code before a next
major release. major release, see [Version 4.0](#version-40).
- **Support a broad range of compilers and platforms.** The [CI](quality_assurance.md) keeps testing old and new - **Support a broad range of compilers and platforms.** The [CI](quality_assurance.md) keeps testing old and new
versions of GCC, Clang, MSVC, and other compilers on Linux, macOS, and Windows. versions of GCC, Clang, MSVC, and other compilers on Linux, macOS, and Windows.
- **Keep the quality assurance up.** Every change keeps the test coverage at 100%, passes the static and dynamic - **Keep the quality assurance up.** Every change keeps the test coverage at 100%, passes the static and dynamic
@@ -37,7 +37,67 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js
## Version 4.0 ## Version 4.0
There is no decision yet on whether or when a version 4.0 with breaking changes will be released. Proposals that need There is no release date for version 4.0 yet. Proposals that need a major version, for instance stricter type
a major version, for instance stricter type conversions, are collected in issue conversions, are collected in issue [#3453](https://github.com/nlohmann/json/issues/3453).
[#3453](https://github.com/nlohmann/json/issues/3453). Until then, such changes are only added as opt-in behavior
behind feature macros. !!! note "Not final"
The plan for version 4.0 described below is not final and may still change: macros may be added to or removed from
the list, and planned defaults may be revised. Any such change will be documented on this page.
### Trying out 4.0 today
Version 4.0 will not be developed on a separate branch. Instead, every breaking change is first added to a 3.x release
behind a macro whose default keeps the 3.x behavior. Version 4.0 then switches the defaults and removes the macros.
Version 4.0 is therefore the sum of these macros: you can try it on the 3.x release train today by defining each macro
to its 4.0 value and fixing what no longer compiles or behaves differently. Once your code works with all of them, it
is ready for version 4.0.
The following macros guard changes that are planned to become the default in version 4.0:
| Macro | 3.x default | 4.0 behavior | CMake option | Added |
|------------------------------------------------------------------------------------------------------------------|-------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|--------|
| [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) | `1` | `0`: no implicit conversions from `basic_json` to other types; use [`get`](../api/basic_json/get.md) instead | [`JSON_ImplicitConversions`](../integration/cmake.md#json_implicitconversions) | 3.9.0 |
| [`JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global_udls.md) | `1` | `0`: the string literals `_json` and `_json_pointer` are only available in namespace `nlohmann::literals` | [`JSON_GlobalUDLs`](../integration/cmake.md#json_globaludls) | 3.11.0 |
| [`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md) | `0` | removed: the deprecated legacy comparison of discarded values can no longer be enabled | [`JSON_LegacyDiscardedValueComparison`](../integration/cmake.md#json_legacydiscardedvaluecomparison) | 3.11.0 |
| [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) | `0` | `1`: single-element brace initialization such as `#!cpp json j{obj};` copies the element instead of creating an array | – | 3.13.0 |
| [`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 |
For example, the following makes a 3.x release behave like version 4.0 with respect to these changes:
```cpp
#define JSON_USE_IMPLICIT_CONVERSIONS 0
#define JSON_USE_GLOBAL_UDLS 0
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#define JSON_BRACE_INIT_COPY_SEMANTICS 1
#define JSON_PRECISE_STREAM_POSITION 1
#define JSON_STRICT_NUL_HANDLING 1
#define JSON_STRICT_BINARY_UTF8 1
#include <nlohmann/json.hpp>
```
The macros must be defined before the library header is included; setting them once in the build system is the easiest
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.
| Deprecated | Since | Migration |
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------------------------------------------------------------------------------|
| `#!cpp operator<<(basic_json&, std::istream&)` | 3.0.0 | [Parsing](../integration/migration_guide.md#parsing) |
| `#!cpp operator>>(const basic_json&, std::ostream&)` | 3.0.0 | [Miscellaneous functions](../integration/migration_guide.md#miscellaneous-functions) |
| `iterator_wrapper` | 3.1.0 | [Miscellaneous functions](../integration/migration_guide.md#miscellaneous-functions) |
| [`parse`](../api/basic_json/parse.md), [`accept`](../api/basic_json/accept.md), and [`sax_parse`](../api/basic_json/sax_parse.md) with an initializer list `{ptr, len}` or `{first, last}` | 3.8.0 | [Parsing](../integration/migration_guide.md#parsing) |
| [`from_bson`](../api/basic_json/from_bson.md), [`from_cbor`](../api/basic_json/from_cbor.md), [`from_msgpack`](../api/basic_json/from_msgpack.md), and [`from_ubjson`](../api/basic_json/from_ubjson.md) with `(ptr, len)` or an initializer list | 3.8.0 | [Parsing](../integration/migration_guide.md#parsing) |
| [`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) |
The deprecated legacy comparison of discarded values is controlled by a macro and therefore listed in the table above.
New breaking changes will follow the same path: they are added to these tables when they land in a 3.x release.
@@ -0,0 +1,41 @@
#include <iostream>
#include <nlohmann/json.hpp>
class base_class_with_hidden_members
{
public:
const char* type_name() const noexcept
{
return "my_type_name";
}
std::size_t size() const noexcept
{
return 42;
}
};
using json = nlohmann::basic_json <
std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
std::vector<std::uint8_t>,
base_class_with_hidden_members
>;
int main()
{
json j = {1, 2, 3};
// the members of basic_json hide the members of the base class
std::cout << j.type_name() << ' ' << j.size() << '\n';
// access the hidden members of the base class
std::cout << j.as_base_class().type_name() << ' ' << j.as_base_class().size() << '\n';
}
@@ -0,0 +1,2 @@
array 3
my_type_name 42
@@ -20,5 +20,6 @@ int main()
<< j_invalid.dump(-1, ' ', false, json::error_handler_t::replace) << j_invalid.dump(-1, ' ', false, json::error_handler_t::replace)
<< "\nstring with ignored invalid characters: " << "\nstring with ignored invalid characters: "
<< j_invalid.dump(-1, ' ', false, json::error_handler_t::ignore) << j_invalid.dump(-1, ' ', false, json::error_handler_t::ignore)
<< '\n'; << "\nstring with the invalid byte kept as is (" << j_invalid.dump(-1, ' ', false, json::error_handler_t::keep).size()
<< " bytes, not valid UTF-8 itself)\n";
} }
@@ -1,3 +1,4 @@
[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9 [json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9
string with replaced invalid characters: "ä�ü" string with replaced invalid characters: "ä�ü"
string with ignored invalid characters: "äü" string with ignored invalid characters: "äü"
string with the invalid byte kept as is (7 bytes, not valid UTF-8 itself)
@@ -79,6 +79,7 @@ Some important things:
* When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.) * When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.)
* In function `from_json`, use function [`at()`](../api/basic_json/at.md) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior. * In function `from_json`, use function [`at()`](../api/basic_json/at.md) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior.
* You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these. * You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these.
* If you control the type, consider defining `to_json`/`from_json` as `friend` functions inside the class ("hidden friends"). Argument-dependent lookup then only finds them for your type, which also avoids a [GCC < 11 compilation error](../home/faq.md#incomplete-detector-type-with-gcc-11).
??? example "Example: serialize a `person` to JSON with `to_json`" ??? example "Example: serialize a `person` to JSON with `to_json`"
+31 -7
View File
@@ -16,14 +16,15 @@ before including the `json.hpp` header.
## Function with runtime assertions ## Function with runtime assertions
### Unchecked object access to a const value ### Unchecked access to a const value
Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for objects. Whereas a missing Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for arrays and objects. Whereas
key is added in the case of non-const objects, accessing a const object with a missing key is undefined behavior (think a missing element is added in the case of non-const values, accessing a const value with a missing object key or an
of a dereferenced null pointer) and yields a runtime assertion. invalid array index is undefined behavior (think of a dereferenced null pointer) and yields a runtime assertion. This
also applies to a [JSON pointer](json_pointer.md) that refers to a missing key or an invalid index.
If you are not sure whether an element in an object exists, use checked access with the If you are not sure whether an element exists, use checked access with the [`at` function](../api/basic_json/at.md)
[`at` function](../api/basic_json/at.md) or call the [`contains` function](../api/basic_json/contains.md) before. or call the [`contains` function](../api/basic_json/contains.md) before.
See also the documentation on [element access](element_access/index.md). See also the documentation on [element access](element_access/index.md).
@@ -46,7 +47,30 @@ See also the documentation on [element access](element_access/index.md).
Output: Output:
``` ```
Assertion failed: (m_value.object->find(key) != m_value.object->end()), function operator[], file json.hpp, line 2144. Assertion failed: (it != m_data.m_value.object->end()), function operator[], file json.hpp, line 28795.
```
??? example "Example 2: Invalid array index in a JSON pointer"
The following code will trigger an assertion at runtime:
```cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using namespace nlohmann::literals;
int main()
{
const json j = {{"array", {1, 2, 3}}};
auto v = j["/array/5"_json_pointer];
}
```
Output:
```
Assertion failed: (idx < m_data.m_value.array->size()), function operator[], file json.hpp, line 28758.
``` ```
### Constructing from an uninitialized iterator range ### Constructing from an uninitialized iterator range
@@ -63,6 +63,15 @@ The library uses the following mapping from JSON values types to BJData types ac
- strings with more than 18446744073709551615 bytes, i.e., 2<sup>64</sup>-1 bytes (theoretical) - strings with more than 18446744073709551615 bytes, i.e., 2<sup>64</sup>-1 bytes (theoretical)
!!! warning "UTF-8 validation of string values and object keys"
BJData strings must use UTF-8 encoding. By default (the [`error_handler`](../../api/basic_json/to_bjdata.md)
parameter left at `keep`), `to_bjdata()` writes the bytes of string values and object keys unchanged, even if they
are not valid UTF-8. With `error_handler_t::strict`, it throws
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 instead;
`replace`/`ignore` sanitize the string. [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md)
makes `strict` the default.
!!! info "Unused BJData markers" !!! info "Unused BJData markers"
The following markers are not used in the conversion: The following markers are not used in the conversion:
@@ -208,6 +217,19 @@ The library maps BJData types to JSON value types as follows:
The mapping is **complete** in the sense that any BJData value can be converted to a JSON value. The mapping is **complete** in the sense that any BJData value can be converted to a JSON value.
!!! warning "Ill-formed UTF-8 in string values and object keys"
BJData strings must use UTF-8 encoding, but checking it on read is opt-in: with the
[`error_handler`](../../api/basic_json/from_bjdata.md) parameter left at `keep` (the default), `from_bjdata()`
accepts a string value or object key whose bytes are not valid UTF-8 and hands them back unchanged. Passing
`error_handler_t::strict` makes `from_bjdata()` check and throw
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and
`replace`/`ignore` sanitize the string instead of keeping it. However,
[`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read with the default
`keep` handler, unless an error handler is passed that replaces or ignores the ill-formed bytes. `to_bjdata()`'s
own `error_handler` parameter defaults to `keep` (see above), so such a value is written back unchanged.
!!! info "Round trips" !!! info "Round trips"
A value returned by [`from_bjdata`](../../api/basic_json/from_bjdata.md) can be serialized with A value returned by [`from_bjdata`](../../api/basic_json/from_bjdata.md) can be serialized with
@@ -109,14 +109,21 @@ The library maps BSON record types to JSON value types as follows:
If BSON input must be validated for strict specification compliance, validate it separately before passing it to If BSON input must be validated for strict specification compliance, validate it separately before passing it to
`from_bson()`. `from_bson()`.
!!! warning "UTF-8 validation of string values" !!! warning "Ill-formed UTF-8 in string values"
The BSON specification requires `string` values (type `0x02`) to be valid UTF-8. This library validates the The BSON specification requires `string` values (type `0x02`) to be valid UTF-8, but this is not required of a
bytes of every such string at decode time and rejects ill-formed UTF-8 with a decoder, so checking is opt-in: with the [`error_handler`](../../api/basic_json/from_bson.md) parameter left at
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions` `keep` (the default), `from_bson()` accepts a `string` value whose bytes are not valid UTF-8 and hands them back
set to `false`, a discarded value), rather than only failing later when the resulting value is dumped. Element unchanged. Passing `error_handler_t::strict` makes `from_bson()` check and throw
(key) names and `binary` values (type `0x05`) are unaffected and are never validated, since they are read [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and
byte-by-byte as a C string, or are not required to hold text, respectively. `replace`/`ignore` sanitize the string instead of keeping it. However, [`dump()`](../../api/basic_json/dump.md)
still requires valid UTF-8 and throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a
value read with the default `keep` handler, unless an error handler is passed that replaces or ignores the
ill-formed bytes. `to_bson()`'s own `error_handler` parameter defaults to `keep`, so such a string value or element
(key) name is written unchanged; with `strict` (the default if
[`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception
instead. Element (key) names are never validated on read, since they are read byte-by-byte as a C string. `binary`
values (type `0x05`) are unaffected, since they are not required to hold text.
??? example "Example: deserialize a JSON value from BSON" ??? example "Example: deserialize a JSON value from BSON"
@@ -189,15 +189,21 @@ The library maps CBOR types to JSON value types as follows:
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a ([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
general-purpose CBOR library instead. general-purpose CBOR library instead.
!!! warning "UTF-8 validation of text strings" !!! warning "Ill-formed UTF-8 in text strings"
[RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings [RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings (major
(major type 3) to be valid UTF-8. This library validates the bytes of every text string (object keys included) at type 3) to be valid UTF-8, but leaves it up to the decoder whether to enforce this, so checking is opt-in: with the
decode time and rejects ill-formed UTF-8 with a [`error_handler`](../../api/basic_json/from_cbor.md) parameter left at `keep` (the default), `from_cbor()` accepts a
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with text string (object keys included) whose bytes are not valid UTF-8 and hands them back unchanged. Passing
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value is `error_handler_t::strict` makes `from_cbor()` check and throw
dumped. Byte strings (major type 2) are unaffected and are never validated, since they are not required to hold [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and
text. `replace`/`ignore` sanitize the string instead of keeping it. However, [`dump()`](../../api/basic_json/dump.md)
still requires valid UTF-8 and throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a
value read with the default `keep` handler, unless an error handler is passed that replaces or ignores the
ill-formed bytes. `to_cbor()`'s own [`error_handler`](../../api/basic_json/to_cbor.md) parameter defaults to `keep`,
so such a value is written back unchanged; with `strict` (the default if
[`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception
instead. Byte strings (major type 2) are unaffected, since they are not required to hold text.
!!! warning "Tagged items" !!! warning "Tagged items"
@@ -153,14 +153,23 @@ The library maps MessagePack types to JSON value types as follows:
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
on. Such input needs a general-purpose MessagePack library instead. on. Such input needs a general-purpose MessagePack library instead.
!!! warning "UTF-8 validation of string values" !!! warning "Ill-formed UTF-8 in string values"
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8. The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain
This library validates the bytes of every such string (object keys included) at decode time and rejects a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged.
ill-formed UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, This library follows that by default: with its
with `allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting [`error_handler`](../../api/basic_json/from_msgpack.md) parameter left at `keep` (the default),
value is dumped. `bin`/`ext`/`fixext` values are unaffected and are never validated, since they are not required `from_msgpack()` reads `str` bytes (object keys included) as-is, without validating them, so such a value
to hold text. round-trips through `from_msgpack(to_msgpack(j))` byte for byte. Passing `error_handler_t::strict` makes
`from_msgpack()` check anyway and throw
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and
`replace`/`ignore` sanitize the string instead of keeping it. `to_msgpack()` also writes `str` bytes as-is by
default, since the specification permits it; its [`error_handler`](../../api/basic_json/to_msgpack.md) parameter
can be set to `strict` to throw [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) instead, or
to `replace`/`ignore` to sanitize the string, for instance for a decoder that rejects ill-formed UTF-8. However,
[`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read this way with the
default `keep` handler, unless an error handler is passed that replaces or ignores the ill-formed bytes.
??? example "Example: deserialize a JSON value from MessagePack" ??? example "Example: deserialize a JSON value from MessagePack"
@@ -47,6 +47,15 @@ The library uses the following mapping from JSON values types to UBJSON types ac
- strings with more than 9223372036854775807 bytes (theoretical) - strings with more than 9223372036854775807 bytes (theoretical)
!!! warning "UTF-8 validation of string values and object keys"
UBJSON's required string encoding is UTF-8. By default (the [`error_handler`](../../api/basic_json/to_ubjson.md)
parameter left at `keep`), `to_ubjson()` writes the bytes of string values and object keys unchanged, even if they
are not valid UTF-8. With `error_handler_t::strict`, it throws
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 instead;
`replace`/`ignore` sanitize the string. [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md)
makes `strict` the default.
!!! info "Unused UBJSON markers" !!! info "Unused UBJSON markers"
The following markers are not used in the conversion: The following markers are not used in the conversion:
@@ -120,6 +129,19 @@ The library maps UBJSON types to JSON value types as follows:
The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value. The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value.
!!! warning "Ill-formed UTF-8 in string values and object keys"
UBJSON's required string encoding is UTF-8, but checking it on read is opt-in: with the
[`error_handler`](../../api/basic_json/from_ubjson.md) parameter left at `keep` (the default), `from_ubjson()`
accepts a string value or object key whose bytes are not valid UTF-8 and hands them back unchanged. Passing
`error_handler_t::strict` makes `from_ubjson()` check and throw
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and
`replace`/`ignore` sanitize the string instead of keeping it. However,
[`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read with the default
`keep` handler, unless an error handler is passed that replaces or ignores the ill-formed bytes. `to_ubjson()`'s
own `error_handler` parameter defaults to `keep` (see above), so such a value is written back unchanged.
??? example "Example: deserialize a JSON value from UBJSON" ??? example "Example: deserialize a JSON value from UBJSON"
```cpp ```cpp
+14
View File
@@ -138,6 +138,20 @@ using the library with compilers that do not fully support C++11 and may only wo
See [full documentation of `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`](../api/macros/json_skip_unsupported_compiler_check.md). See [full documentation of `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`](../api/macros/json_skip_unsupported_compiler_check.md).
## `JSON_STRICT_BINARY_UTF8`
When defined to `1`, [`to_cbor`](../api/basic_json/to_cbor.md), [`to_ubjson`](../api/basic_json/to_ubjson.md),
[`to_bjdata`](../api/basic_json/to_bjdata.md), and [`to_bson`](../api/basic_json/to_bson.md) throw
[`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) for a string value or object key that is not
valid UTF-8. The default value is `0`, which writes the bytes unchanged as before version 3.13.0; this is planned to
become the default in version 4.0.0.
The check can also be enabled with the CMake option
[`JSON_StrictBinaryUTF8`](../integration/cmake.md#json_strictbinaryutf8) (`OFF` by default) which sets
`JSON_STRICT_BINARY_UTF8` accordingly.
See [full documentation of `JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md).
## `JSON_STRICT_NUL_HANDLING` ## `JSON_STRICT_NUL_HANDLING`
When defined to `1`, a `'\0'` (NUL) byte anywhere in the input is rejected with `parse_error.101`, like any other When defined to `1`, a `'\0'` (NUL) byte anywhere in the input is rejected with `parse_error.101`, like any other
+1
View File
@@ -20,6 +20,7 @@ The complete default namespace name is derived as follows:
`_bics`. `_bics`.
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`. - [`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_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`.
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by - 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) underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
below. below.
+10 -1
View File
@@ -341,7 +341,10 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a
string was read where one was required (for instance as a map key), the string's length specification is invalid, or string was read where one was required (for instance as a map key), the string's length specification is invalid, or
the string's bytes are not valid UTF-8. the string's bytes are not valid UTF-8 and the `error_handler` parameter of the corresponding `from_*` function is
set to `strict`. By default (`error_handler_t::keep`), the bytes of a string are not checked for valid UTF-8 on read;
see the ill-formed UTF-8 notes on the individual [binary format](../features/binary_formats/index.md) pages for how
such a string is handled depending on `error_handler`.
CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other
type (for instance integers or `null`) are therefore not supported; see the notes on type (for instance integers or `null`) are therefore not supported; see the notes on
@@ -749,6 +752,12 @@ The [`unflatten()`](../api/basic_json/unflatten.md) function only works for an o
The [`dump()`](../api/basic_json/dump.md) function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. See the FAQ entry on [serializing untrusted or invalid UTF-8](faq.md#serializing-untrusted-or-invalid-utf-8) for background and the recommended fix. The [`dump()`](../api/basic_json/dump.md) function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. See the FAQ entry on [serializing untrusted or invalid UTF-8](faq.md#serializing-untrusted-or-invalid-utf-8) for background and the recommended fix.
The binary writers [`to_cbor()`](../api/basic_json/to_cbor.md), [`to_ubjson()`](../api/basic_json/to_ubjson.md),
[`to_bjdata()`](../api/basic_json/to_bjdata.md), and [`to_bson()`](../api/basic_json/to_bson.md) throw this exception
as well for a string value or object key that is not valid UTF-8 if their `error_handler` is `strict` (the default if
[`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) is enabled). So does
[`to_msgpack()`](../api/basic_json/to_msgpack.md) if `error_handler_t::strict` is passed.
!!! failure "Example message" !!! failure "Example message"
Calling `dump()` on a JSON value containing an ISO 8859-1 encoded string: Calling `dump()` on a JSON value containing an ISO 8859-1 encoded string:
+45
View File
@@ -305,6 +305,51 @@ Only very old NDKs (before r18), which defaulted to GCC and `gnustl`, lacked C++
`std::to_string`. If you run into this, update to a current NDK. `std::to_string`. If you run into this, update to a current NDK.
### Incomplete `detector` type with GCC < 11
!!! question
Why does GCC 10 or older fail with `invalid use of incomplete type 'struct nlohmann::detail::detector<..., to_json_function, ...>'` for a type that holds an `optional` member?
This happens with GCC 10 and older in C++11/C++14 mode when all of these hold:
- a class `Holder` has an `optional<Dummy>` member (e.g., `boost::optional`),
- `Dummy` has a constructor taking a `json` value, and
- `to_json` for `Holder` is a free function in the namespace of `Dummy`.
```cpp
class Dummy {
public:
explicit Dummy(const nlohmann::json& j);
};
class Holder {
boost::optional<Dummy> d;
};
void to_json(nlohmann::json& j, const Holder& h); // triggers the error
```
To decide whether `Dummy` is copyable, the compiler checks whether a `Dummy` can be converted to `json`. That check
looks up `to_json` via argument-dependent lookup, finds the unrelated `to_json` for `Holder`, and eventually asks again
whether `Dummy` is copyable. GCC before version 11 turns this cycle into a hard error; GCC 11 and later, Clang, and
C++17 mode compile the code. The same error shows up without this library whenever a constrained converting constructor
is involved, so the library can't avoid it.
To work around this, define `to_json` (and `from_json`) as a *hidden friend* inside the class. That way,
argument-dependent lookup only finds it for `Holder`:
```cpp
class Holder {
boost::optional<Dummy> d;
friend void to_json(nlohmann::json& j, const Holder& h) { /* ... */ }
};
```
The [`NLOHMANN_DEFINE_TYPE_INTRUSIVE`](../api/macros/nlohmann_define_type_intrusive.md) macros define hidden friends as
well. See [#3669](https://github.com/nlohmann/json/issues/3669) for details.
### Missing STL function ### Missing STL function
!!! question "Questions" !!! question "Questions"
+5
View File
@@ -212,6 +212,11 @@ Use the non-amalgamated version of the library. This option is `ON` by default.
Treat the library headers like system headers (i.e., adding `SYSTEM` to the [`target_include_directories`](https://cmake.org/cmake/help/latest/command/target_include_directories.html) call) to check for this library by tools like Clang-Tidy. This option is `OFF` by default. Treat the library headers like system headers (i.e., adding `SYSTEM` to the [`target_include_directories`](https://cmake.org/cmake/help/latest/command/target_include_directories.html) call) to check for this library by tools like Clang-Tidy. This option is `OFF` by default.
### `JSON_StrictBinaryUTF8`
Check string values and object keys for valid UTF-8 in the CBOR, UBJSON, BJData, and BSON writers, by defining the
macro [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md). This option is `OFF` by default.
### `JSON_StrictNulHandling` ### `JSON_StrictNulHandling`
Reject a `'\0'` (NUL) byte in the input instead of treating it as end of input, by defining the macro Reject a `'\0'` (NUL) byte in the input instead of treating it as end of input, by defining the macro
@@ -0,0 +1,149 @@
# Compile times
The library is header-only and makes heavy use of templates, so every translation unit that includes
`<nlohmann/json.hpp>` pays for parsing the header and instantiating what it uses. This page lists the options to reduce
that cost, ordered by how much they typically save.
!!! info "Measurements"
The numbers below are medians of nine runs compiling a single translation unit with `-std=c++17 -c` against the
single-header version, with Apple clang and GCC 16 on macOS (Apple silicon). They show the order of magnitude to
expect; measure your own code before and after a change.
## Include `json_fwd.hpp` in headers
Header files that only need to *name* the `json` type — for function declarations, members held by pointer or
reference, or friend declarations — can include `<nlohmann/json_fwd.hpp>` instead of `<nlohmann/json.hpp>`. It only
forward-declares `basic_json`, `json`, `ordered_json`, `json_pointer`, and `adl_serializer`. The translation units that
actually use the values then include `<nlohmann/json.hpp>`.
```cpp title="person.hpp"
#pragma once
#include <nlohmann/json_fwd.hpp>
struct person;
void to_json(nlohmann::json& j, const person& p);
void from_json(const nlohmann::json& j, person& p);
```
```cpp title="person.cpp"
#include "person.hpp"
#include <nlohmann/json.hpp>
void to_json(nlohmann::json& j, const person& p) { /* ... */ }
void from_json(const nlohmann::json& j, person& p) { /* ... */ }
```
| Compiler | `json.hpp` (`-O0`) | `json_fwd.hpp` (`-O0`) | Change |
|-------------|-------------------:|-----------------------:|-------:|
| Apple clang | 704 ms | 329 ms | −53% |
| GCC 16 | 779 ms | 242 ms | −69% |
This is the most effective option, because it avoids the full header in every translation unit that includes
*your* headers.
## Opt out of the automatic user-defined string literals
The user-defined string literals [`operator""_json`](../api/operator_literal_json.md) and
[`operator""_json_pointer`](../api/operator_literal_json_pointer.md) are ordinary inline functions whose bodies call the
parser. As `<nlohmann/json.hpp>` includes them by default, every translation unit instantiates the parser, even if it
never parses anything itself.
Define [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md) for the whole project and include
`<nlohmann/json_literals.hpp>` instead of `<nlohmann/json.hpp>` in the files that use the literals (it includes
`<nlohmann/json.hpp>` itself):
```cmake
target_compile_definitions(my_target PRIVATE JSON_NO_AUTOMATIC_UDLS)
```
```cpp
#include <nlohmann/json_literals.hpp> // only where "..."_json is used; includes <nlohmann/json.hpp>
```
The saving applies to translation units that do not parse JSON, for example ones that define types and their
conversions or only pass `json` values around:
| Compiler | Translation unit | Default (`-O0` / `-O2`) | `JSON_NO_AUTOMATIC_UDLS` (`-O0` / `-O2`) | Change |
|-------------|------------------|------------------------:|-----------------------------------------:|------------:|
| Apple clang | model | 776 ms / 846 ms | 629 ms / 692 ms | −19% / −18% |
| GCC 16 | model | 1022 ms / 1120 ms | 882 ms / 965 ms | −14% / −14% |
| Apple clang | parsing | 992 ms / 1815 ms | 1006 ms / 1823 ms | +1% / 0% |
| GCC 16 | parsing | 2018 ms / 3420 ms | 1990 ms / 3454 ms | −1% / +1% |
Translation units that include only the header save up to a third. Translation units that parse anyway instantiate
the parser regardless and see no difference.
## Instantiate `basic_json` once
Each translation unit instantiates the member functions of `nlohmann::json` it uses. An explicit instantiation
declaration tells the compiler that the non-template members are instantiated elsewhere, so it can skip them:
```cpp title="json_instance.hpp"
#pragma once
#include <nlohmann/json.hpp>
extern template class nlohmann::basic_json<>;
```
```cpp title="json_instance.cpp"
#include "json_instance.hpp"
template class nlohmann::basic_json<>;
```
Include `json_instance.hpp` instead of `<nlohmann/json.hpp>` and compile and link `json_instance.cpp` once.
| Compiler | Translation unit | Default (`-O0` / `-O2`) | `extern template` (`-O0` / `-O2`) | Change |
|-------------|---------------------|------------------------:|----------------------------------:|------------:|
| Apple clang | parsing | 992 ms / 1815 ms | 953 ms / 1625 ms | −4% / −10% |
| GCC 16 | parsing | 2018 ms / 3420 ms | 1522 ms / 2728 ms | −25% / −20% |
| Apple clang | `json_instance.cpp` | — | 2166 ms / 4660 ms | — |
| GCC 16 | `json_instance.cpp` | — | 5085 ms / 10616 ms | — |
Notes:
- The saving grows with the number of translation units that use `json`, while the instantiation translation unit is
compiled only once (and is rarely recompiled, as it does not depend on your code).
- Member function templates (such as `get<T>()`, `parse(InputType&&)`, or `value(key, default)`) are not covered by
the explicit instantiation and are still instantiated where they are used.
- The declaration covers exactly `nlohmann::json`. Add the same lines for `nlohmann::ordered_json`
(`nlohmann::basic_json<nlohmann::ordered_map>`) or your own `basic_json` specializations if you use them.
## Use C++20 modules
With a toolchain that supports named modules, `import nlohmann.json;` compiles the library once into a module and
avoids parsing the header in every translation unit. See [Modules](../features/modules.md) for requirements and known
issues. Module support is experimental and currently depends heavily on the compiler version.
## Use precompiled headers
Build systems can precompile `<nlohmann/json.hpp>` together with other stable headers, for example with CMake's
[`target_precompile_headers`](https://cmake.org/cmake/help/latest/command/target_precompile_headers.html):
```cmake
target_precompile_headers(my_target PRIVATE <nlohmann/json.hpp>)
```
This removes the cost of parsing the header, but not of instantiating templates in each translation unit, so it
combines well with the options above.
## Options without effect on compile times
Some configuration macros change what the library declares, but do not measurably change compile times:
| Macro | Apple clang, model (`-O0` / `-O2`) | GCC 16, model (`-O0` / `-O2`) |
|------------------------------------------------------------------------|-----------------------------------:|------------------------------:|
| default | 776 ms / 846 ms | 1022 ms / 1120 ms |
| [`JSON_NO_IO`](../api/macros/json_no_io.md) | 764 ms / 836 ms | 1022 ms / 1117 ms |
| [`JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global_udls.md)`=0` | 763 ms / 852 ms | 1019 ms / 1106 ms |
`JSON_USE_GLOBAL_UDLS` only controls *where* the literals are declared; to avoid their cost, use
`JSON_NO_AUTOMATIC_UDLS` instead.
## See also
- [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md) - do not include the user-defined string
literals automatically
- [Modules](../features/modules.md) - C++20 module support
- [Header only](index.md) - including the library
+1 -1
View File
@@ -45,7 +45,7 @@ Clang).
You can further use file You can further use file
[`single_include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_fwd.hpp) [`single_include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_fwd.hpp)
for forward declarations, and file for forward declarations (see [Compile times](compile_times.md)), and file
[`single_include/nlohmann/json_literals.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_literals.hpp) [`single_include/nlohmann/json_literals.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_literals.hpp)
for the user-defined string literals if you define for the user-defined string literals if you define
[`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md). [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md).
@@ -2,11 +2,14 @@
This page collects some guidelines on how to future-proof your code for future versions of this library. For how to This page collects some guidelines on how to future-proof your code for future versions of this library. For how to
add the library to your project in the first place, see [Integration](index.md), [CMake](cmake.md), or add the library to your project in the first place, see [Integration](index.md), [CMake](cmake.md), or
[Package Managers](package_managers.md). [Package Managers](package_managers.md). The [roadmap](../community/roadmap.md#version-40) lists what will change in
version 4.0, including the macros that let you try its behavior with a 3.x release; this page describes how to adjust
your code.
## Replace deprecated functions ## Replace deprecated functions
The following functions have been deprecated and will be removed in the next major version (i.e., 4.0.0). All The following functions have been deprecated and will be removed in the next major version (i.e., 4.0.0), see the
[roadmap](../community/roadmap.md#removal-of-deprecated-functions) for an overview. All
deprecations are annotated with deprecations are annotated with
[`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which [`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which
function to use instead. function to use instead.
+3
View File
@@ -108,6 +108,7 @@ nav:
- integration/cmake.md - integration/cmake.md
- integration/package_managers.md - integration/package_managers.md
- integration/pkg-config.md - integration/pkg-config.md
- integration/compile_times.md
- API Documentation: - API Documentation:
- basic_json: - basic_json:
- 'Overview': api/basic_json/index.md - 'Overview': api/basic_json/index.md
@@ -116,6 +117,7 @@ nav:
- 'accept': api/basic_json/accept.md - 'accept': api/basic_json/accept.md
- 'array': api/basic_json/array.md - 'array': api/basic_json/array.md
- 'array_t': api/basic_json/array_t.md - 'array_t': api/basic_json/array_t.md
- 'as_base_class': api/basic_json/as_base_class.md
- 'at': api/basic_json/at.md - 'at': api/basic_json/at.md
- 'back': api/basic_json/back.md - 'back': api/basic_json/back.md
- 'begin': api/basic_json/begin.md - 'begin': api/basic_json/begin.md
@@ -306,6 +308,7 @@ nav:
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md - 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md - 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
- 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md - 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md
- 'JSON_STRICT_BINARY_UTF8': api/macros/json_strict_binary_utf8.md
- 'JSON_STRICT_NUL_HANDLING': api/macros/json_strict_nul_handling.md - 'JSON_STRICT_NUL_HANDLING': api/macros/json_strict_nul_handling.md
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md - 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md - 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
+15 -4
View File
@@ -46,6 +46,10 @@
#define JSON_STRICT_NUL_HANDLING 0 #define JSON_STRICT_NUL_HANDLING 0
#endif #endif
#ifndef JSON_STRICT_BINARY_UTF8
#define JSON_STRICT_BINARY_UTF8 0
#endif
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else #else
@@ -82,14 +86,20 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING #define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
#endif #endif
#if JSON_STRICT_BINARY_UTF8
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8 _sbu8
#else
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif #endif
// Construct the namespace ABI tags component // Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f #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) \ #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) NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS \ #define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -98,7 +108,8 @@
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \ NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \ NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING) NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
// Construct the namespace version component // Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
@@ -27,7 +27,6 @@
#include <nlohmann/detail/meta/identity_tag.hpp> #include <nlohmann/detail/meta/identity_tag.hpp>
#include <nlohmann/detail/meta/std_fs.hpp> #include <nlohmann/detail/meta/std_fs.hpp>
#include <nlohmann/detail/meta/type_traits.hpp> #include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/meta/logic.hpp>
#include <nlohmann/detail/string_concat.hpp> #include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/value_t.hpp> #include <nlohmann/detail/value_t.hpp>
@@ -211,62 +210,29 @@ inline void from_json(const BasicJsonType& j, std::valarray<T>& l)
}); });
} }
// element is not itself a C array: read it directly
template<typename BasicJsonType, typename T>
auto from_json_c_array_element(const BasicJsonType& j, T& e)
-> decltype(e = j.template get<T>(), void())
{
e = j.template get<T>();
}
// element is itself a C array: recurse one dimension at a time, so any rank is supported
template<typename BasicJsonType, typename T, std::size_t N> template<typename BasicJsonType, typename T, std::size_t N>
auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) void from_json_c_array_element(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
-> decltype(j.template get<T>(), void())
{ {
for (std::size_t i = 0; i < N; ++i) for (std::size_t i = 0; i < N; ++i)
{ {
arr[i] = j.at(i).template get<T>(); from_json_c_array_element(j.at(i), arr[i]);
} }
} }
template<typename BasicJsonType, typename T, std::size_t N1, std::size_t N2> template<typename BasicJsonType, typename T, std::size_t N>
auto from_json(const BasicJsonType& j, T (&arr)[N1][N2]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
-> decltype(j.template get<T>(), void()) -> decltype(j.template get<typename std::remove_all_extents<T>::type>(), void())
{ {
for (std::size_t i1 = 0; i1 < N1; ++i1) from_json_c_array_element(j, arr);
{
for (std::size_t i2 = 0; i2 < N2; ++i2)
{
arr[i1][i2] = j.at(i1).at(i2).template get<T>();
}
}
}
template<typename BasicJsonType, typename T, std::size_t N1, std::size_t N2, std::size_t N3>
auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
-> decltype(j.template get<T>(), void())
{
for (std::size_t i1 = 0; i1 < N1; ++i1)
{
for (std::size_t i2 = 0; i2 < N2; ++i2)
{
for (std::size_t i3 = 0; i3 < N3; ++i3)
{
arr[i1][i2][i3] = j.at(i1).at(i2).at(i3).template get<T>();
}
}
}
}
template<typename BasicJsonType, typename T, std::size_t N1, std::size_t N2, std::size_t N3, std::size_t N4>
auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3][N4]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
-> decltype(j.template get<T>(), void())
{
for (std::size_t i1 = 0; i1 < N1; ++i1)
{
for (std::size_t i2 = 0; i2 < N2; ++i2)
{
for (std::size_t i3 = 0; i3 < N3; ++i3)
{
for (std::size_t i4 = 0; i4 < N4; ++i4)
{
arr[i1][i2][i3][i4] = j.at(i1).at(i2).at(i3).at(i4).template get<T>();
}
}
}
}
} }
template<typename BasicJsonType> template<typename BasicJsonType>
@@ -286,20 +252,33 @@ auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
} }
} }
// reserve() is called through this pair (modeled on from_json_object_reserve)
// so from_json_array_impl below has a single body for both ConstructibleArrayType
// that support reserve() and those that don't.
template<typename ConstructibleArrayType>
auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/)
-> decltype(arr.reserve(size), void())
{
arr.reserve(size);
}
template<typename ConstructibleArrayType>
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
template<typename BasicJsonType, typename ConstructibleArrayType, template<typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t< enable_if_t<
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value, std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
int> = 0> int> = 0>
auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/)
-> decltype( -> decltype(
arr.reserve(std::declval<typename ConstructibleArrayType::size_type>()),
j.template get<typename ConstructibleArrayType::value_type>(), j.template get<typename ConstructibleArrayType::value_type>(),
void()) void())
{ {
using std::end; using std::end;
ConstructibleArrayType ret; ConstructibleArrayType ret;
ret.reserve(j.size()); from_json_array_reserve(ret, j.size(), priority_tag<1> {});
std::transform(j.begin(), j.end(), std::transform(j.begin(), j.end(),
std::inserter(ret, end(ret)), [](const BasicJsonType & i) std::inserter(ret, end(ret)), [](const BasicJsonType & i)
{ {
@@ -310,27 +289,6 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
arr = std::move(ret); arr = std::move(ret);
} }
template<typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t<
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
int> = 0>
inline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr,
priority_tag<0> /*unused*/)
{
using std::end;
ConstructibleArrayType ret;
std::transform(
j.begin(), j.end(), std::inserter(ret, end(ret)),
[](const BasicJsonType & i)
{
// get<BasicJsonType>() returns *this, this won't call a from_json
// method when value_type is BasicJsonType
return i.template get<typename ConstructibleArrayType::value_type>();
});
arr = std::move(ret);
}
template < typename BasicJsonType, typename ConstructibleArrayType, template < typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t < enable_if_t <
is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value&& is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value&&
@@ -433,9 +391,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
} }
// overload for arithmetic types, not chosen for basic_json template arguments // overload for arithmetic types, not chosen for basic_json template arguments
// (BooleanType, etc.); note: Is it really necessary to provide explicit // (BooleanType, etc.)
// overloads for boolean_t etc. in case of a custom BooleanType which is not
// an arithmetic type?
template < typename BasicJsonType, typename ArithmeticType, template < typename BasicJsonType, typename ArithmeticType,
enable_if_t < enable_if_t <
std::is_arithmetic<ArithmeticType>::value&& std::is_arithmetic<ArithmeticType>::value&&
@@ -531,7 +487,7 @@ inline void from_json_tuple_impl(const BasicJsonType& j, std::pair<A1, A2>& p, p
template<typename BasicJsonType, typename... Args> template<typename BasicJsonType, typename... Args>
std::tuple<Args...> from_json_tuple_impl(const BasicJsonType& j, identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/) std::tuple<Args...> from_json_tuple_impl(const BasicJsonType& j, identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/)
{ {
static_assert(cxpr_and<cxpr_or<cxpr_not<std::is_reference<Args>>, is_compatible_reference_type<const BasicJsonType&, Args>>...>::value, static_assert(conjunction<disjunction<negation<std::is_reference<Args>>, is_compatible_reference_type<const BasicJsonType&, Args>>...>::value,
"Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()");
return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for<Args...> {}); return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for<Args...> {});
} }
@@ -554,10 +510,10 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t)
return from_json_tuple_impl(j, std::forward<TupleRelated>(t), priority_tag<3> {}); return from_json_tuple_impl(j, std::forward<TupleRelated>(t), priority_tag<3> {});
} }
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, // shared body for std::map/std::unordered_map with a non-string Key: both
typename = enable_if_t < !std::is_constructible < // containers are read from an array of [key, value] pairs the same way
typename BasicJsonType::string_t, Key >::value >> template<typename BasicJsonType, typename MapType>
inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m) void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{ {
@@ -570,33 +526,29 @@ inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allo
{ {
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p));
} }
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>()); m.emplace(p.at(0).template get<typename MapType::key_type>(), p.at(1).template get<typename MapType::mapped_type>());
} }
} }
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)
{
from_json_pair_array_to_map(j, m);
}
template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator,
typename = enable_if_t < !std::is_constructible < typename = enable_if_t < !std::is_constructible <
typename BasicJsonType::string_t, Key >::value >> typename BasicJsonType::string_t, Key >::value >>
inline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m) void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
{ {
if (JSON_HEDLEY_UNLIKELY(!j.is_array())) from_json_pair_array_to_map(j, m);
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
}
m.clear();
for (const auto& p : j)
{
if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p));
}
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
}
} }
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
// Workaround for MSVC 19.51 (and possibly later): in large in large cpp files, the compiler may fail to resolve with generic has_from_json (issue #4996) // Workaround for MSVC 19.51 (and possibly later): in large cpp files, the compiler may fail to resolve with generic has_from_json (issue #4996)
template<typename BasicJsonType> template<typename BasicJsonType>
struct has_from_json<BasicJsonType, std_fs::path, void> : std::true_type {}; struct has_from_json<BasicJsonType, std_fs::path, void> : std::true_type {};
@@ -178,7 +178,7 @@ struct external_constructor<value_t::array>
template < typename BasicJsonType, typename CompatibleArrayType, template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__) #if JSON_HAS_RANGE_VIEW_CONVERSION
&& !is_compatible_range_view<CompatibleArrayType>::value && !is_compatible_range_view<CompatibleArrayType>::value
#endif #endif
, int > = 0 > , int > = 0 >
@@ -222,9 +222,7 @@ struct external_constructor<value_t::array>
j.assert_invariant(); j.assert_invariant();
} }
// std::ranges does not work properly on MinGW due to incomplete C++20 support #if JSON_HAS_RANGE_VIEW_CONVERSION
// see https://github.com/nlohmann/json/issues/4916
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template<typename BasicJsonType, typename CompatibleArrayType, template<typename BasicJsonType, typename CompatibleArrayType,
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0> enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr) static void construct(BasicJsonType& j, CompatibleArrayType && arr)
@@ -294,7 +292,9 @@ void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_not
{ {
if (opt.has_value()) if (opt.has_value())
{ {
j = *opt; // explicit construction, as the conversion from a basic_json with a different
// string type is explicit if JSON_USE_IMPLICIT_CONVERSIONS is 0 (#2649)
j = BasicJsonType(*opt);
} }
else else
{ {
@@ -382,7 +382,7 @@ template < typename BasicJsonType, typename CompatibleArrayType,
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&& !std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
!is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value&& !is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value&&
!is_basic_json<CompatibleArrayType>::value !is_basic_json<CompatibleArrayType>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__) #if JSON_HAS_RANGE_VIEW_CONVERSION
&& !is_compatible_range_view<CompatibleArrayType>::value && !is_compatible_range_view<CompatibleArrayType>::value
#endif #endif
, ,
@@ -392,7 +392,7 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
external_constructor<value_t::array>::construct(j, arr); external_constructor<value_t::array>::construct(j, arr);
} }
#if JSON_HAS_RANGES && !defined(__MINGW32__) #if JSON_HAS_RANGE_VIEW_CONVERSION
template < typename BasicJsonType, typename T, template < typename BasicJsonType, typename T,
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
&& !is_compatible_string_type<BasicJsonType, std::remove_cvref_t<T>>::value && !is_compatible_string_type<BasicJsonType, std::remove_cvref_t<T>>::value
+21
View File
@@ -286,6 +286,27 @@ class other_error : public exception
other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} other_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
}; };
/*!
@brief helper function to call JSON_THROW from a template
@note JSON_THROW is a macro that, depending on the JSON_THROW_USER /
JSON_TRY_USER / JSON_NOEXCEPTION configuration, may expand to code
that does not reference its argument (e.g. `std::abort()`), which
would trigger a compilation error if the argument's type depends on
a template parameter that is otherwise unused. Wrapping the call in
a templated function avoids this and gives the compiler a single
place to see the (possibly unused) parameter.
*/
template<typename ExceptionType>
void templated_json_throw(ExceptionType exception)
{
JSON_THROW(exception);
// JSON_THROW may expand to code that discards its argument (e.g. when
// exceptions are disabled) - the cast below avoids an unused-parameter
// warning with -Werror in that case
(void)exception;
}
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+77 -42
View File
@@ -31,6 +31,7 @@
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/is_sax.hpp> #include <nlohmann/detail/meta/is_sax.hpp>
#include <nlohmann/detail/meta/type_traits.hpp> #include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/output/error_handler.hpp>
#include <nlohmann/detail/string_concat.hpp> #include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/string_utils.hpp> #include <nlohmann/detail/string_utils.hpp>
#include <nlohmann/detail/value_t.hpp> #include <nlohmann/detail/value_t.hpp>
@@ -108,8 +109,16 @@ class binary_reader
@brief create a binary reader @brief create a binary reader
@param[in] adapter input adapter to read from @param[in] adapter input adapter to read from
@param[in] format the binary format to parse
@param[in] error_handler_ how to treat text strings and object keys that
are not well-formed UTF-8; none of the supported formats
requires a decoder to reject those, so the default is to
@ref error_handler_t::keep them unchanged, as every binary
reader did before this parameter existed
*/ */
explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json) noexcept : ia(std::move(adapter)), input_format(format) explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json,
const error_handler_t error_handler_ = error_handler_t::keep) noexcept
: ia(std::move(adapter)), input_format(format), error_handler(error_handler_)
{ {
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {}; (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
} }
@@ -428,7 +437,7 @@ class binary_reader
{ {
if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {})) if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {}))
{ {
return true; return check_string_utf8(result, "key");
} }
auto out = std::back_inserter(result); auto out = std::back_inserter(result);
@@ -441,7 +450,7 @@ class binary_reader
} }
if (current == 0x00) if (current == 0x00)
{ {
return true; return check_string_utf8(result, "key");
} }
*out++ = static_cast<typename string_t::value_type>(current); *out++ = static_cast<typename string_t::value_type>(current);
} }
@@ -522,7 +531,7 @@ class binary_reader
"string"), nullptr)); "string"), nullptr));
} }
return true; return check_string_utf8(result, "string");
} }
/*! /*!
@@ -1149,7 +1158,7 @@ class binary_reader
@return whether string creation completed @return whether string creation completed
*/ */
bool get_cbor_string(string_t& result) bool get_cbor_string(string_t& result, const char* context = "string")
{ {
// number of indefinite-length strings that have been opened and not // number of indefinite-length strings that have been opened and not
// closed yet. RFC 8949, Section 3.2.3 does not permit nesting them, // closed yet. RFC 8949, Section 3.2.3 does not permit nesting them,
@@ -1179,7 +1188,7 @@ class binary_reader
{ {
if (--open == 0) if (--open == 0)
{ {
return true; return check_string_utf8(result, context);
} }
get(); get();
continue; continue;
@@ -1192,7 +1201,7 @@ class binary_reader
if (open == 0) if (open == 0)
{ {
return true; return check_string_utf8(result, context);
} }
get(); get();
@@ -1216,7 +1225,7 @@ class binary_reader
// EOF and major type 3 (text string) are left to get_cbor_string // EOF and major type 3 (text string) are left to get_cbor_string
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u) if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
{ {
return get_cbor_string(result); return get_cbor_string(result, "key");
} }
const char* found = nullptr; const char* found = nullptr;
@@ -2004,7 +2013,7 @@ class binary_reader
@return whether string creation completed @return whether string creation completed
*/ */
bool get_msgpack_string(string_t& result) bool get_msgpack_string(string_t& result, const char* context = "string")
{ {
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string")))
{ {
@@ -2047,25 +2056,25 @@ class binary_reader
case 0xBE: case 0xBE:
case 0xBF: case 0xBF:
{ {
return get_string(input_format_t::msgpack, static_cast<unsigned int>(current) & 0x1Fu, result); return get_string(input_format_t::msgpack, static_cast<unsigned int>(current) & 0x1Fu, result) && check_string_utf8(result, context);
} }
case 0xD9: // str 8 case 0xD9: // str 8
{ {
std::uint8_t len{}; std::uint8_t len{};
return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context);
} }
case 0xDA: // str 16 case 0xDA: // str 16
{ {
std::uint16_t len{}; std::uint16_t len{};
return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context);
} }
case 0xDB: // str 32 case 0xDB: // str 32
{ {
std::uint32_t len{}; std::uint32_t len{};
return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context);
} }
default: default:
@@ -2143,7 +2152,7 @@ class binary_reader
// byte 0xC1 are left to get_msgpack_string // byte 0xC1 are left to get_msgpack_string
if (current == char_traits<char_type>::eof()) if (current == char_traits<char_type>::eof())
{ {
return get_msgpack_string(result); return get_msgpack_string(result, "key");
} }
if (current <= 0x7F || current >= 0xE0) if (current <= 0x7F || current >= 0xE0)
{ {
@@ -2159,7 +2168,7 @@ class binary_reader
} }
else else
{ {
return get_msgpack_string(result); return get_msgpack_string(result, "key");
} }
break; break;
} }
@@ -2405,7 +2414,7 @@ class binary_reader
if (top.is_object) if (top.is_object)
{ {
key.clear(); key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, true, "key") || !sax->key(key)))
{ {
return false; return false;
} }
@@ -2427,7 +2436,7 @@ class binary_reader
if (top.is_object) if (top.is_object)
{ {
key.clear(); key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false, "key") || !sax->key(key)))
{ {
return false; return false;
} }
@@ -2495,7 +2504,7 @@ class binary_reader
@return whether string creation completed @return whether string creation completed
*/ */
bool get_ubjson_string(string_t& result, const bool get_char = true) bool get_ubjson_string(string_t& result, const bool get_char = true, const char* context = "string")
{ {
if (get_char) if (get_char)
{ {
@@ -2516,31 +2525,31 @@ class binary_reader
case 'U': case 'U':
{ {
std::uint8_t len{}; std::uint8_t len{};
return get_number(input_format, len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context);
} }
case 'i': case 'i':
{ {
std::int8_t len{}; std::int8_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context);
} }
case 'I': case 'I':
{ {
std::int16_t len{}; std::int16_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context);
} }
case 'l': case 'l':
{ {
std::int32_t len{}; std::int32_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context);
} }
case 'L': case 'L':
{ {
std::int64_t len{}; std::int64_t len{};
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context);
} }
case 'u': case 'u':
@@ -2550,7 +2559,7 @@ class binary_reader
break; break;
} }
std::uint16_t len{}; std::uint16_t len{};
return get_number(input_format, len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context);
} }
case 'm': case 'm':
@@ -2560,7 +2569,7 @@ class binary_reader
break; break;
} }
std::uint32_t len{}; std::uint32_t len{};
return get_number(input_format, len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context);
} }
case 'M': case 'M':
@@ -2570,7 +2579,7 @@ class binary_reader
break; break;
} }
std::uint64_t len{}; std::uint64_t len{};
return get_number(input_format, len) && get_string(input_format, len, result); return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context);
} }
default: default:
@@ -4044,27 +4053,50 @@ class binary_reader
const NumberType len, const NumberType len,
string_t& result) string_t& result)
{ {
// get_bytes() appends to result, and CBOR indefinite-length strings // Strings are taken as is by default: none of CBOR (RFC 8949 §3.1
// collect all their chunks in the same result; validating only the // leaves the choice to the decoder), MessagePack (whose spec
// newly read bytes keeps the check linear in the input size // explicitly allows a str object to contain an invalid byte
const std::size_t old_size = result.size(); // sequence), UBJSON, BJData, or BSON requires a decoder to reject
if (JSON_HEDLEY_UNLIKELY(!get_bytes(format, len, "string", result))) // ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
{ // rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
return false; // @ref error_handler, applied once the whole string (all chunks of
// an indefinite-length CBOR string included) has been assembled, by
// @ref check_string_utf8 at the call site.
return get_bytes(format, len, "string", result);
} }
// RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications /*!
// all require text strings to be valid UTF-8; reject anything else @brief validate a decoded text string (value or object key) against @ref error_handler
// right here so malformed input is caught at decode time instead of
// only surfacing later as a type_error.316 when the value is dumped None of the binary formats requires a decoder to reject ill-formed UTF-8
// (which would defeat allow_exceptions=false / strict discarding). in a text string (see @ref get_string), so by default
if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size))) (@ref error_handler_t::keep) this does nothing. A stricter
@ref error_handler opts into the same well-formedness check @ref
serializer::dump_escaped_impl applies when dumping a string:
@ref error_handler_t::strict rejects ill-formed input with
parse_error.113 (honoring `allow_exceptions` via @a sax), while
@ref error_handler_t::replace / @ref error_handler_t::ignore sanitize
@a result in place, using the exact same rules.
@param[in,out] result the already assembled string to check
@param[in] context further context information (for diagnostics)
@return whether @a result is acceptable (always true for `keep`)
*/
bool check_string_utf8(string_t& result, const char* context)
{ {
return sax->parse_error(chars_read, get_token_string(), if (error_handler == error_handler_t::keep || is_valid_utf8(result))
parse_error::create(113, chars_read, {
exception_message(format, "invalid string: ill-formed UTF-8 byte", "string"), nullptr)); return true;
} }
if (error_handler == error_handler_t::strict)
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(input_format, "invalid string: ill-formed UTF-8 byte", context), nullptr));
}
result = sanitize_utf8(result, error_handler);
return true; return true;
} }
@@ -4241,6 +4273,9 @@ class binary_reader
/// input format /// input format
const input_format_t input_format = input_format_t::json; const input_format_t input_format = input_format_t::json;
/// how to treat text strings/object keys that are not well-formed UTF-8
const error_handler_t error_handler = error_handler_t::keep;
/// the SAX parser /// the SAX parser
json_sax_t* sax = nullptr; json_sax_t* sax = nullptr;
@@ -453,8 +453,10 @@ struct wide_string_input_helper<BaseInputAdapter, 4>
} }
else else
{ {
// get the current character // get the current character; converted to an unsigned type so that
const auto wc = input.get_character(); // a negative unit (wint_t is signed on some platforms) is not
// mistaken for an ASCII character or for EOF
const auto wc = static_cast<std::uint32_t>(input.get_character());
if (wc <= 0x10FFFF) if (wc <= 0x10FFFF)
{ {
@@ -522,9 +524,11 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
bool valid_pair = false; bool valid_pair = false;
if (wc <= 0xDBFF && JSON_HEDLEY_UNLIKELY(!input.empty())) if (wc <= 0xDBFF && JSON_HEDLEY_UNLIKELY(!input.empty()))
{ {
const auto wc2 = static_cast<unsigned int>(input.get_character()); // only consume the next unit if it completes the pair
const auto wc2 = static_cast<unsigned int>(*input.current);
if (0xDC00 <= wc2 && wc2 <= 0xDFFF) if (0xDC00 <= wc2 && wc2 <= 0xDFFF)
{ {
input.get_character();
const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));
utf8_bytes_filled = 0; utf8_bytes_filled = 0;
encode_utf8(charcode, [&utf8_bytes, &utf8_bytes_filled](std::uint32_t byte) encode_utf8(charcode, [&utf8_bytes, &utf8_bytes_filled](std::uint32_t byte)
@@ -537,7 +541,8 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
if (!valid_pair) if (!valid_pair)
{ {
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc); // emit a byte that is never valid UTF-8 (see the UTF-32 case)
utf8_bytes[0] = 0xFF;
utf8_bytes_filled = 1; utf8_bytes_filled = 1;
} }
} }
@@ -746,7 +751,7 @@ struct container_input_adapter_factory< ContainerType,
{ {
// container is forwarded twice on purpose: the resulting begin/end // container is forwarded twice on purpose: the resulting begin/end
// iterator types must match adapter_type, computed the same way // iterator types must match adapter_type, computed the same way
// NOLINTNEXTLINE(bugprone-use-after-move) // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved)
return input_adapter(begin(std::forward<ContainerType>(container)), end(std::forward<ContainerType>(container))); return input_adapter(begin(std::forward<ContainerType>(container)), end(std::forward<ContainerType>(container)));
} }
}; };
+6 -1
View File
@@ -941,10 +941,15 @@ class lexer : public lexer_base<BasicJsonType>
case '\n': case '\n':
case '\r': case '\r':
case char_traits<char_type>::eof(): case char_traits<char_type>::eof():
return true;
#if !JSON_STRICT_NUL_HANDLING #if !JSON_STRICT_NUL_HANDLING
case '\0': case '\0':
#endif // a NUL byte is the end of the input (see scan()),
// so leave it for scan() to see
unget();
return true; return true;
#endif
default: default:
break; break;
@@ -60,9 +60,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value, static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,
"iter_impl only accepts (const) basic_json"); "iter_impl only accepts (const) basic_json");
// superficial check for the LegacyBidirectionalIterator named requirement // superficial check for the LegacyBidirectionalIterator named requirement
static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value // note: only array_t::iterator is checked here; object_t::iterator may be
&& std::is_base_of<std::bidirectional_iterator_tag, typename std::iterator_traits<typename array_t::iterator>::iterator_category>::value, // a forward-only iterator as long as reverse iteration and operator--
"basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); // are never used on it
static_assert(std::is_base_of<std::bidirectional_iterator_tag, typename std::iterator_traits<typename array_t::iterator>::iterator_category>::value,
"basic_json iterator assumes array type iterators satisfy the LegacyBidirectionalIterator named requirement.");
public: public:
/// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.
+107 -131
View File
@@ -240,6 +240,72 @@ class json_pointer
} }
private: private:
/*!
@brief result of @ref parse_array_index
@ref array_index maps each value to the corresponding parse_error/out_of_range
exception; @ref contains and @ref get_checked_or_null, which must not throw for
an out-of-range or unrepresentable index, switch on it directly instead.
*/
enum class array_index_status
{
ok, ///< @a s is a valid, representable array index
leading_zero, ///< @a s begins with '0' but has more than one character
not_a_number, ///< @a s does not begin with a digit
unresolved, ///< @a s could not be converted to an integer
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
};
/*!
@param[in] s reference token to be converted into an array index
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
is returned; left unchanged otherwise
@return whether @a s is a valid array index, and if not, why
@note this function never throws; @ref array_index and the callers that must not
throw (@ref contains, @ref get_checked_or_null) build on it instead of each
re-implementing the RFC 6901 digit rules and the @a size_type range check
*/
template<typename BasicJsonType>
static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept
{
using size_type = typename BasicJsonType::size_type;
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
{
return array_index_status::leading_zero;
}
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
{
return array_index_status::not_a_number;
}
const char* p = s.data();
char* p_end = nullptr; // NOLINT(misc-const-correctness)
errno = 0; // strtoull doesn't reset errno
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
if (p == p_end // invalid input or empty string
|| errno == ERANGE // out of range
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
{
return array_index_status::unresolved;
}
// the index does not fit into size_type; on 64-bit platforms this is
// only SIZE_MAX itself (see #2203 and #5395)
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
{
return array_index_status::exceeds_size_type;
}
idx = static_cast<size_type>(res);
return array_index_status::ok;
}
/*! /*!
@param[in] s reference token to be converted into an array index @param[in] s reference token to be converted into an array index
@@ -253,39 +319,25 @@ class json_pointer
template<typename BasicJsonType> template<typename BasicJsonType>
static typename BasicJsonType::size_type array_index(const string_t& s) static typename BasicJsonType::size_type array_index(const string_t& s)
{ {
using size_type = typename BasicJsonType::size_type; typename BasicJsonType::size_type idx{};
switch (parse_array_index<BasicJsonType>(s, idx))
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
{ {
// the branches differ in their messages, not after JSON_THROW's expansion
// NOLINTNEXTLINE(bugprone-branch-clone)
case array_index_status::leading_zero:
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
} case array_index_status::not_a_number:
// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
{
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
} case array_index_status::unresolved:
const char* p = s.data();
char* p_end = nullptr; // NOLINT(misc-const-correctness)
errno = 0; // strtoull doesn't reset errno
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
if (p == p_end // invalid input or empty string
|| errno == ERANGE // out of range
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
{
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
} case array_index_status::exceeds_size_type:
// the index does not fit into size_type; on 64-bit platforms this is
// only SIZE_MAX itself (see #2203 and #5395)
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
{
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr));
case array_index_status::ok:
default:
break;
} }
return static_cast<size_type>(res); return idx;
} }
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
@@ -536,6 +588,10 @@ class json_pointer
@return const reference to the JSON value pointed to by the JSON @return const reference to the JSON value pointed to by the JSON
pointer pointer
@pre Every object key and array index the pointer refers to exists.
Like the const operator[] for keys and indices, a missing one is
undefined behavior, guarded by a runtime assertion.
@throw parse_error.106 if an array index begins with '0' @throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number @throw parse_error.109 if an array index was not a number
@throw out_of_range.402 if the array index '-' is used @throw out_of_range.402 if the array index '-' is used
@@ -550,7 +606,8 @@ class json_pointer
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
// use unchecked object access // use unchecked object access; the const operator[]
// asserts that the key exists
ptr = &ptr->operator[](reference_token); ptr = &ptr->operator[](reference_token);
break; break;
} }
@@ -563,7 +620,8 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
} }
// use unchecked array access // use unchecked array access; the const operator[]
// asserts that the index exists
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token)); ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
break; break;
} }
@@ -584,63 +642,6 @@ class json_pointer
return *ptr; return *ptr;
} }
/*!
@throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number
@throw out_of_range.402 if the array index '-' is used
@throw out_of_range.404 if the JSON pointer can not be resolved
*/
template<typename BasicJsonType>
const BasicJsonType& get_checked(const BasicJsonType* ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->type())
{
case detail::value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case detail::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
JSON_THROW(detail::out_of_range::create(402, detail::concat(
"array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
") is out of range"), ptr));
}
const auto idx = array_index<BasicJsonType>(reference_token);
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{
JSON_THROW(detail::out_of_range::create(401, detail::concat(
"array index ", std::to_string(idx), " is out of range"), ptr));
}
ptr = &ptr->operator[](idx);
break;
}
case detail::value_t::null:
case detail::value_t::string:
case detail::value_t::boolean:
case detail::value_t::number_integer:
case detail::value_t::number_unsigned:
case detail::value_t::number_float:
case detail::value_t::binary:
case detail::value_t::discarded:
default:
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
}
}
return *ptr;
}
/*! /*!
@brief return a pointer to the pointed to value, or `nullptr` if the @brief return a pointer to the pointed to value, or `nullptr` if the
pointer cannot be resolved because a key is missing, an array pointer cannot be resolved because a key is missing, an array
@@ -679,18 +680,25 @@ class json_pointer
return nullptr; return nullptr;
} }
// may throw parse_error.106/109 for a malformed index; an // a malformed index throws parse_error.106/109; an
// index that is syntactically valid but cannot be // index that is syntactically valid but cannot be
// represented (out_of_range.404/410) is treated like an // represented (out_of_range.404/410) is treated like an
// out-of-range index below // out-of-range index below
typename BasicJsonType::size_type idx{}; typename BasicJsonType::size_type idx{};
JSON_TRY switch (parse_array_index<BasicJsonType>(reference_token, idx))
{
idx = array_index<BasicJsonType>(reference_token);
}
JSON_INTERNAL_CATCH (detail::out_of_range&)
{ {
// the branches differ in their messages, not after JSON_THROW's expansion
// NOLINTNEXTLINE(bugprone-branch-clone)
case array_index_status::leading_zero:
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
case array_index_status::not_a_number:
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
case array_index_status::unresolved:
case array_index_status::exceeds_size_type:
return nullptr; return nullptr;
case array_index_status::ok:
default:
break;
} }
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
@@ -718,8 +726,8 @@ class json_pointer
} }
/*! /*!
@throw parse_error.106 if an array index begins with '0' @note unlike array_index(), this never throws: a malformed or unrepresentable
@throw parse_error.109 if an array index was not a number array index reference token is treated like a missing key (see #5395)
*/ */
template<typename BasicJsonType> template<typename BasicJsonType>
bool contains(const BasicJsonType* ptr) const bool contains(const BasicJsonType* ptr) const
@@ -747,49 +755,17 @@ class json_pointer
// "-" always fails the range check // "-" always fails the range check
return false; return false;
} }
if (JSON_HEDLEY_UNLIKELY(reference_token.empty()))
{
// an empty reference token is not an array index; array_index()
// would throw out_of_range.404 -- contains() must not throw (see #5395)
return false;
}
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !('0' <= reference_token[0] && reference_token[0] <= '9')))
{
// invalid char
return false;
}
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))
{
if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9')))
{
// the first char should be between '1' and '9'
return false;
}
for (std::size_t i = 1; i < reference_token.size(); i++)
{
if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9')))
{
// other char should be between '0' and '9'
return false;
}
}
}
// the reference token consists only of digits at this point (cf. checks // any parse failure (malformed index, or one that is syntactically
// above); however, its numeric value might not be representable, in which // valid but not representable as size_type) means the reference
// case array_index() would throw out_of_range.404/410 -- contains() must // token cannot denote an existing array element -- contains() must
// not throw (see #5395), so such a reference token is treated as "not found" // not throw (see #5395), so it is treated as "not found"
errno = 0; // strtoull() does not reset errno on success typename BasicJsonType::size_type idx{};
char* p_end = nullptr; // NOLINT(misc-const-correctness) if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(reference_token, idx) != array_index_status::ok))
const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int)
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
{ {
// the array index cannot be represented as size_type
return false; return false;
} }
const auto idx = array_index<BasicJsonType>(reference_token);
if (idx >= ptr->size()) if (idx >= ptr->size())
{ {
// index out of range // index out of range
+17 -43
View File
@@ -9,7 +9,6 @@
#pragma once #pragma once
#include <utility> // declval, pair #include <utility> // declval, pair
#include <nlohmann/detail/meta/detected.hpp>
#include <nlohmann/thirdparty/hedley/hedley.hpp> #include <nlohmann/thirdparty/hedley/hedley.hpp>
// This file contains all internal macro definitions (except those affecting ABI) // This file contains all internal macro definitions (except those affecting ABI)
@@ -140,10 +139,12 @@
// libstdc++ < 11 has incomplete C++20 ranges (issue #4440) // libstdc++ < 11 has incomplete C++20 ranges (issue #4440)
#elif defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11 #elif defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11
#define JSON_HAS_RANGES 0 #define JSON_HAS_RANGES 0
// libc++ < 16 has incomplete C++20 ranges (issue #4440) // clang < 16 with libstdc++ does not implement the ranges customization
// points libstdc++ declares, so its C++20 ranges support is incomplete (issue #5161)
#elif defined(__clang__) && !defined(__apple_build_version__) \ #elif defined(__clang__) && !defined(__apple_build_version__) \
&& __clang_major__ < 16 && defined(__GLIBCXX__) && __clang_major__ < 16 && defined(__GLIBCXX__)
#define JSON_HAS_RANGES 0 #define JSON_HAS_RANGES 0
// libc++ < 16 has incomplete C++20 ranges (issue #4440)
#elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000 #elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000
#define JSON_HAS_RANGES 0 #define JSON_HAS_RANGES 0
// nvcc CUDA 12.0/12.1 chokes on the enable_borrowed_range variable-template // nvcc CUDA 12.0/12.1 chokes on the enable_borrowed_range variable-template
@@ -158,6 +159,18 @@
#endif #endif
#endif #endif
// std::ranges view conversion (to_json/is_compatible_array_type_impl) additionally
// needs to be disabled on MinGW, whose std::ranges support is incomplete
// (issue #4916); this macro combines both conditions so the check and its
// reason are not duplicated at every use site.
#ifndef JSON_HAS_RANGE_VIEW_CONVERSION
#if JSON_HAS_RANGES && !defined(__MINGW32__)
#define JSON_HAS_RANGE_VIEW_CONVERSION 1
#else
#define JSON_HAS_RANGE_VIEW_CONVERSION 0
#endif
#endif
#ifndef JSON_HAS_STD_FORMAT #ifndef JSON_HAS_STD_FORMAT
#if defined(JSON_HAS_CPP_20) && defined(__cpp_lib_format) #if defined(JSON_HAS_CPP_20) && defined(__cpp_lib_format)
#define JSON_HAS_STD_FORMAT 1 #define JSON_HAS_STD_FORMAT 1
@@ -279,21 +292,6 @@
/*!
@brief function to wrap JSON_THROW_MACRO - there can be compilation errors about
there being no arguments to JSON_THROW that depend on template arguments
if this is not used to call JSON_THROW
*/
template<typename ExceptionType>
void templated_json_throw(ExceptionType exception)
{
JSON_THROW(exception);
/* JSON_THROW(exception) discards exception and aborts - void cast needed to supress
compilation error if compiled with -Werror and Wunused-parameter */
(void)exception;
}
/*! /*!
@brief macro to briefly define a mapping between an enum and JSON with exception @brief macro to briefly define a mapping between an enum and JSON with exception
on invalid input on invalid input
@@ -314,7 +312,7 @@ void templated_json_throw(ExceptionType exception)
return ej_pair.first == e; \ return ej_pair.first == e; \
}); \ }); \
if (it != std::end(m)) j = it->second; \ if (it != std::end(m)) j = it->second; \
else templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \
} \ } \
template<typename BasicJsonType> \ template<typename BasicJsonType> \
inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
@@ -329,7 +327,7 @@ void templated_json_throw(ExceptionType exception)
return ej_pair.second == j; \ return ej_pair.second == j; \
}); \ }); \
if (it != std::end(m)) e = it->first; \ if (it != std::end(m)) e = it->first; \
else templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \
} }
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They // Ugly macros to avoid uglier copy-paste when specializing basic_json. They
@@ -874,30 +872,6 @@ void templated_json_throw(ExceptionType exception)
\ \
template<typename... T> \ template<typename... T> \
using result_of_##std_name = decltype(std_name(std::declval<T>()...)); \ using result_of_##std_name = decltype(std_name(std::declval<T>()...)); \
} \
\
namespace detail2 { \
struct std_name##_tag \
{ \
}; \
\
template<typename... T> \
std_name##_tag std_name(T&&...); \
\
template<typename... T> \
using result_of_##std_name = decltype(std_name(std::declval<T>()...)); \
\
template<typename... T> \
struct would_call_std_##std_name \
{ \
static constexpr auto const value = ::nlohmann::detail:: \
is_detected_exact<std_name##_tag, result_of_##std_name, T...>::value; \
}; \
} /* namespace detail2 */ \
\
template<typename... T> \
struct would_call_std_##std_name : detail2::would_call_std_##std_name<T...> \
{ \
} }
#ifndef JSON_USE_IMPLICIT_CONVERSIONS #ifndef JSON_USE_IMPLICIT_CONVERSIONS
@@ -35,12 +35,14 @@
#undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM
#undef JSON_HAS_THREE_WAY_COMPARISON #undef JSON_HAS_THREE_WAY_COMPARISON
#undef JSON_HAS_RANGES #undef JSON_HAS_RANGES
#undef JSON_HAS_RANGE_VIEW_CONVERSION
#undef JSON_HAS_STD_FORMAT #undef JSON_HAS_STD_FORMAT
#undef JSON_HAS_STATIC_RTTI #undef JSON_HAS_STATIC_RTTI
#undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#undef JSON_BRACE_INIT_COPY_SEMANTICS #undef JSON_BRACE_INIT_COPY_SEMANTICS
#undef JSON_PRECISE_STREAM_POSITION #undef JSON_PRECISE_STREAM_POSITION
#undef JSON_STRICT_NUL_HANDLING #undef JSON_STRICT_NUL_HANDLING
#undef JSON_STRICT_BINARY_UTF8
#endif #endif
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp> #include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
@@ -12,6 +12,6 @@
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin)
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
@@ -12,6 +12,6 @@
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end)
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+1 -34
View File
@@ -8,7 +8,7 @@
#pragma once #pragma once
#include <cstdint> // size_t #include <cstddef> // size_t
#include <utility> // declval #include <utility> // declval
#include <string> // string #include <string> // string
@@ -70,37 +70,6 @@ using parse_error_function_t = decltype(std::declval<T&>().parse_error(
std::declval<std::size_t>(), std::declval<const std::string&>(), std::declval<std::size_t>(), std::declval<const std::string&>(),
std::declval<const Exception&>())); std::declval<const Exception&>()));
template<typename SAX, typename BasicJsonType>
struct is_sax
{
private:
static_assert(is_basic_json<BasicJsonType>::value,
"BasicJsonType must be of type basic_json<...>");
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using number_float_t = typename BasicJsonType::number_float_t;
using string_t = typename BasicJsonType::string_t;
using binary_t = typename BasicJsonType::binary_t;
using exception_t = typename BasicJsonType::exception;
public:
static constexpr bool value =
is_detected_exact<bool, null_function_t, SAX>::value &&
is_detected_exact<bool, boolean_function_t, SAX>::value &&
is_detected_exact<bool, number_integer_function_t, SAX, number_integer_t>::value &&
is_detected_exact<bool, number_unsigned_function_t, SAX, number_unsigned_t>::value &&
is_detected_exact<bool, number_float_function_t, SAX, number_float_t, string_t>::value &&
is_detected_exact<bool, string_function_t, SAX, string_t>::value &&
is_detected_exact<bool, binary_function_t, SAX, binary_t>::value &&
is_detected_exact<bool, start_object_function_t, SAX>::value &&
is_detected_exact<bool, key_function_t, SAX, string_t>::value &&
is_detected_exact<bool, end_object_function_t, SAX>::value &&
is_detected_exact<bool, start_array_function_t, SAX>::value &&
is_detected_exact<bool, end_array_function_t, SAX>::value &&
is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value;
};
template<typename SAX, typename BasicJsonType> template<typename SAX, typename BasicJsonType>
struct is_sax_static_asserts struct is_sax_static_asserts
{ {
@@ -120,8 +89,6 @@ struct is_sax_static_asserts
"Missing/invalid function: bool null()"); "Missing/invalid function: bool null()");
static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value, static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
"Missing/invalid function: bool boolean(bool)"); "Missing/invalid function: bool boolean(bool)");
static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
"Missing/invalid function: bool boolean(bool)");
static_assert( static_assert(
is_detected_exact<bool, number_integer_function_t, SAX, is_detected_exact<bool, number_integer_function_t, SAX,
number_integer_t>::value, number_integer_t>::value,
-54
View File
@@ -1,54 +0,0 @@
#pragma once
#include <nlohmann/detail/macro_scope.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
#ifdef JSON_HAS_CPP_17
template<bool... Booleans>
struct cxpr_or_impl : std::integral_constant < bool, (Booleans || ...) > {};
template<bool... Booleans>
struct cxpr_and_impl : std::integral_constant < bool, (Booleans &&...) > {};
#else
template<bool... Booleans>
struct cxpr_or_impl : std::false_type {};
template<bool... Booleans>
struct cxpr_or_impl<true, Booleans...> : std::true_type {};
template<bool... Booleans>
struct cxpr_or_impl<false, Booleans...> : cxpr_or_impl<Booleans...> {};
template<bool... Booleans>
struct cxpr_and_impl : std::true_type {};
template<bool... Booleans>
struct cxpr_and_impl<true, Booleans...> : cxpr_and_impl<Booleans...> {};
template<bool... Booleans>
struct cxpr_and_impl<false, Booleans...> : std::false_type {};
#endif
template<class Boolean>
struct cxpr_not : std::integral_constant < bool, !Boolean::value > {};
template<class... Booleans>
struct cxpr_or : cxpr_or_impl<Booleans::value...> {};
template<bool... Booleans>
struct cxpr_or_c : cxpr_or_impl<Booleans...> {};
template<class... Booleans>
struct cxpr_and : cxpr_and_impl<Booleans::value...> {};
template<bool... Booleans>
struct cxpr_and_c : cxpr_and_impl<Booleans...> {};
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+68 -26
View File
@@ -189,6 +189,37 @@ struct actual_object_comparator
template<typename BasicJsonType> template<typename BasicJsonType>
using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type; using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;
template<typename T>
using detect_key_comp = decltype(std::declval<const T&>().key_comp());
// whether ObjectType can be constructed from a pair of Iterator together with
// a copy of its own comparator, the way std::map can: it needs a nested
// key_compare, a const key_comp() convertible to it, and a matching
// (Iterator, Iterator, const key_compare&) constructor.
//
// used to preserve a stateful comparator when a copy is built from a range
// past the iterative deep copy's nesting bound (see copy_object_level); an
// object type that does not satisfy this, such as nlohmann::ordered_map
// (which has key_compare for its std::map-like interface, but no key_comp()),
// keeps default-constructing its comparator, just as it always has
template<typename ObjectType, typename Iterator, typename = void>
struct is_comparator_constructible_object_type_impl : std::false_type {};
template<typename ObjectType, typename Iterator>
struct is_comparator_constructible_object_type_impl <
ObjectType, Iterator, enable_if_t<is_detected<detect_key_compare, ObjectType>::value >>
{
using key_compare = typename ObjectType::key_compare;
static constexpr bool value =
is_detected_convertible<key_compare, detect_key_comp, ObjectType>::value &&
std::is_constructible<ObjectType, Iterator, Iterator, const key_compare&>::value;
};
template<typename ObjectType, typename Iterator>
struct is_comparator_constructible_object_type
: is_comparator_constructible_object_type_impl<ObjectType, Iterator> {};
///////////////// /////////////////
// char_traits // // char_traits //
///////////////// /////////////////
@@ -283,6 +314,13 @@ template<class B, class... Bn>
struct conjunction<B, Bn...> struct conjunction<B, Bn...>
: std::conditional<static_cast<bool>(B::value), conjunction<Bn...>, B>::type {}; : std::conditional<static_cast<bool>(B::value), conjunction<Bn...>, B>::type {};
// https://en.cppreference.com/w/cpp/types/disjunction
template<class...> struct disjunction : std::false_type { };
template<class B> struct disjunction<B> : B { };
template<class B, class... Bn>
struct disjunction<B, Bn...>
: std::conditional<static_cast<bool>(B::value), B, disjunction<Bn...>>::type {};
// https://en.cppreference.com/w/cpp/types/negation // https://en.cppreference.com/w/cpp/types/negation
template<class B> struct negation : std::integral_constant < bool, !B::value > { }; template<class B> struct negation : std::integral_constant < bool, !B::value > { };
@@ -477,9 +515,7 @@ template<typename T> struct is_range_view_optional_type<std::optional<T>> : std:
template<typename T> struct is_range_view_optional_type : std::false_type {}; template<typename T> struct is_range_view_optional_type : std::false_type {};
#endif #endif
// std::ranges does not work properly on MinGW due to incomplete C++20 support #if JSON_HAS_RANGE_VIEW_CONVERSION
// see https://github.com/nlohmann/json/issues/4916
#if JSON_HAS_RANGES && !defined(__MINGW32__)
// SafeToCheck guards against types that trigger circular constraints when // SafeToCheck guards against types that trigger circular constraints when
// std::ranges::view<T> is evaluated on GCC 12 / libstdc++ 12: // std::ranges::view<T> is evaluated on GCC 12 / libstdc++ 12:
@@ -518,7 +554,7 @@ struct is_compatible_array_type_impl <
// filter_view) can match BOTH this iterator-based specialization AND the view-based one // filter_view) can match BOTH this iterator-based specialization AND the view-based one
// below, causing ambiguity. Exclude views here so the two specializations are mutually // below, causing ambiguity. Exclude views here so the two specializations are mutually
// exclusive: this one handles plain iterable containers, the other handles views. // exclusive: this one handles plain iterable containers, the other handles views.
#if JSON_HAS_RANGES && !defined(__MINGW32__) #if JSON_HAS_RANGE_VIEW_CONVERSION
&& !is_compatible_range_view<CompatibleArrayType>::value && !is_compatible_range_view<CompatibleArrayType>::value
#endif #endif
>> >>
@@ -528,7 +564,7 @@ struct is_compatible_array_type_impl <
range_value_t<CompatibleArrayType>>::value; range_value_t<CompatibleArrayType>>::value;
}; };
#if JSON_HAS_RANGES && !defined(__MINGW32__) #if JSON_HAS_RANGE_VIEW_CONVERSION
template<typename BasicJsonType, typename CompatibleArrayType> template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type_impl < struct is_compatible_array_type_impl <
BasicJsonType, CompatibleArrayType, BasicJsonType, CompatibleArrayType,
@@ -604,7 +640,6 @@ struct is_compatible_integer_type_impl <
std::is_integral<CompatibleNumberIntegerType>::value&& std::is_integral<CompatibleNumberIntegerType>::value&&
!std::is_same<bool, CompatibleNumberIntegerType>::value >> !std::is_same<bool, CompatibleNumberIntegerType>::value >>
{ {
// is there an assert somewhere on overflows?
using RealLimits = std::numeric_limits<RealIntegerType>; using RealLimits = std::numeric_limits<RealIntegerType>;
using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;
@@ -760,6 +795,30 @@ using is_usable_as_key_type = typename std::conditional <
std::true_type, std::true_type,
std::false_type >::type; std::false_type >::type;
#ifdef JSON_HAS_CPP_17
// type trait to check if KeyType can only be used as an object key after
// converting it to std::string_view: it is convertible to std::string_view, the
// object's comparator cannot compare it with object_t::key_type directly, but
// can compare a std::string_view. JSON pointers and JSON iterators are ruled out
// first, so that the conversion checks are never instantiated for them (a JSON
// pointer's deprecated conversion to string_t would be named otherwise).
template < typename BasicJsonType, typename KeyTypeCVRef, typename KeyType = uncvref_t<KeyTypeCVRef>,
bool = is_json_pointer<KeyType>::value || is_json_iterator_of<BasicJsonType, KeyType>::value >
struct is_string_view_convertible_key_type : std::false_type {};
template<typename BasicJsonType, typename KeyTypeCVRef, typename KeyType>
struct is_string_view_convertible_key_type<BasicJsonType, KeyTypeCVRef, KeyType, false>
: std::integral_constant < bool,
std::is_convertible<KeyTypeCVRef, std::string_view>::value
&& !is_usable_as_key_type<typename BasicJsonType::object_comparator_t,
typename BasicJsonType::object_t::key_type, KeyTypeCVRef, true, false>::value
&& is_usable_as_key_type<typename BasicJsonType::object_comparator_t,
typename BasicJsonType::object_t::key_type, std::string_view, true, false>::value > {};
#else
template<typename BasicJsonType, typename KeyTypeCVRef>
struct is_string_view_convertible_key_type : std::false_type {};
#endif
// type trait to check if KeyType can be used as an object key // type trait to check if KeyType can be used as an object key
// true if: // true if:
// - KeyType is comparable with BasicJsonType::object_t::key_type // - KeyType is comparable with BasicJsonType::object_t::key_type
@@ -773,9 +832,7 @@ using is_usable_as_basic_json_key_type = typename std::conditional <
typename BasicJsonType::object_t::key_type, KeyTypeCVRef, typename BasicJsonType::object_t::key_type, KeyTypeCVRef,
RequireTransparentComparator, ExcludeObjectKeyType>::value RequireTransparentComparator, ExcludeObjectKeyType>::value
&& !is_json_iterator_of<BasicJsonType, KeyType>::value) && !is_json_iterator_of<BasicJsonType, KeyType>::value)
#ifdef JSON_HAS_CPP_17 || is_string_view_convertible_key_type<BasicJsonType, KeyTypeCVRef>::value
|| std::is_convertible<KeyType, std::string_view>::value
#endif
, std::true_type, , std::true_type,
std::false_type >::type; std::false_type >::type;
@@ -810,20 +867,7 @@ struct has_capacity : std::integral_constant<bool, is_detected<detect_capacity,
// a naive helper to check if a type is an ordered_map (exploits the fact that // a naive helper to check if a type is an ordered_map (exploits the fact that
// ordered_map inherits capacity() from std::vector) // ordered_map inherits capacity() from std::vector)
template <typename T> template <typename T>
struct is_ordered_map struct is_ordered_map : has_capacity<T> {};
{
using one = char;
struct two
{
char x[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
};
template <typename C> static one test( decltype(&C::capacity) ) ;
template <typename C> static two test(...);
enum { value = sizeof(test<T>(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-use-enum-class)
};
// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) // to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324)
template < typename T, typename U, enable_if_t < !std::is_same<T, U>::value, int > = 0 > template < typename T, typename U, enable_if_t < !std::is_same<T, U>::value, int > = 0 >
@@ -847,10 +891,8 @@ using all_signed = conjunction<std::is_signed<Types>...>;
template<typename... Types> template<typename... Types>
using all_unsigned = conjunction<std::is_unsigned<Types>...>; using all_unsigned = conjunction<std::is_unsigned<Types>...>;
// there's a disjunction trait in another PR; replace when merged
template<typename... Types> template<typename... Types>
using same_sign = std::integral_constant < bool, using same_sign = disjunction<all_signed<Types...>, all_unsigned<Types...>>;
all_signed<Types...>::value || all_unsigned<Types...>::value >;
template<typename OfType, typename T> template<typename OfType, typename T>
using never_out_of_range = std::integral_constant < bool, using never_out_of_range = std::integral_constant < bool,
+178 -29
View File
@@ -26,6 +26,7 @@
#include <nlohmann/detail/input/binary_reader.hpp> #include <nlohmann/detail/input/binary_reader.hpp>
#include <nlohmann/detail/input/string_scan.hpp> #include <nlohmann/detail/input/string_scan.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/output/error_handler.hpp>
#include <nlohmann/detail/output/output_adapters.hpp> #include <nlohmann/detail/output/output_adapters.hpp>
#include <nlohmann/detail/string_concat.hpp> #include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/string_utils.hpp> #include <nlohmann/detail/string_utils.hpp>
@@ -93,8 +94,12 @@ class binary_writer
@param[in] sink output sink to write to (a value-type sink such as @param[in] sink output sink to write to (a value-type sink such as
output_vector_sink, or output_adapter_sink wrapping a output_vector_sink, or output_adapter_sink wrapping a
type-erased output adapter) type-erased output adapter)
@param[in] error_handler_ how to treat a string value or object key that
is not valid UTF-8 (CBOR, MessagePack, UBJSON, BJData, and BSON;
never consulted by @ref write_bon8)
*/ */
explicit binary_writer(OutputSinkType sink) : oa(std::move(sink)) explicit binary_writer(OutputSinkType sink, const error_handler_t error_handler_ = binary_writer_default_error_handler())
: oa(std::move(sink)), error_handler(error_handler_)
{} {}
/*! /*!
@@ -107,14 +112,20 @@ class binary_writer
from one. from one.
@param[in] adapter output adapter to write to @param[in] adapter output adapter to write to
@param[in] error_handler_ how to treat a string value or object key that
is not valid UTF-8 (CBOR, MessagePack, UBJSON, BJData, and BSON;
never consulted by @ref write_bon8)
*/ */
template < typename SinkType = OutputSinkType, template < typename SinkType = OutputSinkType,
typename std::enable_if < std::is_constructible<SinkType, output_adapter_t<CharType>>::value, int >::type = 0 > typename std::enable_if < std::is_constructible<SinkType, output_adapter_t<CharType>>::value, int >::type = 0 >
explicit binary_writer(output_adapter_t<CharType> adapter) : oa(SinkType(std::move(adapter))) explicit binary_writer(output_adapter_t<CharType> adapter, const error_handler_t error_handler_ = binary_writer_default_error_handler())
: oa(SinkType(std::move(adapter))), error_handler(error_handler_)
{} {}
/*! /*!
@param[in] j JSON value to serialize @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.317 if @a j is not an object @throw type_error.317 if @a j is not an object
*/ */
void write_bson(const BasicJsonType& j) void write_bson(const BasicJsonType& j)
@@ -145,6 +156,8 @@ class binary_writer
/*! /*!
@param[in] j JSON value to serialize @param[in] j JSON value to serialize
@throw type_error.316 if a string value or an object key is not valid
UTF-8
*/ */
void write_cbor(const BasicJsonType& j) void write_cbor(const BasicJsonType& j)
{ {
@@ -211,13 +224,16 @@ class binary_writer
case value_t::string: case value_t::string:
{ {
string_t storage;
const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage);
// step 1: write control byte and the string length // step 1: write control byte and the string length
write_cbor_head(0x60, j.m_data.m_value.string->size()); write_cbor_head(0x60, value.size());
// step 2: write the string // step 2: write the string
oa.write_characters( oa.write_characters(
reinterpret_cast<const CharType*>(j.m_data.m_value.string->data()), reinterpret_cast<const CharType*>(value.data()),
j.m_data.m_value.string->size()); value.size());
break; break;
} }
@@ -287,6 +303,17 @@ class binary_writer
// step 2: write each element // step 2: write each element
for (const auto& el : *j.m_data.m_value.object) for (const auto& el : *j.m_data.m_value.object)
{ {
// el.first is checked here, against the object as
// diagnostics context, because write_cbor(el.first)
// converts it to a temporary basic_json that would be
// used as the context instead; for error_handler_t::keep
// and ::replace/::ignore the recursive write_cbor(el.first)
// call below handles the key like any other string, so no
// separate check is needed here for those
if (error_handler == error_handler_t::strict)
{
check_utf8(el.first, j);
}
write_cbor(el.first); write_cbor(el.first);
write_cbor(el.second); write_cbor(el.second);
} }
@@ -434,8 +461,11 @@ class binary_writer
case value_t::string: case value_t::string:
{ {
string_t storage;
const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage);
// step 1: write control byte and the string length // step 1: write control byte and the string length
const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j); const auto N = to_msgpack_length(value.size(), j);
if (N <= 31) if (N <= 31)
{ {
// fixstr // fixstr
@@ -462,8 +492,8 @@ class binary_writer
// step 2: write the string // step 2: write the string
oa.write_characters( oa.write_characters(
reinterpret_cast<const CharType*>(j.m_data.m_value.string->data()), reinterpret_cast<const CharType*>(value.data()),
j.m_data.m_value.string->size()); value.size());
break; break;
} }
@@ -610,6 +640,13 @@ class binary_writer
// step 2: write each element // step 2: write each element
for (const auto& el : *j.m_data.m_value.object) for (const auto& el : *j.m_data.m_value.object)
{ {
// as in write_cbor, el.first is checked here against the
// object as diagnostics context; the recursive call below
// handles keep/replace/ignore like any other string
if (error_handler == error_handler_t::strict)
{
check_utf8(el.first, j);
}
write_msgpack(el.first); write_msgpack(el.first);
write_msgpack(el.second); write_msgpack(el.second);
} }
@@ -629,6 +666,8 @@ class binary_writer
@param[in] add_prefix whether prefixes need to be used for this value @param[in] add_prefix whether prefixes need to be used for this value
@param[in] use_bjdata whether write in BJData format, default is false @param[in] use_bjdata whether write in BJData format, default is false
@param[in] bjdata_version which BJData version to use, default is draft2 @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
*/ */
void write_ubjson(const BasicJsonType& j, const bool use_count, void write_ubjson(const BasicJsonType& j, const bool use_count,
const bool use_type, const bool add_prefix = true, const bool use_type, const bool add_prefix = true,
@@ -678,14 +717,17 @@ class binary_writer
case value_t::string: case value_t::string:
{ {
string_t storage;
const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage);
if (add_prefix) if (add_prefix)
{ {
oa.write_character(to_char_type('S')); oa.write_character(to_char_type('S'));
} }
write_number_with_ubjson_prefix(j.m_data.m_value.string->size(), true, use_bjdata); write_number_with_ubjson_prefix(value.size(), true, use_bjdata);
oa.write_characters( oa.write_characters(
reinterpret_cast<const CharType*>(j.m_data.m_value.string->data()), reinterpret_cast<const CharType*>(value.data()),
j.m_data.m_value.string->size()); value.size());
break; break;
} }
@@ -840,10 +882,12 @@ class binary_writer
for (const auto& el : *j.m_data.m_value.object) for (const auto& el : *j.m_data.m_value.object)
{ {
write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata); string_t storage;
const string_t& key = sanitize_utf8_for_write(el.first, j, storage);
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
oa.write_characters( oa.write_characters(
reinterpret_cast<const CharType*>(el.first.data()), reinterpret_cast<const CharType*>(key.data()),
el.first.size()); key.size());
write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version);
} }
@@ -884,8 +928,12 @@ class binary_writer
/*! /*!
@return The size of a BSON document entry header, including the id marker @return The size of a BSON document entry header, including the id marker
and the entry name size (and its null-terminator). and the entry name size (and its null-terminator).
@throw out_of_range.409 if @a name contains U+0000, before anything is
written
@throw type_error.316 if @a name is not valid UTF-8, before anything is
written
*/ */
static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j)
{ {
const auto it = name.find(static_cast<typename string_t::value_type>(0)); const auto it = name.find(static_cast<typename string_t::value_type>(0));
if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos))
@@ -893,8 +941,10 @@ class binary_writer
JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j));
} }
static_cast<void>(j); string_t storage;
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; const string_t& sanitized = sanitize_utf8_for_write(name, j, storage);
return /*id*/ 1ul + sanitized.size() + /*zero-terminator*/1u;
} }
/*! /*!
@@ -914,14 +964,28 @@ class binary_writer
/*! /*!
@brief Writes the given @a element_type and @a name to the output adapter @brief Writes the given @a element_type and @a name to the output adapter
@a name has already been validated (and, for @ref error_handler_t::strict,
found well-formed) by @ref calc_bson_entry_header_size during the earlier
size pass, so only @ref error_handler_t::replace / @ref
error_handler_t::ignore need to sanitize it again here, to actually write
the bytes that size was computed from.
*/ */
void write_bson_entry_header(const string_t& name, void write_bson_entry_header(const string_t& name,
const std::uint8_t element_type) const std::uint8_t element_type)
{ {
oa.write_character(to_char_type(element_type)); oa.write_character(to_char_type(element_type));
oa.write_characters(
reinterpret_cast<const CharType*>(name.data()), if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(name))
name.size()); {
oa.write_characters(reinterpret_cast<const CharType*>(name.data()), name.size());
}
else
{
const string_t sanitized = sanitize_utf8(name, error_handler);
oa.write_characters(reinterpret_cast<const CharType*>(sanitized.data()), sanitized.size());
}
// the terminating null byte is written explicitly rather than taken // the terminating null byte is written explicitly rather than taken
// from the buffer, so that string_t::data() need not be null-terminated // from the buffer, so that string_t::data() need not be null-terminated
oa.write_character(to_char_type(0x00)); oa.write_character(to_char_type(0x00));
@@ -949,24 +1013,50 @@ class binary_writer
/*! /*!
@return The size of the BSON-encoded string in @a value @return The size of the BSON-encoded string in @a value
@throw type_error.316 if @a value is not valid UTF-8, before anything is
written
@note The UTF-8 check is skipped if @a value is already too long for the
32-bit BSON length field (@ref to_bson_length rejects it later, once
the size of the whole document is known); this also keeps the check
from reading past a StringType that reports a size larger than what
it actually holds.
*/ */
static std::size_t calc_bson_string_size(const string_t& value) std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j)
{ {
if (JSON_HEDLEY_LIKELY(value_in_range_of<std::int32_t>(value.size())))
{
string_t storage;
const string_t& sanitized = sanitize_utf8_for_write(value, j, storage);
return sizeof(std::int32_t) + sanitized.size() + 1ul;
}
return sizeof(std::int32_t) + value.size() + 1ul; return sizeof(std::int32_t) + value.size() + 1ul;
} }
/*! /*!
@brief Writes a BSON element with key @a name and string value @a value @brief Writes a BSON element with key @a name and string value @a value
@a value has already been validated (and, for @ref error_handler_t::strict,
found well-formed) by @ref calc_bson_string_size during the earlier size
pass, so only @ref error_handler_t::replace / @ref error_handler_t::ignore
need to sanitize it again here, to actually write the bytes that size was
computed from.
*/ */
void write_bson_string(const string_t& name, void write_bson_string(const string_t& name,
const string_t& value) const string_t& value)
{ {
write_bson_entry_header(name, 0x02); write_bson_entry_header(name, 0x02);
write_number<std::int32_t>(to_bson_length(value.size() + 1ul), true); const bool sanitize = error_handler != error_handler_t::keep
&& error_handler != error_handler_t::strict
&& !is_valid_utf8(value);
const string_t sanitized = sanitize ? sanitize_utf8(value, error_handler) : string_t{};
const string_t& written = sanitize ? sanitized : value;
write_number<std::int32_t>(to_bson_length(written.size() + 1ul), true);
oa.write_characters( oa.write_characters(
reinterpret_cast<const CharType*>(value.data()), reinterpret_cast<const CharType*>(written.data()),
value.size()); written.size());
// the terminating null byte is written explicitly rather than taken // the terminating null byte is written explicitly rather than taken
// from the buffer, so that string_t::data() need not be null-terminated // from the buffer, so that string_t::data() need not be null-terminated
oa.write_character(to_char_type(0x00)); oa.write_character(to_char_type(0x00));
@@ -1080,8 +1170,10 @@ class binary_writer
is neither an object nor an array is neither an object nor an array
@throw out_of_range.415 if @a j is binary with a subtype that does not fit @throw out_of_range.415 if @a j is binary with a subtype that does not fit
into a byte, before anything is written 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
*/ */
static std::size_t calc_bson_value_size(const BasicJsonType& j) std::size_t calc_bson_value_size(const BasicJsonType& j)
{ {
switch (j.type()) switch (j.type())
{ {
@@ -1101,7 +1193,7 @@ class binary_writer
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned); return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
case value_t::string: case value_t::string:
return calc_bson_string_size(*j.m_data.m_value.string); return calc_bson_string_size(*j.m_data.m_value.string, j);
case value_t::null: case value_t::null:
return 0ul; return 0ul;
@@ -1214,8 +1306,10 @@ class binary_writer
written written
@throw out_of_range.415 if a binary value's subtype does not fit into a @throw out_of_range.415 if a binary value's subtype does not fit into a
byte, before anything is written 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
*/ */
static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes) std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
{ {
// the object or array whose entries are being sized, and the ones it // the object or array whose entries are being sized, and the ones it
// is in; nothing is allocated unless the document nests // is in; nothing is allocated unless the document nests
@@ -2092,7 +2186,7 @@ class binary_writer
*/ */
void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context) void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context)
{ {
check_bon8_utf8(s, context); check_utf8(s, context);
// a string that follows another string terminates it // a string that follows another string terminates it
if (string_open) if (string_open)
@@ -2122,7 +2216,7 @@ class binary_writer
@throw type_error.316 if @a s is not valid UTF-8; the message names the @throw type_error.316 if @a s is not valid UTF-8; the message names the
first byte of the first invalid or incomplete sequence first byte of the first invalid or incomplete sequence
*/ */
static void check_bon8_utf8(const string_t& s, const BasicJsonType& context) static void check_utf8(const string_t& s, const BasicJsonType& context)
{ {
static_cast<void>(context); // only used when exceptions are enabled static_cast<void>(context); // only used when exceptions are enabled
const auto* data = reinterpret_cast<const unsigned char*>(s.data()); const auto* data = reinterpret_cast<const unsigned char*>(s.data());
@@ -2133,6 +2227,57 @@ class binary_writer
} }
} }
/*!
@brief return @a s as it should be written, honoring @ref error_handler
Used by @ref write_cbor, @ref write_msgpack, @ref write_ubjson (and so
@ref write_bjdata), and the BSON writing functions for string values and
object keys; never by @ref write_bon8, which always validates, since UTF-8
lead bytes are structural there.
- @ref error_handler_t::keep: @a s is returned unchanged, without even
checking it (the behavior of release 3.12.0 and earlier).
- @ref error_handler_t::strict: @ref check_utf8 is called, which throws
type_error.316 if @a s is not valid UTF-8.
- @ref error_handler_t::replace / @ref error_handler_t::ignore: @a s is
sanitized into @a storage with exactly the rules @ref
serializer::dump_escaped_impl uses, so that parsing what @ref
basic_json::dump produces for the same string and the same handler
yields the same result.
Well-formed input is never copied: this returns a reference to @a s
itself in every case but a sanitized `replace`/`ignore` one, so @a
storage must outlive the returned reference only then.
@param[in] s the string (value or object key) to write
@param[in] context the value @a s belongs to (for diagnostics)
@param[out] storage backing storage for a sanitized copy
@return a reference to @a s, or to @a storage once it holds a sanitized copy
*/
const string_t& sanitize_utf8_for_write(const string_t& s, const BasicJsonType& context, string_t& storage) const
{
switch (error_handler)
{
case error_handler_t::keep:
return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call
case error_handler_t::strict:
check_utf8(s, context);
return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call
case error_handler_t::replace:
case error_handler_t::ignore:
default:
if (is_valid_utf8(s))
{
return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call
}
storage = sanitize_utf8(s, error_handler);
return storage;
}
}
/*! /*!
@brief write an integer in the shortest encoding @brief write an integer in the shortest encoding
@@ -2461,6 +2606,10 @@ class binary_writer
/// the output /// the output
OutputSinkType oa; OutputSinkType oa;
/// how to treat a string value or object key that is not valid UTF-8
/// (CBOR, MessagePack, UBJSON, BJData, and BSON; not BON8)
const error_handler_t error_handler = binary_writer_default_error_handler();
}; };
} // namespace detail } // namespace detail
@@ -0,0 +1,50 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <nlohmann/detail/abi_macros.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
/// how to treat decoding errors
///
/// @ref basic_json::dump uses this to decide what to do with ill-formed
/// UTF-8 while escaping a string, and the binary writers (@ref
/// basic_json::to_cbor, @ref basic_json::to_ubjson, @ref
/// basic_json::to_bjdata, @ref basic_json::to_bson) use it the same way for
/// string values and object keys. The binary readers (@ref
/// basic_json::from_cbor, @ref basic_json::from_msgpack, @ref
/// basic_json::from_ubjson, @ref basic_json::from_bjdata, @ref
/// basic_json::from_bson) use it to decide whether to check text strings
/// and object keys for well-formed UTF-8 at all, since none of those
/// formats requires a decoder to do so.
enum class error_handler_t
{
strict, ///< throw a type_error/parse_error exception in case of invalid UTF-8
replace, ///< replace invalid UTF-8 sequences with U+FFFD
ignore, ///< ignore invalid UTF-8 sequences
keep ///< keep invalid UTF-8 sequences unchanged
};
/// the default error handler of the CBOR, UBJSON, BJData, and BSON writers:
/// error_handler_t::strict if JSON_STRICT_BINARY_UTF8 is enabled, otherwise
/// error_handler_t::keep (the behavior before version 3.13.0)
constexpr error_handler_t binary_writer_default_error_handler() noexcept
{
#if JSON_STRICT_BINARY_UTF8
return error_handler_t::strict;
#else
return error_handler_t::keep;
#endif
}
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+70 -10
View File
@@ -27,6 +27,7 @@
#include <nlohmann/detail/input/string_scan.hpp> #include <nlohmann/detail/input/string_scan.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp> #include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/output/error_handler.hpp>
#include <nlohmann/detail/output/output_adapters.hpp> #include <nlohmann/detail/output/output_adapters.hpp>
#include <nlohmann/detail/recursion_depth_limit.hpp> #include <nlohmann/detail/recursion_depth_limit.hpp>
#include <nlohmann/detail/string_concat.hpp> #include <nlohmann/detail/string_concat.hpp>
@@ -41,14 +42,6 @@ namespace detail
// serialization // // serialization //
/////////////////// ///////////////////
/// how to treat decoding errors
enum class error_handler_t
{
strict, ///< throw a type_error exception in case of invalid UTF-8
replace, ///< replace invalid UTF-8 sequences with U+FFFD
ignore ///< ignore invalid UTF-8 sequences
};
template<typename BasicJsonType> template<typename BasicJsonType>
class serializer class serializer
{ {
@@ -713,6 +706,11 @@ class serializer
@a ensure_ascii is a template parameter here so that the branch on it is @a ensure_ascii is a template parameter here so that the branch on it is
resolved once, outside the loop; see @ref dump_escaped. resolved once, outside the loop; see @ref dump_escaped.
*/ */
#ifdef JSON_HEDLEY_MSVC_VERSION
#pragma warning(push)
// EnsureAscii is a template parameter; C++11 has no if constexpr
#pragma warning(disable : 4127) // conditional expression is constant
#endif
template<bool EnsureAscii> template<bool EnsureAscii>
void dump_escaped_impl(const string_t& s) void dump_escaped_impl(const string_t& s)
{ {
@@ -839,6 +837,16 @@ class serializer
// EnsureAscii parameter is used, non-ASCII characters // EnsureAscii parameter is used, non-ASCII characters
if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F))) if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F)))
{ {
if (EnsureAscii && error_handler == error_handler_t::keep)
{
// this character was buffered as raw bytes
// below in case it turned out to be part of
// an ill-formed sequence (which is kept as
// is); now that it decoded to a well-formed
// code point, undo that and \u-escape it
// like any other character instead
bytes = bytes_after_last_accept;
}
if (codepoint <= 0xFFFF) if (codepoint <= 0xFFFF)
{ {
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint)); write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
@@ -937,6 +945,44 @@ class serializer
break; break;
} }
case error_handler_t::keep:
{
// the bytes of this (now abandoned) ill-formed
// sequence seen so far are already buffered below
// and are kept unchanged in the output
if (undumped_chars > 0)
{
// the byte that ended the sequence may be OK
// for itself (e.g., a quote that must still be
// escaped, or the lead byte of a well-formed
// code point), so read it again
--i;
}
else
{
// a byte that cannot start a sequence (e.g.,
// 0xFF or a stray continuation byte) is kept
// as well
string_buffer[bytes++] = s[i];
}
// write buffer and reset index; there must be 13 bytes
// left, as this is the maximal number of bytes to be
// written ("\uxxxx\uxxxx\0") for one code point
if (string_buffer.size() - bytes < 13)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
bytes_after_last_accept = bytes;
undumped_chars = 0;
// continue processing the string
state = UTF8_ACCEPT;
break;
}
default: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
} }
@@ -945,9 +991,12 @@ class serializer
default: // decode found yet incomplete multibyte code point default: // decode found yet incomplete multibyte code point
{ {
if (!EnsureAscii) if (!EnsureAscii || error_handler == error_handler_t::keep)
{ {
// code point will not be escaped - copy byte to buffer // code point will not be escaped (or will be kept as
// is if it turns out to be ill-formed) - copy byte to
// buffer; dropped again above if it decodes to a
// well-formed code point that needs \u-escaping
string_buffer[bytes++] = s[i]; string_buffer[bytes++] = s[i];
} }
++undumped_chars; ++undumped_chars;
@@ -998,11 +1047,22 @@ class serializer
break; break;
} }
case error_handler_t::keep:
{
// write the ill-formed trailing bytes as is; they were
// buffered above regardless of EnsureAscii
put_buffer(string_buffer, bytes);
break;
}
default: // LCOV_EXCL_LINE default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
} }
} }
} }
#ifdef JSON_HEDLEY_MSVC_VERSION
#pragma warning(pop)
#endif
private: private:
/*! /*!
@@ -39,7 +39,6 @@ inline std::size_t concat_length(const char /*c*/, const Args& ... rest)
template<typename... Args> template<typename... Args>
inline std::size_t concat_length(const char* cstr, const Args& ... rest) inline std::size_t concat_length(const char* cstr, const Args& ... rest)
{ {
// cppcheck-suppress ignoredReturnValue
return ::strlen(cstr) + concat_length(rest...); return ::strlen(cstr) + concat_length(rest...);
} }
+113 -15
View File
@@ -16,6 +16,7 @@
#include <nlohmann/detail/abi_macros.hpp> #include <nlohmann/detail/abi_macros.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/output/error_handler.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@@ -117,13 +118,14 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally
written by Björn Hoehrmann. See written by Björn Hoehrmann. See
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
The library checks UTF-8 well-formedness (RFC 3629, section 4) in four The library checks UTF-8 well-formedness (RFC 3629, section 4) in three
places, which differ in speed, diagnostics, and how they read the input: places, which differ in speed, diagnostics, and how they read the input:
- decode() and @ref is_valid_utf8 below: the serializer (to escape and, in - decode() below: the serializer, to escape and, in strict mode, reject
strict mode, reject ill-formed UTF-8 when dumping a string) and the CBOR, ill-formed UTF-8 when dumping a string. The CBOR, MessagePack, BSON,
MessagePack, BSON, UBJSON and BJData readers (to reject ill-formed UTF-8 in UBJSON and BJData readers do not use it: none of those specs requires a
text strings at decode time). decoder to reject ill-formed UTF-8 in text strings, so the readers keep
the bytes as is and leave the check to dump() and the binary writers.
- the per-lead-byte switch in lexer::scan_string(): JSON text, with a - the per-lead-byte switch in lexer::scan_string(): JSON text, with a
diagnostic for each kind of error. diagnostic for each kind of error.
- validate_one_utf8() and valid_utf8_prefix() in string_scan.hpp: the lexer's - validate_one_utf8() and valid_utf8_prefix() in string_scan.hpp: the lexer's
@@ -179,19 +181,19 @@ inline std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std:
} }
/*! /*!
@brief check whether a string consists solely of valid UTF-8 @brief check a string for well-formed UTF-8 (RFC 3629, section 4)
Used by the CBOR/MessagePack/BSON/UBJSON binary readers to reject text Used by the binary readers (CBOR, MessagePack, UBJSON, BJData, BSON) when an
strings that are not valid UTF-8 at decode time (RFC 8949 §3.1 and the @ref error_handler_t other than `keep` is requested for a text string value
MessagePack/BSON specifications all require text strings to be UTF-8), so or object key: none of those formats requires a decoder to reject ill-formed
that malformed input is caught immediately instead of only surfacing later UTF-8 on its own, so the check is opt-in there, unlike the JSON lexer and the
as a type_error.316 when the resulting value is dumped. serializer's @ref decode -based escaping, which always run it.
@param[in] s the string to check @param[in] s the string to check
@param[in] first index of the first byte to check; the bytes before it are @param[in] first the index to start checking at
assumed to have been validated already and to end on a @return whether `s.substr(first)` is well-formed UTF-8
code point boundary
@return whether @a s (from index @a first on) is valid UTF-8 @sa @ref decode
*/ */
template<typename StringType> template<typename StringType>
inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noexcept inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noexcept
@@ -211,5 +213,101 @@ inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noex
return state == UTF8_ACCEPT; return state == UTF8_ACCEPT;
} }
/*!
@brief sanitize a string with ill-formed UTF-8 for @ref error_handler_t::replace or @ref error_handler_t::ignore
Replaces every maximal ill-formed subsequence with U+FFFD (`replace`) or
drops it (`ignore`), using exactly the same boundaries @ref
serializer::dump_escaped_impl uses while escaping a string: a byte that does
not extend the sequence started by the previous byte(s) is reread as the
start of a new one, instead of being swallowed along with them.
@pre @a error_handler is @ref error_handler_t::replace or @ref error_handler_t::ignore
@note Well-formed input is copied through unchanged, including bytes (e.g.
control characters or quotes) that @ref serializer::dump_escaped_impl
would itself escape; this function only concerns itself with
well-formedness, not with producing valid JSON text.
@param[in] s the string to sanitize
@param[in] error_handler @ref error_handler_t::replace or @ref error_handler_t::ignore
@return @a s with every ill-formed subsequence replaced or removed
@sa @ref decode
*/
template<typename StringType>
inline StringType sanitize_utf8(const StringType& s, const error_handler_t error_handler)
{
JSON_ASSERT(error_handler == error_handler_t::replace || error_handler == error_handler_t::ignore);
StringType result;
result.reserve(s.size());
std::uint32_t codepoint = 0;
std::uint8_t state = UTF8_ACCEPT;
// length of result after the last accepted code point
std::size_t result_len_after_last_accept = 0;
// whether bytes of an as yet unresolved sequence were already appended
bool pending = false;
for (std::size_t i = 0; i < s.size(); ++i)
{
switch (decode(state, codepoint, static_cast<std::uint8_t>(s[i])))
{
case UTF8_ACCEPT: // decode found a well-formed code point
{
result.push_back(s[i]);
result_len_after_last_accept = result.size();
pending = false;
break;
}
case UTF8_REJECT: // decode found an ill-formed byte
{
// in case we saw this byte for the first time, read it again,
// because it may be fine for itself, just not for the
// sequence that came before it
if (pending)
{
--i;
}
// drop the bytes of the ill-formed sequence buffered below
result.resize(result_len_after_last_accept);
if (error_handler == error_handler_t::replace)
{
result.append("\xEF\xBF\xBD");
result_len_after_last_accept = result.size();
}
pending = false;
state = UTF8_ACCEPT;
break;
}
default: // decode found yet incomplete multibyte code point
{
result.push_back(s[i]);
pending = true;
break;
}
}
}
// the string ended with an incomplete sequence
if (state != UTF8_ACCEPT)
{
result.resize(result_len_after_last_accept);
if (error_handler == error_handler_t::replace)
{
result.append("\xEF\xBF\xBD");
}
}
return result;
}
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+566 -477
View File
File diff suppressed because it is too large Load Diff
+72 -116
View File
@@ -12,7 +12,7 @@
#include <functional> // equal_to, less #include <functional> // equal_to, less
#include <initializer_list> // initializer_list #include <initializer_list> // initializer_list
#include <iterator> // input_iterator_tag, iterator_traits #include <iterator> // input_iterator_tag, iterator_traits
#include <memory> // allocator #include <memory> // allocator // IWYU pragma: keep
#include <new> // for operator new (placement new) #include <new> // for operator new (placement new)
#include <stdexcept> // for out_of_range #include <stdexcept> // for out_of_range
#include <tuple> // forward_as_tuple #include <tuple> // forward_as_tuple
@@ -74,31 +74,59 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return *this; return *this;
} }
std::pair<iterator, bool> emplace(const key_type& key, T&& t) private:
/// @brief find the entry for @a key, for either constness of @a self
/// @note the single place that performs the linear key search
template<typename Self, typename KeyType>
static auto find_impl(Self& self, const KeyType& key) -> decltype(self.begin())
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = self.begin(); it != self.end(); ++it)
{ {
if (m_compare(it->first, key)) if (self.m_compare(it->first, key))
{
return it;
}
}
return self.end();
}
/// @brief remove the entry @a it points to, preserving order
/// @note keys are not movable, so the tail is destroyed and re-constructed in place
void erase_at(iterator it)
{
for (auto next = it; ++next != this->end(); ++it)
{
it->~value_type(); // Destroy but keep allocation
new (&*it) value_type{std::move(*next)};
}
Container::pop_back();
}
public:
template<class V, detail::enable_if_t<
detail::is_constructible<T, V>::value, int> = 0>
std::pair<iterator, bool> emplace(const key_type& key, V && t)
{
const auto it = find_impl(*this, key);
if (it != this->end())
{ {
return {it, false}; return {it, false};
} }
} append(key, std::forward<V>(t));
append(key, std::forward<T>(t));
return {std::prev(this->end()), true}; return {std::prev(this->end()), true};
} }
template<class KeyType, detail::enable_if_t< template<class KeyType, class V, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::conjunction<detail::is_usable_as_key_type<key_compare, key_type, KeyType>,
std::pair<iterator, bool> emplace(KeyType && key, T && t) detail::is_constructible<T, V>>::value, int> = 0>
std::pair<iterator, bool> emplace(KeyType && key, V && t)
{ {
for (auto it = this->begin(); it != this->end(); ++it) const auto it = find_impl(*this, key);
{ if (it != this->end())
if (m_compare(it->first, key))
{ {
return {it, false}; return {it, false};
} }
} append(std::forward<KeyType>(key), std::forward<V>(t));
append(std::forward<KeyType>(key), std::forward<T>(t));
return {std::prev(this->end()), true}; return {std::prev(this->end()), true};
} }
@@ -128,76 +156,56 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
T& at(const key_type& key) T& at(const key_type& key)
{ {
for (auto it = this->begin(); it != this->end(); ++it) const auto it = find_impl(*this, key);
if (it == this->end())
{ {
if (m_compare(it->first, key))
{
return it->second;
}
}
JSON_THROW(std::out_of_range("key not found")); JSON_THROW(std::out_of_range("key not found"));
} }
return it->second;
}
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
for (auto it = this->begin(); it != this->end(); ++it) const auto it = find_impl(*this, key);
if (it == this->end())
{ {
if (m_compare(it->first, key))
{
return it->second;
}
}
JSON_THROW(std::out_of_range("key not found")); JSON_THROW(std::out_of_range("key not found"));
} }
return it->second;
}
const T& at(const key_type& key) const const T& at(const key_type& key) const
{ {
for (auto it = this->begin(); it != this->end(); ++it) const auto it = find_impl(*this, key);
if (it == this->end())
{ {
if (m_compare(it->first, key))
{
return it->second;
}
}
JSON_THROW(std::out_of_range("key not found")); JSON_THROW(std::out_of_range("key not found"));
} }
return it->second;
}
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
for (auto it = this->begin(); it != this->end(); ++it) const auto it = find_impl(*this, key);
if (it == this->end())
{ {
if (m_compare(it->first, key))
{
return it->second;
}
}
JSON_THROW(std::out_of_range("key not found")); JSON_THROW(std::out_of_range("key not found"));
} }
return it->second;
}
size_type erase(const key_type& key) size_type erase(const key_type& key)
{ {
for (auto it = this->begin(); it != this->end(); ++it) const auto it = find_impl(*this, key);
if (it != this->end())
{ {
if (m_compare(it->first, key)) erase_at(it);
{
// Since we cannot move const Keys, re-construct them in place
for (auto next = it; ++next != this->end(); ++it)
{
it->~value_type(); // Destroy but keep allocation
new (&*it) value_type{std::move(*next)};
}
Container::pop_back();
return 1; return 1;
} }
}
return 0; return 0;
} }
@@ -205,20 +213,12 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
for (auto it = this->begin(); it != this->end(); ++it) const auto it = find_impl(*this, key);
if (it != this->end())
{ {
if (m_compare(it->first, key)) erase_at(it);
{
// Since we cannot move const Keys, re-construct them in place
for (auto next = it; ++next != this->end(); ++it)
{
it->~value_type(); // Destroy but keep allocation
new (&*it) value_type{std::move(*next)};
}
Container::pop_back();
return 1; return 1;
} }
}
return 0; return 0;
} }
@@ -282,80 +282,38 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
size_type count(const key_type& key) const size_type count(const key_type& key) const
{ {
for (auto it = this->begin(); it != this->end(); ++it) return find_impl(*this, key) != this->end() ? 1 : 0;
{
if (m_compare(it->first, key))
{
return 1;
}
}
return 0;
} }
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
for (auto it = this->begin(); it != this->end(); ++it) return find_impl(*this, key) != this->end() ? 1 : 0;
{
if (m_compare(it->first, key))
{
return 1;
}
}
return 0;
} }
iterator find(const key_type& key) iterator find(const key_type& key)
{ {
for (auto it = this->begin(); it != this->end(); ++it) return find_impl(*this, key);
{
if (m_compare(it->first, key))
{
return it;
}
}
return Container::end();
} }
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
for (auto it = this->begin(); it != this->end(); ++it) return find_impl(*this, key);
{
if (m_compare(it->first, key))
{
return it;
}
}
return Container::end();
} }
const_iterator find(const key_type& key) const const_iterator find(const key_type& key) const
{ {
for (auto it = this->begin(); it != this->end(); ++it) return find_impl(*this, key);
{
if (m_compare(it->first, key))
{
return it;
}
}
return Container::end();
} }
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
const_iterator find(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) const_iterator find(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
{ {
for (auto it = this->begin(); it != this->end(); ++it) return find_impl(*this, key);
{
if (m_compare(it->first, key))
{
return it;
}
}
return Container::end();
} }
std::pair<iterator, bool> insert( value_type&& value ) std::pair<iterator, bool> insert( value_type&& value )
@@ -365,13 +323,11 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
std::pair<iterator, bool> insert( const value_type& value ) std::pair<iterator, bool> insert( const value_type& value )
{ {
for (auto it = this->begin(); it != this->end(); ++it) const auto it = find_impl(*this, value.first);
{ if (it != this->end())
if (m_compare(it->first, value.first))
{ {
return {it, false}; return {it, false};
} }
}
append(value); append(value);
return {--this->end(), true}; return {--this->end(), true};
} }
+3840
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
@@ -63,6 +63,10 @@
#define JSON_STRICT_NUL_HANDLING 0 #define JSON_STRICT_NUL_HANDLING 0
#endif #endif
#ifndef JSON_STRICT_BINARY_UTF8
#define JSON_STRICT_BINARY_UTF8 0
#endif
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else #else
@@ -99,14 +103,20 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING #define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
#endif #endif
#if JSON_STRICT_BINARY_UTF8
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8 _sbu8
#else
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif #endif
// Construct the namespace ABI tags component // Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f #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) \ #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) NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS \ #define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -115,7 +125,8 @@
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \ NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \ NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING) NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
// Construct the namespace version component // Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+4
View File
@@ -44,6 +44,10 @@ TEST_CASE("default namespace")
expected += "_snul"; expected += "_snul";
#endif #endif
#if JSON_STRICT_BINARY_UTF8
expected += "_sbu8";
#endif
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR); expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR); expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json"; expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
+4
View File
@@ -45,6 +45,10 @@ TEST_CASE("default namespace without version component")
expected += "_snul"; expected += "_snul";
#endif #endif
#if JSON_STRICT_BINARY_UTF8
expected += "_sbu8";
#endif
expected += "::basic_json"; expected += "::basic_json";
// fallback for Clang // fallback for Clang
+2 -1
View File
@@ -10,7 +10,8 @@
#include <cstdint> // uint8_t #include <cstdint> // uint8_t
#include <cstddef> // size_t #include <cstddef> // size_t
#include <fstream> // ifstream, istreambuf_iterator, ios #include <fstream> // ifstream, ios
#include <iterator> // istream_iterator
#include <vector> // vector #include <vector> // vector
namespace utils namespace utils
+1 -1
View File
@@ -567,7 +567,7 @@ struct allocator_no_forward : std::allocator<T>
{ {
allocator_no_forward() = default; allocator_no_forward() = default;
template <class U> template <class U>
allocator_no_forward(allocator_no_forward<U> /*unused*/) {} allocator_no_forward(const allocator_no_forward<U>& /*unused*/) {}
template <class U> template <class U>
struct rebind struct rebind
+36
View File
@@ -13,6 +13,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -423,6 +424,41 @@ TEST_CASE("alternative string type")
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})"); CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
} }
SECTION("conversion between basic_json specializations (#2649)")
{
// explicit conversions are always possible
CHECK(std::is_constructible<nlohmann::json, alt_json>::value);
CHECK(std::is_constructible<alt_json, nlohmann::json>::value);
CHECK(std::is_constructible<nlohmann::json, nlohmann::ordered_json>::value);
CHECK(std::is_constructible<nlohmann::ordered_json, nlohmann::json>::value);
// specializations with the same string type are implicitly convertible
CHECK(std::is_convertible<nlohmann::ordered_json, nlohmann::json>::value);
CHECK(std::is_convertible<nlohmann::json, nlohmann::ordered_json>::value);
// specializations with different string types are only implicitly convertible
// if implicit conversions are enabled
#if JSON_USE_IMPLICIT_CONVERSIONS
CHECK(std::is_convertible<alt_json, nlohmann::json>::value);
CHECK(std::is_convertible<nlohmann::json, alt_json>::value);
#else
CHECK_FALSE(std::is_convertible<alt_json, nlohmann::json>::value);
CHECK_FALSE(std::is_convertible<nlohmann::json, alt_json>::value);
#endif
// get<BasicJsonType>() works in either case
const nlohmann::json j = {{"foo", 1}, {"bar", true}};
CHECK(j.get<nlohmann::ordered_json>() == nlohmann::ordered_json(j));
// (only a number is converted here, as objects and strings are affected by #3425)
CHECK(nlohmann::json(42).get<alt_json>() == 42);
CHECK(alt_json(nlohmann::json(42)) == 42);
// get_to() also works in either case
alt_json a;
nlohmann::json(42).get_to(a);
CHECK(a == 42);
}
SECTION("strict enum") SECTION("strict enum")
{ {
// regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json // regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json
@@ -0,0 +1,372 @@
// __ _____ _____ _____
// __| | __| | | | 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"
#include "test_utils.hpp"
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <string>
#include <vector>
namespace
{
struct ill_formed_case
{
const char* name;
std::string bytes;
};
// RFC 3629 ill-formed sequences used throughout this file, plus one
// well-formed sequence for contrast
std::vector<ill_formed_case> ill_formed_cases()
{
return
{
{"overlong", "\xC0\xAE"},
{"lone_0xFF", "\xFF"},
{"truncated", "\xE2\x82"},
{"surrogate", "\xED\xA0\x80"},
};
}
std::string valid_sequence()
{
return "\xC3\xA9"; // U+00E9, "é"
}
using eh = json::error_handler_t;
std::vector<eh> all_handlers()
{
return {eh::strict, eh::replace, eh::ignore, eh::keep};
}
// what dump()+parse() produces for a sanitizing error_handler; this is the
// ground truth every binary writer/reader is checked against
std::string dump_and_parse(const std::string& raw, eh error_handler)
{
return json::parse(json(raw).dump(-1, ' ', false, error_handler)).get<std::string>();
}
} // namespace
TEST_CASE("UTF-8 error_handler for the binary readers and writers")
{
SECTION("writers: string value")
{
for (const auto& c : ill_formed_cases())
{
CAPTURE(c.name)
const json jval = c.bytes;
CHECK_THROWS_AS(json::to_cbor(jval, eh::strict), json::type_error&);
CHECK_THROWS_AS(json::to_msgpack(jval, eh::strict), json::type_error&);
CHECK_THROWS_AS(json::to_ubjson(jval, false, false, eh::strict), json::type_error&);
CHECK_THROWS_AS(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::strict), json::type_error&);
{
json jobj;
jobj["k"] = jval;
CHECK_THROWS_AS(json::to_bson(jobj, eh::strict), json::type_error&);
}
for (const auto h :
{
eh::replace, eh::ignore
})
{
CAPTURE(static_cast<int>(h))
const std::string expected = dump_and_parse(c.bytes, h);
CHECK(json::from_cbor(json::to_cbor(jval, h)).get<std::string>() == expected);
CHECK(json::from_msgpack(json::to_msgpack(jval, h)).get<std::string>() == expected);
CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, h)).get<std::string>() == expected);
CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, h)).get<std::string>() == expected);
{
json jobj;
jobj["k"] = jval;
const auto bytes = json::to_bson(jobj, h);
CHECK(json::from_bson(bytes)["k"].get<std::string>() == expected);
}
}
// keep: the writer passes the ill-formed bytes through unchanged,
// exactly as every binary writer did before this parameter existed
CHECK(json::from_cbor(json::to_cbor(jval, eh::keep)).get<std::string>() == c.bytes);
CHECK(json::from_msgpack(json::to_msgpack(jval, eh::keep)).get<std::string>() == c.bytes);
CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, eh::keep)).get<std::string>() == c.bytes);
CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep)).get<std::string>() == c.bytes);
{
json jobj;
jobj["k"] = jval;
const auto bytes = json::to_bson(jobj, eh::keep);
CHECK(json::from_bson(bytes)["k"].get<std::string>() == c.bytes);
}
}
}
SECTION("writers: object key")
{
for (const auto& c : ill_formed_cases())
{
CAPTURE(c.name)
json jobj;
jobj[c.bytes] = 1;
CHECK_THROWS_AS(json::to_cbor(jobj, eh::strict), json::type_error&);
CHECK_THROWS_AS(json::to_msgpack(jobj, eh::strict), json::type_error&);
CHECK_THROWS_AS(json::to_ubjson(jobj, false, false, eh::strict), json::type_error&);
CHECK_THROWS_AS(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::strict), json::type_error&);
CHECK_THROWS_AS(json::to_bson(jobj, eh::strict), json::type_error&);
for (const auto h :
{
eh::replace, eh::ignore
})
{
CAPTURE(static_cast<int>(h))
const std::string expected = dump_and_parse(c.bytes, h);
CHECK(json::from_cbor(json::to_cbor(jobj, h)).begin().key() == expected);
CHECK(json::from_msgpack(json::to_msgpack(jobj, h)).begin().key() == expected);
CHECK(json::from_ubjson(json::to_ubjson(jobj, false, false, h)).begin().key() == expected);
CHECK(json::from_bjdata(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, h)).begin().key() == expected);
CHECK(json::from_bson(json::to_bson(jobj, h)).begin().key() == expected);
}
// keep: object keys round-trip unchanged too
CHECK(json::from_cbor(json::to_cbor(jobj, eh::keep)).begin().key() == c.bytes);
CHECK(json::from_msgpack(json::to_msgpack(jobj, eh::keep)).begin().key() == c.bytes);
CHECK(json::from_ubjson(json::to_ubjson(jobj, false, false, eh::keep)).begin().key() == c.bytes);
CHECK(json::from_bjdata(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::keep)).begin().key() == c.bytes);
CHECK(json::from_bson(json::to_bson(jobj, eh::keep)).begin().key() == c.bytes);
}
}
SECTION("readers: string value")
{
for (const auto& c : ill_formed_cases())
{
CAPTURE(c.name)
// bytes produced the lenient (keep) way, as any binary reader
// accepted them before this parameter existed
const auto cbor_bytes = json::to_cbor(json(c.bytes), eh::keep);
const auto msgpack_bytes = json::to_msgpack(json(c.bytes)); // to_msgpack has no error_handler; always pass-through
const auto ubjson_bytes = json::to_ubjson(json(c.bytes), false, false, eh::keep);
const auto bjdata_bytes = json::to_bjdata(json(c.bytes), false, false, json::bjdata_version_t::draft2, eh::keep);
const auto bson_bytes = [&c]
{
json jobj;
jobj["k"] = c.bytes;
return json::to_bson(jobj, eh::keep);
}();
// keep (the default): bytes are kept unchanged
CHECK(json::from_cbor(cbor_bytes).get<std::string>() == c.bytes);
CHECK(json::from_msgpack(msgpack_bytes).get<std::string>() == c.bytes);
CHECK(json::from_ubjson(ubjson_bytes).get<std::string>() == c.bytes);
CHECK(json::from_bjdata(bjdata_bytes).get<std::string>() == c.bytes);
CHECK(json::from_bson(bson_bytes)["k"].get<std::string>() == c.bytes);
// strict: parse_error.113, discarded (not thrown) when allow_exceptions is false
CHECK_THROWS_AS(utils::ignore_return_value(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict)), json::parse_error&);
CHECK(json::from_cbor(cbor_bytes, true, false, json::cbor_tag_handler_t::error, eh::strict).is_discarded());
CHECK_THROWS_AS(utils::ignore_return_value(json::from_msgpack(msgpack_bytes, true, true, eh::strict)), json::parse_error&);
CHECK(json::from_msgpack(msgpack_bytes, true, false, eh::strict).is_discarded());
CHECK_THROWS_AS(utils::ignore_return_value(json::from_ubjson(ubjson_bytes, true, true, eh::strict)), json::parse_error&);
CHECK(json::from_ubjson(ubjson_bytes, true, false, eh::strict).is_discarded());
CHECK_THROWS_AS(utils::ignore_return_value(json::from_bjdata(bjdata_bytes, true, true, eh::strict)), json::parse_error&);
CHECK(json::from_bjdata(bjdata_bytes, true, false, eh::strict).is_discarded());
CHECK_THROWS_AS(utils::ignore_return_value(json::from_bson(bson_bytes, true, true, eh::strict)), json::parse_error&);
CHECK(json::from_bson(bson_bytes, true, false, eh::strict).is_discarded());
// replace / ignore: match what dump() would have sanitized the same bytes to
for (const auto h :
{
eh::replace, eh::ignore
})
{
CAPTURE(static_cast<int>(h))
const std::string expected = dump_and_parse(c.bytes, h);
CHECK(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, h).get<std::string>() == expected);
CHECK(json::from_msgpack(msgpack_bytes, true, true, h).get<std::string>() == expected);
CHECK(json::from_ubjson(ubjson_bytes, true, true, h).get<std::string>() == expected);
CHECK(json::from_bjdata(bjdata_bytes, true, true, h).get<std::string>() == expected);
CHECK(json::from_bson(bson_bytes, true, true, h)["k"].get<std::string>() == expected);
}
}
}
SECTION("readers: object key")
{
for (const auto& c : ill_formed_cases())
{
CAPTURE(c.name)
json jobj;
jobj[c.bytes] = 1;
const auto cbor_bytes = json::to_cbor(jobj, eh::keep);
const auto msgpack_bytes = json::to_msgpack(jobj);
const auto ubjson_bytes = json::to_ubjson(jobj, false, false, eh::keep);
const auto bjdata_bytes = json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::keep);
const auto bson_bytes = json::to_bson(jobj, eh::keep);
CHECK(json::from_cbor(cbor_bytes).begin().key() == c.bytes);
CHECK(json::from_msgpack(msgpack_bytes).begin().key() == c.bytes);
CHECK(json::from_ubjson(ubjson_bytes).begin().key() == c.bytes);
CHECK(json::from_bjdata(bjdata_bytes).begin().key() == c.bytes);
CHECK(json::from_bson(bson_bytes).begin().key() == c.bytes);
CHECK_THROWS_AS(utils::ignore_return_value(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict)), json::parse_error&);
CHECK_THROWS_AS(utils::ignore_return_value(json::from_msgpack(msgpack_bytes, true, true, eh::strict)), json::parse_error&);
CHECK_THROWS_AS(utils::ignore_return_value(json::from_ubjson(ubjson_bytes, true, true, eh::strict)), json::parse_error&);
CHECK_THROWS_AS(utils::ignore_return_value(json::from_bjdata(bjdata_bytes, true, true, eh::strict)), json::parse_error&);
CHECK_THROWS_AS(utils::ignore_return_value(json::from_bson(bson_bytes, true, true, eh::strict)), json::parse_error&);
for (const auto h :
{
eh::replace, eh::ignore
})
{
CAPTURE(static_cast<int>(h))
const std::string expected = dump_and_parse(c.bytes, h);
CHECK(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, h).begin().key() == expected);
CHECK(json::from_msgpack(msgpack_bytes, true, true, h).begin().key() == expected);
CHECK(json::from_ubjson(ubjson_bytes, true, true, h).begin().key() == expected);
CHECK(json::from_bjdata(bjdata_bytes, true, true, h).begin().key() == expected);
CHECK(json::from_bson(bson_bytes, true, true, h).begin().key() == expected);
}
}
}
SECTION("well-formed UTF-8 is unaffected by error_handler")
{
const json jval = valid_sequence();
json jobj;
jobj[valid_sequence()] = valid_sequence();
for (const auto h : all_handlers())
{
CAPTURE(static_cast<int>(h))
CHECK(json::from_cbor(json::to_cbor(jval, h)).get<std::string>() == valid_sequence());
CHECK(json::from_msgpack(json::to_msgpack(jval, h)).get<std::string>() == valid_sequence());
CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, h)).get<std::string>() == valid_sequence());
CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, h)).get<std::string>() == valid_sequence());
CHECK(json::from_bson(json::to_bson(jobj, h)).begin().key() == valid_sequence());
CHECK(json::from_cbor(json::to_cbor(jval, eh::keep), true, true, json::cbor_tag_handler_t::error, h).get<std::string>() == valid_sequence());
CHECK(json::from_msgpack(json::to_msgpack(jval), true, true, h).get<std::string>() == valid_sequence());
}
}
SECTION("dump() with error_handler_t::keep writes raw bytes as is")
{
for (const auto& c : ill_formed_cases())
{
CAPTURE(c.name)
const json jval = c.bytes;
const std::string dumped = jval.dump(-1, ' ', false, eh::keep);
CHECK(dumped.find(c.bytes) != std::string::npos);
// even with ensure_ascii, the ill-formed bytes are written as is
const std::string dumped_ascii = jval.dump(-1, ' ', true, eh::keep);
CHECK(dumped_ascii.find(c.bytes) != std::string::npos);
}
// well-formed characters around an ill-formed sequence are still
// escaped as usual under ensure_ascii
const json mixed = valid_sequence() + ill_formed_cases()[1].bytes; // "é" + lone 0xFF
const std::string dumped_mixed = mixed.dump(-1, ' ', true, eh::keep);
CHECK(dumped_mixed.find("\\u00e9") != std::string::npos);
CHECK(dumped_mixed.find(ill_formed_cases()[1].bytes) != std::string::npos);
// the byte that ends an ill-formed sequence is read again, so a quote,
// a backslash, or a control character after it is still escaped, and
// a well-formed code point after it is escaped under ensure_ascii
for (const bool ensure_ascii :
{
false, true
})
{
CAPTURE(ensure_ascii)
CHECK(json("\xC3\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\\"\"");
CHECK(json("\xC3\\").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\\\\"");
CHECK(json("\xC3\n").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\n\"");
CHECK(json("\xE2\x82\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xE2\x82\\\"\"");
CHECK(json("\xFF\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xFF\\\"\"");
CHECK(json("a\xE2\x82").dump(-1, ' ', ensure_ascii, eh::keep) == "\"a\xE2\x82\"");
}
CHECK(json("\xC3\xC3\xA9").dump(-1, ' ', false, eh::keep) == "\"\xC3\xC3\xA9\"");
CHECK(json("\xC3\xC3\xA9").dump(-1, ' ', true, eh::keep) == "\"\xC3\\u00e9\"");
}
SECTION("to_msgpack defaults to keep; to_bon8 is not affected by error_handler")
{
const json jval = ill_formed_cases()[1].bytes; // lone 0xFF
// to_msgpack's error_handler defaults to keep, as MessagePack's spec
// allows any bytes in a str, so the bytes are passed through
CHECK(json::to_msgpack(jval) == json::to_msgpack(jval, eh::keep));
CHECK(json::from_msgpack(json::to_msgpack(jval)).get<std::string>() == ill_formed_cases()[1].bytes);
// the diagnostics context of an ill-formed key is the object
json jobj;
jobj["\xFF"] = 1;
CHECK_THROWS_WITH_AS(json::to_msgpack(jobj, eh::strict), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// to_bon8 has no error_handler parameter; UTF-8 is structural for
// BON8, so it always rejects ill-formed input
CHECK_THROWS_AS(json::to_bon8(jval), json::type_error&);
}
SECTION("allow_exceptions=false with error_handler_t::strict discards the value")
{
const auto bytes = json::to_cbor(json(ill_formed_cases()[0].bytes), eh::keep);
const json result = json::from_cbor(bytes, true, false, json::cbor_tag_handler_t::error, eh::strict);
CHECK(result.is_discarded());
}
SECTION("default parameters are unchanged")
{
const json jval = ill_formed_cases()[0].bytes;
// to_*: the default error_handler is keep, so ill-formed bytes are
// written unchanged, exactly as in release 3.12.0 (it is strict only
// if JSON_STRICT_BINARY_UTF8 is enabled, see
// unit-binary_utf8_strict.cpp)
CHECK(json::to_cbor(jval) == json::to_cbor(jval, eh::keep));
CHECK(json::to_ubjson(jval) == json::to_ubjson(jval, false, false, eh::keep));
CHECK(json::to_bjdata(jval) == json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep));
{
json jobj;
jobj["k"] = jval;
CHECK(json::to_bson(jobj) == json::to_bson(jobj, eh::keep));
}
// from_*: the default error_handler is keep, so ill-formed bytes are
// still accepted unchanged, exactly as in release 3.12.0
const auto cbor_bytes = json::to_cbor(jval, eh::keep);
CHECK(json::from_cbor(cbor_bytes).get<std::string>() == ill_formed_cases()[0].bytes);
const auto ubjson_bytes = json::to_ubjson(jval, false, false, eh::keep);
CHECK(json::from_ubjson(ubjson_bytes).get<std::string>() == ill_formed_cases()[0].bytes);
const auto bjdata_bytes = json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep);
CHECK(json::from_bjdata(bjdata_bytes).get<std::string>() == ill_formed_cases()[0].bytes);
const auto msgpack_bytes = json::to_msgpack(jval);
CHECK(json::from_msgpack(msgpack_bytes).get<std::string>() == ill_formed_cases()[0].bytes);
json bson_obj;
bson_obj["k"] = jval;
const auto bson_bytes = json::to_bson(bson_obj, eh::keep);
CHECK(json::from_bson(bson_bytes)["k"].get<std::string>() == ill_formed_cases()[0].bytes);
}
}
+146
View File
@@ -0,0 +1,146 @@
// __ _____ _____ _____
// __| | __| | | | 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"
// The binary writers check strings and object keys for valid UTF-8 only if
// JSON_STRICT_BINARY_UTF8 is enabled (planned to be the default in 4.0.0).
// Without it, they write the bytes unchanged, as before version 3.13.0; the
// tests for that are next to the other tests of each format.
#ifdef JSON_STRICT_BINARY_UTF8
#undef JSON_STRICT_BINARY_UTF8
#endif
#define JSON_STRICT_BINARY_UTF8 1
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cstdint>
#include <vector>
TEST_CASE("JSON_STRICT_BINARY_UTF8 (see #5529, #5651)")
{
SECTION("CBOR")
{
// a string value with ill-formed UTF-8 is rejected
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// a truncated multi-byte sequence
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
// an encoded surrogate half (U+D800)
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
// an overlong encoding of '.'
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
// an object key with ill-formed UTF-8 is rejected the same way
CHECK_THROWS_WITH_AS(json::to_cbor(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// binary values are not text and are unaffected
CHECK_NOTHROW(json::to_cbor(json::binary(std::vector<std::uint8_t>({0xFF}))));
// a value read back from CBOR with ill-formed bytes cannot be written
// back either (the reader is lenient regardless of the macro)
const json j = json::from_cbor(std::vector<std::uint8_t>({0x62, 0xc0, 0xae}));
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
}
SECTION("UBJSON")
{
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// a truncated multi-byte sequence
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
// an encoded surrogate half (U+D800)
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
// an overlong encoding of '.'
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
// an object key with ill-formed UTF-8 is rejected the same way
CHECK_THROWS_WITH_AS(json::to_ubjson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
}
SECTION("BJData")
{
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// a truncated multi-byte sequence
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
// an encoded surrogate half (U+D800)
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
// an overlong encoding of '.'
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
// an object key with ill-formed UTF-8 is rejected the same way
CHECK_THROWS_WITH_AS(json::to_bjdata(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
}
SECTION("BSON")
{
// to_bson() rejects the same kind of ill-formed string value, before
// any bytes reach the output adapter (the BSON document length
// prefix must be known up front, so nothing is written incrementally)
std::vector<std::uint8_t> out{0x42}; // a sentinel byte the writer must not touch
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}, nlohmann::detail::output_adapter<std::uint8_t>(out)), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}, nlohmann::detail::output_adapter<std::uint8_t>(out)), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
#endif
CHECK(out == std::vector<std::uint8_t> {0x42});
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
#endif
// a truncated multi-byte sequence
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC3"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC3"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
#endif
// an encoded surrogate half (U+D800)
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xED", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
#endif
// an overlong encoding of '.'
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
#endif
// an object key with ill-formed UTF-8 is rejected as well; unlike
// the reader (which never validates element names), the writer
// checks both string values and object keys
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bson(json {{"\xFF", 1}}), "[json.exception.type_error.316] (/\xFF) invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bson(json {{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
#endif
}
SECTION("an explicit error_handler overrides the default")
{
// the macro only changes the default of the error_handler parameter
CHECK(json::to_cbor(json("\xFF"), json::error_handler_t::keep) == std::vector<std::uint8_t>({0x61, 0xff}));
CHECK(json::to_ubjson(json("\xFF"), false, false, json::error_handler_t::keep) == std::vector<std::uint8_t>({'S', 'i', 1, 0xff}));
CHECK(json::to_bjdata(json("\xFF"), false, false, json::bjdata_version_t::draft2, json::error_handler_t::keep) == std::vector<std::uint8_t>({'S', 'i', 1, 0xff}));
CHECK(json::from_bson(json::to_bson(json{{"s", "\xFF"}}, json::error_handler_t::keep)) == json{{"s", "\xFF"}});
CHECK(json::to_cbor(json("\xFF"), json::error_handler_t::replace) == std::vector<std::uint8_t>({0x63, 0xef, 0xbf, 0xbd}));
}
SECTION("MessagePack and BON8 are unaffected")
{
// MessagePack allows any bytes in a str, so to_msgpack() still
// defaults to keep (strict only if passed explicitly); BON8 always
// checks, because the lead bytes mark where strings end
CHECK(json::to_msgpack(json("\xFF")) == std::vector<std::uint8_t>({0xa1, 0xff}));
CHECK_THROWS_AS(json::to_msgpack(json("\xFF"), json::error_handler_t::strict), json::type_error&);
CHECK_THROWS_AS(json::to_bon8(json("\xFF")), json::type_error&);
}
}
+37
View File
@@ -3906,6 +3906,43 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
CHECK(json::to_bjdata(j) == v); CHECK(json::to_bjdata(j) == v);
CHECK(json::from_bjdata(v) == j); CHECK(json::from_bjdata(v) == j);
} }
SECTION("ill-formed UTF-8 (see #5529, #5651)")
{
// none of the binary format specs requires a decoder to reject
// ill-formed UTF-8 in a text string, so a value whose bytes are
// not valid UTF-8 (0xC0 0xAE is an overlong encoding of '.')
// round-trips byte for byte as a string value; to_bjdata() writes
// the bytes unchanged, as before 3.13.0, unless
// JSON_STRICT_BINARY_UTF8 is enabled (see
// unit-binary_utf8_strict.cpp)
const std::vector<uint8_t> v = {'S', 'i', 2, 0xc0, 0xae};
json j;
CHECK_NOTHROW(j = json::from_bjdata(v));
REQUIRE(j.is_string());
CHECK(j.get_ref<const json::string_t&>() == std::string("\xc0\xae"));
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
CHECK(json::from_bjdata(json::to_bjdata(j)) == j);
// the same bytes as an object key round-trip as well
const std::vector<uint8_t> v_key = {'{', 'i', 2, 0xc0, 0xae, 'i', 1, '}'};
json j_key;
CHECK_NOTHROW(j_key = json::from_bjdata(v_key));
REQUIRE(j_key.is_object());
CHECK(j_key.contains(std::string("\xc0\xae")));
CHECK(json::from_bjdata(json::to_bjdata(j_key)) == j_key);
CHECK(json::from_bjdata(json::to_bjdata(json("\xFF"))) == json("\xFF"));
// a truncated multi-byte sequence
CHECK(json::from_bjdata(json::to_bjdata(json("\xC3"))) == json("\xC3"));
// an encoded surrogate half (U+D800)
CHECK(json::from_bjdata(json::to_bjdata(json("\xED\xA0\x80"))) == json("\xED\xA0\x80"));
// an overlong encoding of '.'
CHECK(json::from_bjdata(json::to_bjdata(json("\xC0\xAF"))) == json("\xC0\xAF"));
// an object key with ill-formed UTF-8 is kept the same way
CHECK(json::from_bjdata(json::to_bjdata(json{{"\xFF", 1}})) == json{{"\xFF", 1}});
}
} }
SECTION("Array Type") SECTION("Array Type")
+2
View File
@@ -786,6 +786,7 @@ TEST_CASE("Parse BON8 directly from a file using iterator and sentinel")
CHECK((parsed.is_object() || parsed.is_array())); CHECK((parsed.is_object() || parsed.is_array()));
} }
#if !defined(JSON_NOEXCEPTION) // corpus values that do not survive the round trip are skipped by catching the exception
TEST_CASE("BON8 round-trip invariants") TEST_CASE("BON8 round-trip invariants")
{ {
// This checks what the parse_bon8_fuzzer driver checks (see // This checks what the parse_bon8_fuzzer driver checks (see
@@ -818,6 +819,7 @@ TEST_CASE("BON8 round-trip invariants")
CHECK(json::to_bon8(j2) == vec); CHECK(json::to_bon8(j2) == vec);
} }
} }
#endif
TEST_CASE("BON8 roundtrips" * doctest::skip()) TEST_CASE("BON8 roundtrips" * doctest::skip())
{ {
+39
View File
@@ -62,6 +62,8 @@ class huge_string_t : public std::string
{ {
public: public:
using std::string::string; using std::string::string;
// inheriting std::string's constructors does not inherit its default constructor
huge_string_t() = default;
huge_string_t(const std::string& s) : std::string(s) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) huge_string_t(const std::string& s) : std::string(s) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
// returns a copy of @a s whose size() pretends to be huge // returns a copy of @a s whose size() pretends to be huge
@@ -154,6 +156,43 @@ TEST_CASE("BSON")
#endif #endif
} }
SECTION("ill-formed UTF-8 (see #5529, #5651)")
{
// a BSON document {"s": "\xC0\xAE"} (0xC0 0xAE is an overlong
// encoding of '.'); the BSON spec does not require a decoder to
// reject ill-formed UTF-8 in a string value, so the reader hands the
// bytes back unchanged
const std::vector<uint8_t> v =
{
0x0F, 0x00, 0x00, 0x00, // document length
0x02, 's', 0x00, // type 0x02 (string), key "s"
0x03, 0x00, 0x00, 0x00, // string length (including null)
0xc0, 0xae, 0x00, // string content and its null terminator
0x00 // document terminator
};
json j;
CHECK_NOTHROW(j = json::from_bson(v));
REQUIRE(j.is_object());
REQUIRE(j.contains("s"));
CHECK(j["s"].get_ref<const json::string_t&>() == std::string("\xc0\xae"));
// dump() still requires valid UTF-8 and throws for such a value
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
// to_bson() writes the bytes back unchanged, as before 3.13.0,
// unless JSON_STRICT_BINARY_UTF8 is enabled (see unit-binary_utf8_strict.cpp)
CHECK(json::from_bson(json::to_bson(j)) == j);
CHECK(json::from_bson(json::to_bson(json{{"s", "\xFF"}})) == json{{"s", "\xFF"}});
// a truncated multi-byte sequence
CHECK(json::from_bson(json::to_bson(json{{"s", "\xC3"}})) == json{{"s", "\xC3"}});
// an encoded surrogate half (U+D800)
CHECK(json::from_bson(json::to_bson(json{{"s", "\xED\xA0\x80"}})) == json{{"s", "\xED\xA0\x80"}});
// an overlong encoding of '.'
CHECK(json::from_bson(json::to_bson(json{{"s", "\xC0\xAF"}})) == json{{"s", "\xC0\xAF"}});
// an object key with ill-formed UTF-8 is kept as well
CHECK(json::from_bson(json::to_bson(json{{"\xFF", 1}})) == json{{"\xFF", 1}});
}
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON") SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
{ {
// out_of_range.412 is thrown from a single shared helper // out_of_range.412 is thrown from a single shared helper

Some files were not shown because too many files have changed in this diff Show More