Compare commits

..
Author SHA1 Message Date
Niels Lohmann 70a4b25cd2 MessagePack reader: enter the scalar run only when a scalar follows
As for CBOR: enter_msgpack_array tests the first byte and the remaining length
inline and calls the out-of-line run only for a scalar, so arrays of maps or
strings no longer pay for a call that reads nothing; the run's loop condition
moves to the end (do-while).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 17:52:47 +01:00
Niels Lohmann 5c7589dd15 Merge branch 'perf/binary-reader-byteswap' into perf/msgpack-scalar-runs
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 17:52:11 +01:00
Niels Lohmann 6b07a393fb Merge branch 'perf/cbor-scalar-runs' into perf/binary-reader-byteswap
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 17:51:59 +01:00
Niels Lohmann 8bbfb5ec1d CBOR reader: enter the scalar run only when a scalar follows
An array of containers or strings called the out-of-line run start once per
array only to leave it at once; on nested data (arrays of objects) that call
made reading slower than before the run reader. enter_cbor_array now tests the
first byte and the remaining length inline and calls the run only for a
scalar, so the run's loop condition moves to the end (do-while).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 17:51:51 +01:00
Niels Lohmann e5cd9db7b3 MessagePack reader: read runs of scalar array elements in one loop
As for CBOR: when an array opens and the input is contiguous, read the
integers, floats, booleans, and nils that follow in a tight loop instead
of going through parse_msgpack_internal and parse_msgpack_value for every
element. The loop stops at any other kind of value and when fewer than 9
bytes are left; those values are read by the general path as before.
Non-contiguous inputs are not affected.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 09:36:18 +01:00
Niels Lohmann cc31370a78 Binary readers: swap the bytes of floats with a single byteswap
byte_swap reversed float and double values byte by byte. GCC does not
merge that loop into one bswap, and C++23's std::byteswap accepts only
integers. Overloads for float and double now copy the bits into a
uint32_t/uint64_t, call detail::byteswap, and copy them back. This
applies to every binary reader that uses get_number.

In the CBOR reader, the single- and double-precision float cases call a
non-inlined helper. Without it, GCC stopped inlining the stream
adapter's get_character() into parse_cbor_internal, which made reading
from a std::istream about 0.5 % slower.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 08:43:35 +01:00
Niels Lohmann ce7498b54c CBOR reader: address review of the scalar-run reader
- parse_cbor_scalar_run: set the size explicitly in every case that
  reads a value, instead of relying on a default that every other case
  overwrites.
- Remove cbor_scalar_follows(): parse_cbor_scalar_run already returns on
  the same conditions, and without the pre-check SAX canada needs another
  8.5 % fewer instructions with GCC 13 (0.915 against the previous
  version), with no row more than 0.5 % worse (Mac and GCC).
- Restore the comment on why parse_cbor_internal copies the container
  frame.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 08:43:27 +01:00
Niels Lohmann e3e46253ed CBOR reader: read runs of scalar array elements in one loop
When a definite-length CBOR array opens and the input is contiguous, read
the integers, floats, booleans, and nulls that follow in a tight loop
instead of going through parse_cbor_internal's container logic and
parse_cbor_value's dispatch for every element. The loop stops at any other
kind of value and when fewer than 9 bytes are left; those values are read
by the general path exactly as before. Non-contiguous inputs are not
affected (tag dispatch on the bulk-scan capability).

Also pass a member empty string as the number source to number_float
instead of constructing a std::string for every float.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 00:27:27 +01:00
28 changed files with 1873 additions and 1872 deletions

No files matched your search

+3 -4
View File
@@ -68,10 +68,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
because a reference token is used on a primitive value.
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
Pointer path of the value within the document, and with
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
[`materialize()`](materialize.md) sets them).
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
+3 -3
View File
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
+8 -7
View File
@@ -80,13 +80,14 @@ Strong guarantee: there are no changes to the view or the document it refers to.
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws — typically `type_error.302`,
or whatever a user-provided `from_json()` throws.
The exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four bullets
above) carry the context of those of `BasicJsonType`: with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the
JSON Pointer path of the value within the document, and with
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text. An exception
thrown while converting through `materialize()` (the last bullet) is thrown by the materialized `BasicJsonType` value
instead: its path is relative to the root of the view's subtree, because `materialize()` builds only the subtree
(with parent pointers), so it is empty if the failing value is the subtree's root itself.
None of the exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four
bullets above) carry a
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent
pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole
document.
## Complexity
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
`"type must be string, but is array"`.
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
`"type must be number, but is string"`.
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
@@ -73,10 +73,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
number.
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
Pointer path of the value within the document, and with
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
[`materialize()`](materialize.md) sets them).
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
@@ -61,10 +61,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
`out_of_range` and returns `default_value`.
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
Pointer path of the value within the document, and with
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
[`materialize()`](materialize.md) sets them).
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
+15 -19
View File
@@ -134,21 +134,9 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
how it reports errors. Two more macros add context to the exceptions of views, as they do for those of
`#!cpp basic_json`:
- With [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md), an exception names the JSON Pointer path of the
value within the document.
- With [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), an exception names the bytes of
the value in the source text.
A view has neither parent pointers nor stored positions, so both are found from the document's index when the
exception is thrown. This takes time linear in the size of the document. One exception has neither:
`key()` of an iterator over an array or a primitive value
([`invalid_iterator.207`](../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does
not know the value it iterates over.
The other macros only affect the values `materialize()` produces, through `BasicJsonType`.
how it reports errors. The others either do not apply ([`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md)
needs parent pointers in `basic_json` values) or only affect the values `materialize()` produces, through
`BasicJsonType`.
## What is different
@@ -179,6 +167,10 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
[`at`](../api/basic_json_view/at.md) throws for every missing value.
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
below.
- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
`BasicJsonType` was built.
- **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
@@ -337,10 +329,14 @@ Its body may only use what views and `basic_json` have in common: the type check
a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
converted through `materialize()` as well.
One thing differs when a `from_json()` reads the view: members looked up by name (`at()`, `value()`, ... and thus
the macros) find the **first** member with a repeated key, see [Duplicate keys](#duplicate-keys);
`#!cpp materialize().get<T>()` would see the last one. Containers built from an object, such as `#!cpp std::map`,
keep the **last** value either way, as [`parse()`](../api/basic_json/parse.md) does.
Two things differ when a `from_json()` reads the view:
- **Duplicate keys.** Members looked up by name (`at()`, `value()`, ... and thus the macros) find the **first** member
with a repeated key, see [Duplicate keys](#duplicate-keys); `#!cpp materialize().get<T>()` would see the last one.
Containers built from an object, such as `#!cpp std::map`, keep the **last** value either way, as
[`parse()`](../api/basic_json/parse.md) does.
- **[`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md).** Exceptions thrown by the view carry no path to the
value.
`to_json()` is unaffected: it writes `basic_json` values, never views.
+1 -23
View File
@@ -431,28 +431,6 @@ std::nullptr_t exception_context(const View& /*unused*/) noexcept
return nullptr;
}
namespace view
{
// the path and bytes of the value of a view (defined in <nlohmann/detail/view/errors.hpp>)
template<typename View>
std::string diagnostics_of(const View& v);
} // namespace view
// what goes in front of the message of an exception about j: nothing for a
// basic_json value (exception_context() passes the value itself), the context
// basic_json would add (JSON_DIAGNOSTICS, JSON_DIAGNOSTIC_POSITIONS) for a view
template<typename BasicJsonType, enable_if_t<is_basic_json<BasicJsonType>::value, int> = 0>
const char* exception_prefix(const BasicJsonType& /*unused*/) noexcept
{
return "";
}
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
std::string exception_prefix(const View& v)
{
return view::diagnostics_of(v);
}
/*!
@brief throws because @a j does not have the type a conversion expects
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
@@ -464,7 +442,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha
{
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(j);
JSON_THROW(type_error::create(exception_id::type_mismatch, concat(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
}
/*!
+390 -25
View File
@@ -22,6 +22,7 @@
#include <bit> //byteswap
#endif
#include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/input/input_adapters.hpp>
#include <nlohmann/detail/input/json_sax.hpp>
@@ -741,11 +742,13 @@ class binary_reader
bool get_cbor_negative_integer()
{
NumberType number{};
if (JSON_HEDLEY_UNLIKELY(!get_number(number)))
{
return false;
}
return get_number(number) && emit_cbor_negative_integer(number);
}
/// pass the CBOR negative integer -1 - @a number to the SAX parser
template<typename NumberType>
bool emit_cbor_negative_integer(const NumberType number)
{
// the value is -1 - number, which fits into number_integer_t
// whenever number does; the outer cast undoes the integral promotion
// for number_integer_t types narrower than int
@@ -970,7 +973,7 @@ class binary_reader
case 0x95:
case 0x96:
case 0x97:
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
return enter_cbor_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
case 0x98: // array (one-byte uint8_t for n follows)
case 0x99: // array (two-byte uint16_t for n follow)
@@ -979,7 +982,7 @@ class binary_reader
{
std::uint64_t len{};
std::size_t size{};
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size);
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
}
case 0x9F: // array (indefinite length)
@@ -1133,16 +1136,10 @@ class binary_reader
return get_half_float(false);
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
{
float number{};
return get_number(number) && emit_float(number);
}
return get_cbor_float<float>();
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
{
double number{};
return get_number(number) && emit_float(number);
}
return get_cbor_float<double>();
default: // anything else (0xFF is handled inside the other types)
{
@@ -1151,6 +1148,16 @@ class binary_reader
}
}
/// read a single- or double-precision float (kept out of
/// parse_cbor_value, which is inlined into parse_cbor_internal)
template<typename NumberType>
JSON_HEDLEY_NEVER_INLINE
bool get_cbor_float()
{
NumberType number{};
return get_number(number) && emit_float(number);
}
/*!
@brief reports a nested indefinite-length CBOR string or byte array
@param[in] type_name name of the rejected string type
@@ -1676,6 +1683,181 @@ class binary_reader
return true;
}
/*!
@brief read a big-endian number of type @a NumberType at @a data
*/
template<typename NumberType>
NumberType load_big_endian(const unsigned char* data) const noexcept
{
NumberType result{};
std::memcpy(&result, data, sizeof(NumberType));
if (is_little_endian)
{
byte_swap(result);
}
return result;
}
/*!
@brief read the integers, floats, booleans, and nulls that follow in a
definite-length array, without the per-element bookkeeping of
@ref parse_cbor_internal
Stops at the end of the array, at any other kind of value, and when fewer
than 9 bytes (the longest of these values) are left in the input, leaving
those to @ref parse_cbor_value, which reads them the same way.
The caller has checked that an element remains, that at least 9 bytes are
left, and that the next byte begins a scalar.
@param[in,out] remaining the number of elements of the array not yet read
@return whether the SAX parser accepted the values
*/
bool parse_cbor_scalar_run(std::size_t& remaining)
{
do
{
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
const unsigned char head = data[0];
std::size_t size = 0;
bool accepted = true;
switch (head)
{
case 0x18:
size = 2;
accepted = emit_unsigned(data[1]);
break;
case 0x19:
size = 3;
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
break;
case 0x1A:
size = 5;
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
break;
case 0x1B:
size = 9;
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
break;
case 0x38:
size = 2;
accepted = emit_cbor_negative_integer(data[1]);
break;
case 0x39:
size = 3;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint16_t>(data + 1));
break;
case 0x3A:
size = 5;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint32_t>(data + 1));
break;
case 0x3B:
size = 9;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint64_t>(data + 1));
break;
case 0xF4:
size = 1;
accepted = sax->boolean(false);
break;
case 0xF5:
size = 1;
accepted = sax->boolean(true);
break;
case 0xF6:
size = 1;
accepted = sax->null();
break;
case 0xFA:
size = 5;
accepted = emit_float(load_big_endian<float>(data + 1));
break;
case 0xFB:
size = 9;
accepted = emit_float(load_big_endian<double>(data + 1));
break;
default:
if (head <= 0x17)
{
size = 1;
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
}
else if (head >= 0x20 && head <= 0x37)
{
size = 1;
accepted = sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - head));
}
else
{
// any other value is read by parse_cbor_value
return true;
}
break;
}
// as if read by get() and get_number(): current is the head byte
ia.bulk_skip(size);
chars_read += size;
current = head;
--remaining;
if (JSON_HEDLEY_UNLIKELY(!accepted))
{
return false;
}
}
while (remaining != 0 && ia.bulk_remaining() >= 9);
return true;
}
/*!
@brief open a definite-length array and read the scalars it begins with
(see @ref parse_cbor_scalar_run)
Only the start of an array is tried, which covers arrays of numbers
(e.g. coordinates) but costs arrays of other values only one test, not one
per element.
@param[in] len the number of elements
@return whether the SAX parser accepted the array and the values read
*/
bool enter_cbor_array(const std::size_t len)
{
return enter_array(len) && (len == 0 || !cbor_scalar_follows(std::integral_constant<bool, bulk_scan> {})
|| parse_cbor_array_start(std::integral_constant<bool, bulk_scan> {}));
}
/// whether a run of scalars can start at the next byte: at least 9 bytes
/// are left and the byte begins an integer, a float, a boolean, or null;
/// one test, so that an array of containers or strings makes no call
bool cbor_scalar_follows(std::true_type /*bulk*/) const
{
if (ia.bulk_remaining() < 9)
{
return false;
}
const auto head = static_cast<unsigned char>(*ia.bulk_data());
return head < 0x40 || head >= 0xF4;
}
/// input that is not contiguous: every value is read by parse_cbor_value
bool cbor_scalar_follows(std::false_type /*bulk*/) const noexcept
{
return false;
}
/// read the scalars the array just opened begins with; out of line, so
/// that @ref parse_cbor_value stays small enough to be inlined into
/// @ref parse_cbor_internal
JSON_HEDLEY_NEVER_INLINE
bool parse_cbor_array_start(std::true_type /*bulk*/)
{
return parse_cbor_scalar_run(container_stack.back().remaining);
}
/// input that is not contiguous: every value is read by parse_cbor_value
bool parse_cbor_array_start(std::false_type /*bulk*/) const noexcept
{
return true;
}
/*!
@brief read a CBOR value and everything nested inside it
@@ -1705,6 +1887,7 @@ class binary_reader
// pop_back() below, which destroys the container_stack element
// it would otherwise alias
const container_frame top = container_stack.back();
const bool is_object = top.is_object;
bool at_end = false;
if (top.remaining != npos)
@@ -1716,7 +1899,7 @@ class binary_reader
{
// claim the element about to be read
--container_stack.back().remaining;
if (top.is_object)
if (is_object)
{
get();
}
@@ -1729,7 +1912,7 @@ class binary_reader
// Testing for it consumes a byte, which is the first byte
// of the next element when it is not one.
at_end = (get() == 0xFF);
fetch = top.is_object;
fetch = is_object;
}
if (at_end)
@@ -1746,7 +1929,7 @@ class binary_reader
continue;
}
if (top.is_object)
if (is_object)
{
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
{
@@ -1971,7 +2154,7 @@ class binary_reader
case 0x9D:
case 0x9E:
case 0x9F:
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));
return enter_msgpack_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));
// fixstr
case 0xA0:
@@ -2102,13 +2285,13 @@ class binary_reader
case 0xDC: // array 16
{
std::uint16_t len{};
return get_number(len) && enter_array(static_cast<std::size_t>(len));
return get_number(len) && enter_msgpack_array(static_cast<std::size_t>(len));
}
case 0xDD: // array 32
{
std::uint32_t len{};
return get_number(len) && enter_array(conditional_static_cast<std::size_t>(len));
return get_number(len) && enter_msgpack_array(conditional_static_cast<std::size_t>(len));
}
case 0xDE: // map 16
@@ -2450,6 +2633,163 @@ class binary_reader
}
}
/*!
@brief read the integers, floats, booleans, and nils that follow in a
MessagePack array, without the per-element bookkeeping of
@ref parse_msgpack_internal
Stops at the end of the array, at any other kind of value, and when fewer
than 9 bytes (the longest of these values) are left in the input, leaving
those to @ref parse_msgpack_value, which reads them the same way.
The caller has checked that an element remains, that at least 9 bytes are
left, and that the next byte begins a scalar.
@param[in,out] remaining the number of elements of the array not yet read
@return whether the SAX parser accepted the values
*/
bool parse_msgpack_scalar_run(std::size_t& remaining)
{
do
{
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
const unsigned char head = data[0];
std::size_t size = 0;
bool accepted = true;
switch (head)
{
case 0xC0: // nil
size = 1;
accepted = sax->null();
break;
case 0xC2: // false
size = 1;
accepted = sax->boolean(false);
break;
case 0xC3: // true
size = 1;
accepted = sax->boolean(true);
break;
case 0xCA: // float 32
size = 5;
accepted = emit_float(load_big_endian<float>(data + 1));
break;
case 0xCB: // float 64
size = 9;
accepted = emit_float(load_big_endian<double>(data + 1));
break;
case 0xCC: // uint 8
size = 2;
accepted = emit_unsigned(data[1]);
break;
case 0xCD: // uint 16
size = 3;
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
break;
case 0xCE: // uint 32
size = 5;
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
break;
case 0xCF: // uint 64
size = 9;
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
break;
case 0xD0: // int 8
size = 2;
accepted = emit_signed(static_cast<std::int8_t>(data[1]));
break;
case 0xD1: // int 16
size = 3;
accepted = emit_signed(load_big_endian<std::int16_t>(data + 1));
break;
case 0xD2: // int 32
size = 5;
accepted = emit_signed(load_big_endian<std::int32_t>(data + 1));
break;
case 0xD3: // int 64
size = 9;
accepted = emit_signed(load_big_endian<std::int64_t>(data + 1));
break;
default:
if (head <= 0x7F)
{
// positive fixint
size = 1;
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
}
else if (head >= 0xE0)
{
// negative fixint
size = 1;
accepted = sax->number_integer(static_cast<std::int8_t>(head));
}
else
{
// any other value is read by parse_msgpack_value
return true;
}
break;
}
// as if read by get() and get_number(): current is the last byte
ia.bulk_skip(size);
chars_read += size;
current = data[size - 1];
--remaining;
if (JSON_HEDLEY_UNLIKELY(!accepted))
{
return false;
}
}
while (remaining != 0 && ia.bulk_remaining() >= 9);
return true;
}
/*!
@brief open a MessagePack array and read the scalars it begins with (see
@ref parse_msgpack_scalar_run)
@param[in] len the number of elements
@return whether the SAX parser accepted the array and the values read
*/
bool enter_msgpack_array(const std::size_t len)
{
return enter_array(len) && (len == 0 || !msgpack_scalar_follows(std::integral_constant<bool, bulk_scan> {})
|| parse_msgpack_array_start(std::integral_constant<bool, bulk_scan> {}));
}
/// whether a run of scalars can start at the next byte: at least 9 bytes
/// are left and the byte begins a fixint, nil, a boolean, a float, or a
/// sized integer (0xC0-0xD3); one test, so that an array of containers or
/// strings makes no call
bool msgpack_scalar_follows(std::true_type /*bulk*/) const
{
if (ia.bulk_remaining() < 9)
{
return false;
}
const auto head = static_cast<unsigned char>(*ia.bulk_data());
return head < 0x80 || head >= 0xE0 || (head >= 0xC0 && head <= 0xD3);
}
/// input that is not contiguous: every value is read by parse_msgpack_value
bool msgpack_scalar_follows(std::false_type /*bulk*/) const noexcept
{
return false;
}
/// read the scalars the array just opened begins with; out of line, so
/// that @ref parse_msgpack_value stays small enough to be inlined
JSON_HEDLEY_NEVER_INLINE
bool parse_msgpack_array_start(std::true_type /*bulk*/)
{
return parse_msgpack_scalar_run(container_stack.back().remaining);
}
/// input that is not contiguous: every value is read by parse_msgpack_value
bool parse_msgpack_array_start(std::false_type /*bulk*/) const noexcept
{
return true;
}
/*!
@brief read a MessagePack value and everything nested inside it
@@ -3691,13 +4031,13 @@ class binary_reader
return sax->null();
case 0xFB:
return sax->number_float(static_cast<number_float_t>(-1.0), "");
return sax->number_float(static_cast<number_float_t>(-1.0), no_source);
case 0xFC:
return sax->number_float(static_cast<number_float_t>(0.0), "");
return sax->number_float(static_cast<number_float_t>(0.0), no_source);
case 0xFD:
return sax->number_float(static_cast<number_float_t>(1.0), "");
return sax->number_float(static_cast<number_float_t>(1.0), no_source);
case 0xFF: // empty string
{
@@ -4066,6 +4406,27 @@ class binary_reader
return current;
}
/// reverse the byte order of a double with a single byteswap(): GCC does
/// not merge the byte-wise swap of the generic version into one
static void byte_swap(double& number) noexcept
{
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
std::uint64_t bits{};
std::memcpy(&bits, &number, sizeof(bits));
bits = byteswap(bits);
std::memcpy(&number, &bits, sizeof(bits));
}
/// reverse the byte order of a float with a single byteswap()
static void byte_swap(float& number) noexcept
{
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
std::uint32_t bits{};
std::memcpy(&bits, &number, sizeof(bits));
bits = byteswap(bits);
std::memcpy(&number, &bits, sizeof(bits));
}
template<class NumberType>
static void byte_swap(NumberType& number)
{
@@ -4201,7 +4562,7 @@ class binary_reader
{
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
}
return sax->number_float(result, "");
return sax->number_float(result, no_source);
}
/*!
@@ -4261,7 +4622,7 @@ class binary_reader
}();
return sax->number_float((half & 0x8000u) != 0
? static_cast<number_float_t>(-val)
: static_cast<number_float_t>(val), "");
: static_cast<number_float_t>(val), no_source);
}
/*!
@@ -4586,6 +4947,10 @@ class binary_reader
/// the SAX parser
json_sax_t* sax = nullptr;
/// the (empty) source text of every number_float event: binary formats
/// have none, and a string_t built for each float would cost more
const string_t no_source{};
/// the containers that have been opened and not closed yet; see @ref container_frame
std::vector<container_frame> container_stack{};
+1 -1
View File
@@ -347,7 +347,7 @@
return ej_pair.second == j; \
}); \
if (it != std::end(m)) e = it->first; \
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat(nlohmann::detail::exception_prefix(j), "enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
}
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
+11 -11
View File
@@ -157,7 +157,7 @@ class editor
node* const o = own(object);
if (o->kind != node_kind::object && o->kind != node_kind::null)
{
throw_type_error(site_of(object), exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
}
check_utf8(key.data(), key.size());
const encoded e = encode(std::forward<V>(value));
@@ -208,9 +208,9 @@ class editor
node* const a = own(array);
if (a->kind != node_kind::array)
{
throw_type_error(site_of(array), exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
}
check_index(a, idx, a->len);
check_index(idx, a->len);
const encoded e = encode(std::forward<V>(value));
node* const slot = const_cast<node*>(nav::value(element_at<true>(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast)
assign(slot, e, a, true);
@@ -225,7 +225,7 @@ class editor
node* const a = own(array);
if (a->kind != node_kind::array && a->kind != node_kind::null)
{
throw_type_error(site_of(array), exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
}
const encoded e = encode(std::forward<V>(value));
if (a->kind == node_kind::null)
@@ -249,9 +249,9 @@ class editor
node* const a = own(array);
if (a->kind != node_kind::array)
{
throw_type_error(site_of(array), exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
}
check_index(a, idx, a->len + 1);
check_index(idx, a->len + 1);
const encoded e = encode(std::forward<V>(value));
node* const slot = new_slot(e);
node* const h = block_of(m_doc, a, 1);
@@ -269,7 +269,7 @@ class editor
node* const o = own(object);
if (o->kind != node_kind::object)
{
throw_type_error(site_of(object), exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
}
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
{
@@ -287,9 +287,9 @@ class editor
node* const a = own(array);
if (a->kind != node_kind::array)
{
throw_type_error(site_of(array), exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
}
check_index(a, idx, a->len);
check_index(idx, a->len);
node* const h = block_of(m_doc, a, 0);
std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node));
--h->next;
@@ -316,11 +316,11 @@ class editor
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
}
void check_index(const node* array, std::size_t idx, std::size_t limit) const
static void check_index(std::size_t idx, std::size_t limit)
{
if (idx >= limit)
{
throw_array_index_out_of_range(error_site(&m_doc, array), idx);
throw_array_index_out_of_range(idx);
}
}
+8 -315
View File
@@ -10,15 +10,11 @@
#include <algorithm> // min
#include <cstddef> // size_t
#include <string> // string, to_string
#include <vector> // vector
#include <string> // string
#include <nlohmann/json.hpp>
#include <nlohmann/detail/view/builder.hpp>
#include <nlohmann/detail/view/document_data.hpp>
#include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/materialize.hpp>
#include <nlohmann/detail/view/node.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
@@ -26,292 +22,8 @@ namespace detail
namespace view
{
/*!
@brief the value an exception is about
basic_json passes the value to the exception, which then names its path
(JSON_DIAGNOSTICS) and its bytes in the source (JSON_DIAGNOSTIC_POSITIONS). A
view's value has no parent pointer and no stored positions: both are found
from the document and the node, when the exception is thrown (so only the
error path pays for them). Without the two macros, a site is empty.
*/
class error_site
{
public:
error_site() noexcept = default;
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
error_site(const document_data* d, const node* n) noexcept
: m_doc(d), m_node(n)
{}
const document_data* doc() const noexcept
{
return m_doc;
}
const node* value() const noexcept
{
return m_node;
}
private:
const document_data* m_doc = nullptr;
const node* m_node = nullptr;
#else
error_site(const document_data* /*d*/, const node* /*n*/) noexcept {}
#endif
};
/// the site of a view's value (the views make this a friend)
struct view_access
{
template<typename View>
static error_site site(const View& v) noexcept
{
return error_site(v.m_doc, v.m_node);
}
};
template<typename View>
error_site site_of(const View& v) noexcept
{
static_assert(is_json_view<View>::value, "the site of a value is only defined for views");
return view_access::site(v);
}
#if JSON_DIAGNOSTICS
/*!
@brief the JSON pointer tokens from the root of document @a d to node @a target
Nodes have no parent index, so the path is found by descending from the root:
before the first edit, only into the subtree that contains the target (a
container spans the next `next` nodes); after edits, elements can live in
other sequences, so every container is searched. Iterative (the nesting depth
is limited by memory only). False if the target is not found (then tokens is
unspecified).
*/
template<bool Editable>
bool value_path(const document_data& d, const node* target, std::vector<std::string>& tokens)
{
using nav = navigation<Editable>;
struct frame
{
const node* pos;
const node* end;
std::size_t index;
bool object;
};
const node* const root = d.nodes;
if (target == root)
{
return true;
}
if (!is_container(*root))
{
return false;
}
std::vector<frame> stack;
stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object});
// (tokens[i] leads from stack[i] to stack[i + 1], or to the target)
for (;;)
{
// (f is not used after the stack changes: pop_back() below, push_back() at the end)
frame& f = stack.back();
if (f.pos == f.end)
{
stack.pop_back();
if (stack.empty())
{
return false;
}
tokens.pop_back();
continue;
}
const node* entry = f.pos;
std::string token;
if (f.object)
{
// (as basic_json: the key up to a null byte)
token.assign(d.str(*entry), entry->len);
const std::size_t nul = token.find('\0');
if (nul != std::string::npos)
{
token.resize(nul);
}
++entry;
}
else
{
token = std::to_string(f.index);
}
const node* const child = nav::value(entry);
if (child == target)
{
tokens.push_back(token);
return true;
}
f.pos = document_data::after(entry);
++f.index;
if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next)))
{
tokens.push_back(token);
stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object});
}
}
}
#endif
#if JSON_DIAGNOSTIC_POSITIONS
/*!
@brief the bytes [start, end) of node @a n in the source, as materialize() sets them
Scalars as position_source::set_scalar; an array or object from its opening
bracket to its closing bracket, which is found by following the chain of last
elements to a scalar (or an empty container) and scanning from there to the
closing brackets. None (npos) for values written by edits, and for containers
whose last element has none, as with materialize().
*/
template<bool Editable>
void value_range(const document_data& d, const node* n, std::size_t& start, std::size_t& end)
{
using nav = navigation<Editable>;
position_source p;
start = std::string::npos;
end = std::string::npos;
if (!is_container(*n))
{
p.set_scalar(d, *n);
start = p.start;
end = p.end;
return;
}
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
{
return;
}
// the open brackets: n, then the last element as long as it is a non-empty container
std::size_t closing = 0;
std::size_t pos = std::string::npos;
for (const node* c = n;;)
{
if (!is_container(*c))
{
p.set_scalar(d, *c);
pos = p.end;
break;
}
if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size)
{
return;
}
++closing;
const node* const first = nav::first(d, c);
const node* const last_end = nav::end(d, c);
if (first == last_end)
{
pos = std::size_t{c->off} + 1;
break;
}
const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0;
const node* last = first;
for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset))
{
last = e;
}
c = nav::value(last + value_offset);
}
for (; closing != 0 && pos != std::string::npos; --closing)
{
pos = position_source::container_end(d.base[0], d.size, pos);
}
if (pos != std::string::npos)
{
start = n->off;
end = pos;
}
}
#endif
/// the context basic_json puts in front of the message of an exception about
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
/// if there is a path)
///
/// This and the throw helpers that take an error_site are templates, so that
/// the path and range code is instantiated only where an exception can be
/// thrown: instantiated in every translation unit (as a non-template inline
/// function makes it), it changed GCC's early inlining of unrelated view
/// functions, so that GCC 13 no longer inlined a recursive walk over a view
/// into itself (2.7 times the instructions).
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
template<typename Unused = void>
std::string diagnostics(const error_site site)
{
if (site.doc() == nullptr || site.value() == nullptr)
{
return "";
}
const document_data& d = *site.doc();
const bool edited = d.edits != nullptr;
std::string context;
#if JSON_DIAGNOSTICS
std::vector<std::string> tokens;
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
if (!found || tokens.empty())
{
return "";
}
std::string path;
for (const std::string& token : tokens)
{
path.push_back('/');
path += detail::escape(token);
}
context = concat('(', path, ") ");
#endif
#if JSON_DIAGNOSTIC_POSITIONS
std::size_t start = 0;
std::size_t end = 0;
if (edited)
{
value_range<true>(d, site.value(), start, end);
}
else
{
value_range<false>(d, site.value(), start, end);
}
if (start != std::string::npos && end != std::string::npos)
{
context += concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") ");
}
#endif
return context;
}
#else
template<typename Unused = void>
std::string diagnostics(const error_site /*site*/)
{
return "";
}
#endif
/// the context of an exception about view @a v (see exception_prefix())
template<typename View>
std::string diagnostics_of(const View& v)
{
return diagnostics<>(site_of(v));
}
// Exceptions are thrown out of line, so that the accessors that may throw stay
// small enough to be inlined. The overloads with an error_site put its context
// in front of the message.
//
// The overloads without one are for exceptions that are not about a value of a
// document: limits and invalid input of the editing and image functions, a
// discarded view (it has no node), a view of another document (its path would
// be one in that document), and those that basic_json creates without a
// context too (the errors of json_pointer, invalid UTF-8). The one exception
// about a value that has no site is key(), see view_iterator::key().
// small enough to be inlined.
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
{
@@ -333,24 +45,6 @@ std::string diagnostics_of(const View& v)
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
}
template<typename Unused = void>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_type_error(const error_site site, exception_id id, const char* prefix, const char* type)
{
NLOHMANN_VIEW_THROW(type_error::create(id, concat(diagnostics<>(site), prefix, type), nullptr));
}
template<typename Unused = void>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_out_of_range(const error_site site, exception_id id, const std::string& msg)
{
NLOHMANN_VIEW_THROW(out_of_range::create(id, concat(diagnostics<>(site), msg), nullptr));
}
template<typename Unused = void>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_invalid_iterator(const error_site site, exception_id id, const char* msg)
{
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, concat(diagnostics<>(site), msg), nullptr));
}
/// a parse error without a position (as those of json_pointer)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
{
@@ -359,23 +53,22 @@ template<typename Unused = void>
/// out_of_range.401: the array index @a idx is not below the size
template<typename Index>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const error_site site, const Index idx)
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
{
throw_out_of_range(site, exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
}
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
template<typename Unused = void>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_past_end(const error_site site, std::size_t size)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
{
throw_out_of_range(site, exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
}
/// out_of_range.403: the object has no member @a key
template<typename Key>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const error_site site, const Key& key)
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
{
throw_out_of_range(site, exception_id::key_not_found, concat("key '", key, "' not found"));
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
}
/// SAX handler of the error replay: accepts every event without storing it,
+1 -4
View File
@@ -105,10 +105,7 @@ class view_iterator
}
/// the key of the current object member; throws invalid_iterator.207 for
/// other iterators, like basic_json's iterators (but without the context
/// JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS add: the iterator does not
/// know the value it iterates over, and keeping it would make iterators
/// larger, which keeps GCC from inlining recursive walks)
/// other iterators, like basic_json's iterators
string_view_t key() const
{
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
+4 -5
View File
@@ -44,7 +44,6 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept
}
/// throws the exception json_pointer::array_index throws for this status
/// (without a context, as json_pointer::array_index)
template<typename StringType>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
{
@@ -84,7 +83,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
if (mode == pointer_mode::checked)
{
throw_key_not_found(site_of(cur), token);
throw_key_not_found(token);
}
return View();
}
@@ -96,7 +95,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
if (throwing)
{
throw_array_index_past_end(site_of(cur), cur.size());
throw_array_index_past_end(cur.size());
}
return View();
}
@@ -115,7 +114,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
if (mode == pointer_mode::checked)
{
throw_array_index_out_of_range(site_of(cur), idx);
throw_array_index_out_of_range(idx);
}
return View();
}
@@ -125,7 +124,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
if (throwing)
{
throw_out_of_range(site_of(cur), exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
}
return View();
}
+3 -3
View File
@@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::binary:
case value_t::discarded:
default:
throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
}
}
@@ -70,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
{
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
{
throw_type_error(site_of(v), exception_id::type_mismatch, "type must be array, but is ", v.type_name());
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
}
std::vector<U, A> r;
r.reserve(v.size());
@@ -88,7 +88,7 @@ Map map_value(const View& v)
{
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
{
throw_type_error(site_of(v), exception_id::type_mismatch, "type must be object, but is ", v.type_name());
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
}
Map r;
for (auto it = v.begin(); it != v.end(); ++it)
+26 -33
View File
@@ -285,7 +285,7 @@ class basic_json_view
{
return {};
}
detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
}
return lookup(key);
}
@@ -311,7 +311,7 @@ class basic_json_view
{
return {};
}
detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
}
return idx < m_node->len ? element(idx) : basic_json_view();
}
@@ -344,12 +344,12 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
}
const basic_json_view r = lookup(key);
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
{
detail::view::throw_key_not_found(site(), std::string(key.data(), key.size()));
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
}
return r;
}
@@ -370,11 +370,11 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{
detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
}
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
{
detail::view::throw_array_index_out_of_range(site(), idx);
detail::view::throw_array_index_out_of_range(idx);
}
return element(idx);
}
@@ -402,7 +402,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
}
const basic_json_view r = lookup(key);
return r.is_discarded() ? default_value : r.template get<T>();
@@ -412,7 +412,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
}
// (the default is made only if there is no such member)
const basic_json_view r = lookup(key);
@@ -427,7 +427,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
}
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? default_value : r.template get<T>();
@@ -437,7 +437,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
}
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
@@ -450,7 +450,7 @@ class basic_json_view
const iterator it = begin();
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
{
detail::view::throw_invalid_iterator(site(), detail::exception_id::iterator_value_unavailable, "cannot get value");
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
}
return *it;
}
@@ -606,7 +606,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
{
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
}
return {m_doc->str(*m_node), m_node->len};
}
@@ -618,7 +618,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
{
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
}
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
}
@@ -764,19 +764,12 @@ class basic_json_view
template<typename, view_mode> friend class basic_json_view;
template<typename, typename> friend class detail::view::editor;
template<typename, typename> friend class detail::view::view_side;
friend struct detail::view::view_access;
friend iterator;
basic_json_view(const document_data* d, const node* n) noexcept
: m_doc(d), m_node(n)
{}
/// this value as the context of an exception
detail::view::error_site site() const noexcept
{
return detail::view::error_site(m_doc, m_node);
}
using json_side_t = detail::view::json_side<BasicJsonType, string_view_t>;
static detail::view::view_side<BasicJsonType, basic_json_view> side(const basic_json_view& v) noexcept
@@ -883,7 +876,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
{
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
}
return (m_node->flags & detail::view::node_flags::is_true) != 0;
}
@@ -893,7 +886,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
{
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
}
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
}
@@ -902,7 +895,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
{
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
}
return nullptr;
}
@@ -1316,7 +1309,7 @@ class basic_json_document
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
view_type set(view_type array, I idx, V && value)
{
return editor().set(array, index(array, idx), std::forward<V>(value));
return editor().set(array, index(idx), std::forward<V>(value));
}
/// set the value at a JSON pointer: its parent must exist; an object
@@ -1353,7 +1346,7 @@ class basic_json_document
const std::size_t idx = pointer_index(token);
if (idx >= 0xFFFFFFFFu)
{
detail::view::throw_array_index_out_of_range(detail::view::site_of(parent), idx);
detail::view::throw_array_index_out_of_range(idx);
}
for (std::size_t i = 0; i < idx; ++i)
{
@@ -1387,7 +1380,7 @@ class basic_json_document
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
view_type insert(view_type array, I idx, V && value)
{
return editor().insert(array, index(array, idx), std::forward<V>(value));
return editor().insert(array, index(idx), std::forward<V>(value));
}
/// remove all members with this key; returns their number
@@ -1400,7 +1393,7 @@ class basic_json_document
template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
void erase(view_type array, I idx)
{
editor().erase(array, index(array, idx));
editor().erase(array, index(idx));
}
/// remove the value at a JSON pointer; returns the number of removed
@@ -1434,25 +1427,25 @@ class basic_json_document
return detail::view::editor<BasicJsonType, view_type>(*m_data);
}
/// an index into @a array (out_of_range.401 if negative)
/// an index (out_of_range.401 if negative)
template<typename I>
static std::size_t index(const view_type& array, I idx)
static std::size_t index(I idx)
{
return index(array, idx, std::is_signed<I> {});
return index(idx, std::is_signed<I> {});
}
template<typename I>
static std::size_t index(const view_type& array, I idx, std::true_type /*signed*/)
static std::size_t index(I idx, std::true_type /*signed*/)
{
if (idx < 0)
{
detail::view::throw_array_index_out_of_range(detail::view::site_of(array), idx);
detail::view::throw_array_index_out_of_range(idx);
}
return static_cast<std::size_t>(idx);
}
template<typename I>
static std::size_t index(const view_type& /*array*/, I idx, std::false_type /*unsigned*/)
static std::size_t index(I idx, std::false_type /*unsigned*/)
{
return static_cast<std::size_t>(idx);
}
+618 -274
View File
File diff suppressed because it is too large. Load diff
+505 -826
View File
File diff suppressed because it is too large. Load diff
-21
View File
@@ -15,7 +15,6 @@
#include <nlohmann/json_view.hpp>
#include <algorithm> // any_of
#include <cstddef> // size_t
#include <functional> // function
#include <random> // mt19937
#include <string> // string
@@ -89,26 +88,6 @@ inline bool has_duplicate_keys(const nlohmann::ordered_json_view& v)
});
}
// the message of an exception about the value at path (a JSON pointer, "" for
// the root) whose source bytes are [start, end): the context basic_json adds
// with JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS, if enabled, then what
inline std::string message(const std::string& id, const std::string& path, std::size_t start, std::size_t end, const std::string& what)
{
std::string context;
#if JSON_DIAGNOSTIC_POSITIONS
context = "(bytes " + std::to_string(start) + "-" + std::to_string(end) + ") ";
#else
static_cast<void>(start);
static_cast<void>(end);
#endif
#if JSON_DIAGNOSTICS
context = path.empty() ? "" : "(" + path + ") " + context;
#else
static_cast<void>(path);
#endif
return "[json.exception." + id + "] " + context + what;
}
#if !defined(JSON_NOEXCEPTION)
// the exception a call throws, or "" if it throws none
inline std::string exception_of_call(const std::function<void()>& f)
+74
View File
@@ -29,7 +29,9 @@ using nlohmann::json;
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
#include "sax_event_loggers.hpp"
using utils::SaxCountdown;
using utils::SaxEventLogger;
TEST_CASE("CBOR")
@@ -2446,6 +2448,78 @@ TEST_CASE("CBOR SAX parsing stops at every event")
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
}
TEST_CASE("CBOR arrays of scalars from contiguous and non-contiguous input")
{
// contiguous input reads the integers, floats, booleans, and nulls an
// array begins with in one loop, other input value by value; both must
// give the same events, value, or error for the complete document and for
// every truncation of it, and stop at every event a SAX handler rejects
const std::vector<std::uint8_t> cbor =
{
0xA3, // map(3)
0x61, 'a', // "a"
0x98, 0x16, // array(22)
0x00, 0x17, 0x18, 0xFF, 0x19, 0x01, 0x00, // 0, 23, 255, 256
0x1A, 0x00, 0x01, 0x00, 0x00, // 65536
0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615
0x20, 0x37, 0x38, 0xFF, 0x39, 0x01, 0x00, // -1, -24, -256, -257
0x3A, 0x00, 0x01, 0x00, 0x00, // -65537
0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -18446744073709551616 (a float)
0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -9223372036854775808
0xF4, 0xF5, 0xF6, // false, true, null
0xFA, 0x3F, 0xC0, 0x00, 0x00, // 1.5 (float32)
0xFB, 0xBF, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, // -0.1
0xF9, 0x3C, 0x00, // 1.0 (float16, ends the loop)
0x01, 0x61, 'a', // 1, "a"
0xFB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.5
0x61, 'b', // "b"
0x83, // array(3)
0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x80, // []
0x61, 'c', // "c"
0x84, 0xA0, 0x01, 0xC1, 0x02, 0x81, 0x03 // [{}, 1, tag(1) 2, [3]]
};
const auto tags = json::cbor_tag_handler_t::ignore;
const json j = json::from_cbor(cbor, true, true, tags);
CHECK(j["a"].size() == 22);
CHECK(j["a"][5] == 18446744073709551615u);
CHECK(j["a"][11].is_number_float());
CHECK(j["a"][11] == -18446744073709551616.0);
CHECK(j["a"][12] == (std::numeric_limits<std::int64_t>::min)());
CHECK(j["a"][17] == -0.1);
CHECK(j["b"] == json::parse("[[1.25, -3.75], [1.25, -3.75], []]"));
CHECK(j["c"] == json::parse("[{}, 1, 2, [3]]"));
for (std::size_t length = 0; length <= cbor.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(cbor.begin(), cbor.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_cbor(contiguous, true, false, tags).dump() == json::from_cbor(bytewise.begin(), bytewise.end(), true, false, tags).dump());
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::cbor, true, false, false, tags)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags));
CHECK(from_contiguous.events == from_bytewise.events);
}
for (int events = 0; events <= 60; ++events)
{
CAPTURE(events)
const std::list<std::uint8_t> bytewise(cbor.begin(), cbor.end());
SaxCountdown from_contiguous(events);
SaxCountdown from_bytewise(events);
CHECK(json::sax_parse(cbor, &from_contiguous, json::input_format_t::cbor, true, false, false, tags)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags));
}
}
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
{
// Reading an indefinite-length string or byte array used to call itself
+71 -39
View File
@@ -225,7 +225,7 @@ TEST_CASE("json_view")
#if JSON_DIAGNOSTICS
// the parents are set, so errors name the path
const json m = materialized<json_document>(R"({"a":{"b":[1]}})");
CHECK_THROWS_WITH_AS(m.at("a").at("b").at(0).at("x"), json_view_test::message("type_error.304", "/a/b/0", 11, 12, "cannot use at() with number").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(m.at("a").at("b").at(0).at("x"), "[json.exception.type_error.304] (/a/b/0) cannot use at() with number", json::type_error&);
#endif
}
@@ -750,6 +750,19 @@ std::string exception_of(F f)
return "";
}
// the exception a call on a basic_json value throws, without the byte range of
// the value (JSON_DIAGNOSTIC_POSITIONS), which the exceptions of views lack
template<typename F>
std::string json_exception_of(F f)
{
std::string what = exception_of(f);
const std::size_t at = what.find("] (bytes ");
if (at != std::string::npos)
{
what.erase(at + 2, what.find(") ", at) - at);
}
return what;
}
#endif
// compares a view with the ordered_json value materialize() gives for it:
@@ -958,25 +971,24 @@ TEST_CASE("json_view element access and iteration")
#if !defined(JSON_NOEXCEPTION)
if (!j.is_object())
{
CHECK(exception_of([&] { static_cast<void>(v["a"]); }) == exception_of([&] { static_cast<void>(j["a"]); }));
CHECK(exception_of([&] { static_cast<void>(v["a"]); }) == json_exception_of([&] { static_cast<void>(j["a"]); }));
}
if (!j.is_array())
{
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == exception_of([&] { static_cast<void>(j[0]); }));
CHECK(exception_of([&] { static_cast<void>(v[0]); }) == json_exception_of([&] { static_cast<void>(j[0]); }));
}
CHECK(exception_of([&] { static_cast<void>(v.at("a")); }) == exception_of([&] { static_cast<void>(j.at("a")); }));
CHECK(exception_of([&] { static_cast<void>(v.at("missing")); }) == exception_of([&] { static_cast<void>(j.at("missing")); }));
CHECK(exception_of([&] { static_cast<void>(v.at(0)); }) == exception_of([&] { static_cast<void>(j.at(0)); }));
CHECK(exception_of([&] { static_cast<void>(v.at(5)); }) == exception_of([&] { static_cast<void>(j.at(5)); }));
CHECK(exception_of([&] { static_cast<void>(v.at("a")); }) == json_exception_of([&] { static_cast<void>(j.at("a")); }));
CHECK(exception_of([&] { static_cast<void>(v.at("missing")); }) == json_exception_of([&] { static_cast<void>(j.at("missing")); }));
CHECK(exception_of([&] { static_cast<void>(v.at(0)); }) == json_exception_of([&] { static_cast<void>(j.at(0)); }));
CHECK(exception_of([&] { static_cast<void>(v.at(5)); }) == json_exception_of([&] { static_cast<void>(j.at(5)); }));
if (!(j.is_object() && j.empty())) // (key() of an end iterator)
{
// (without the context of basic_json: view iterators do not know the value they iterate over)
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == (j.is_object() ? "" : "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators"));
CHECK(exception_of([&] { static_cast<void>(v.begin().key()); }) == json_exception_of([&] { static_cast<void>(j.begin().key()); }));
}
if (!j.empty() || j.is_null())
{
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == exception_of([&] { static_cast<void>(j.front()); }));
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == exception_of([&] { static_cast<void>(j.back()); }));
CHECK(exception_of([&] { static_cast<void>(v.front()); }) == json_exception_of([&] { static_cast<void>(j.front()); }));
CHECK(exception_of([&] { static_cast<void>(v.back()); }) == json_exception_of([&] { static_cast<void>(j.back()); }));
}
#endif
CHECK(v.contains("a") == j.contains("a"));
@@ -988,8 +1000,8 @@ TEST_CASE("json_view element access and iteration")
const json_document d = json_document::parse(R"({"a":[]})");
CHECK(d.root()["b"].is_discarded());
CHECK(d.root()["a"][0].is_discarded());
CHECK_THROWS_WITH_AS(d.root()["a"].front(), json_view_test::message("invalid_iterator.214", "/a", 5, 7, "cannot get value").c_str(), json::invalid_iterator&);
CHECK_THROWS_WITH_AS(d.root()["a"].back(), json_view_test::message("invalid_iterator.214", "/a", 5, 7, "cannot get value").c_str(), json::invalid_iterator&);
CHECK_THROWS_WITH_AS(d.root()["a"].front(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
CHECK_THROWS_WITH_AS(d.root()["a"].back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
const json_view invalid{};
CHECK(invalid.begin() == invalid.end());
CHECK(std::string(invalid.type_name()) == "discarded");
@@ -1020,10 +1032,10 @@ TEST_CASE("json_view element access and iteration")
CHECK(v[json_view::json_pointer("/a/b/5")][0].is_discarded());
#if !defined(JSON_NOEXCEPTION)
// type errors on values that are not discarded stay
CHECK_THROWS_WITH_AS(v[0], json_view_test::message("type_error.305", "", 0, 29, "cannot use operator[] with a numeric argument with object").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(v["a"]["b"]["c"], json_view_test::message("type_error.305", "/a/b", 10, 17, "cannot use operator[] with a string argument with array").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(v["s"]["c"], json_view_test::message("type_error.305", "/s", 23, 28, "cannot use operator[] with a string argument with string").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(v["s"][0], json_view_test::message("type_error.305", "/s", 23, 28, "cannot use operator[] with a numeric argument with string").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(v[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&);
CHECK_THROWS_WITH_AS(v["a"]["b"]["c"], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", json::type_error&);
CHECK_THROWS_WITH_AS(v["s"]["c"], "[json.exception.type_error.305] cannot use operator[] with a string argument with string", json::type_error&);
CHECK_THROWS_WITH_AS(v["s"][0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with string", json::type_error&);
CHECK_THROWS_AS(v["a"]["b"][0]["c"], json::type_error&);
CHECK_THROWS_AS(v["s"][json_view::json_pointer("/x")], json::out_of_range&);
// at() keeps throwing on a discarded view
@@ -1098,9 +1110,9 @@ TEST_CASE("json_view element access and iteration")
CHECK(v[(std::numeric_limits<std::size_t>::max)()].is_discarded());
CHECK(v[std::numeric_limits<int>::max()].is_discarded());
#if !defined(JSON_NOEXCEPTION)
CHECK_THROWS_WITH_AS(v.at(3), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(v.at(3u), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(v.at(std::int64_t(3)), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(v.at(3), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
CHECK_THROWS_WITH_AS(v.at(3u), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
CHECK_THROWS_WITH_AS(v.at(std::int64_t(3)), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
CHECK_THROWS_AS(v.at(-1), json::out_of_range&);
CHECK_THROWS_AS(v.at(-1L), json::out_of_range&);
CHECK_THROWS_AS(v.at(std::int64_t(-1)), json::out_of_range&);
@@ -1234,6 +1246,26 @@ TEST_CASE("json_view element access and iteration")
namespace
{
#if !defined(JSON_NOEXCEPTION)
// an exception message without the context that basic_json adds with
// JSON_DIAGNOSTICS ("(/path) ") and JSON_DIAGNOSTIC_POSITIONS ("(bytes 1-2) ");
// the view's exceptions have no such context
std::string without_path(std::string msg)
{
for (const char* prefix :
{"] (/", "] (bytes "
})
{
const std::size_t open = msg.find(prefix);
if (open != std::string::npos)
{
msg.erase(open + 2, msg.find(") ", open) + 2 - (open + 2));
}
}
return msg;
}
#endif
// the bits of a float, to compare values bit for bit
std::uint64_t bits(double x)
{
@@ -1304,18 +1336,18 @@ void check_values(const ordered_json_view& v, const ordered_json& j, const std::
// conversions to the wrong type throw what basic_json throws
if (!j.is_number())
{
CHECK(exception_of([&] { static_cast<void>(v.get<int>()); }) == exception_of([&] { static_cast<void>(j.get<int>()); }));
CHECK(exception_of([&] { static_cast<void>(v.get<int>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<int>()); })));
}
CHECK(exception_of([&] { static_cast<void>(v.get<bool>()); }) == exception_of([&] { static_cast<void>(j.get<bool>()); }));
CHECK(exception_of([&] { static_cast<void>(v.get<std::string>()); }) == exception_of([&] { static_cast<void>(j.get<std::string>()); }));
CHECK(exception_of([&] { static_cast<void>(v.get<std::nullptr_t>()); }) == exception_of([&] { static_cast<void>(j.get<std::nullptr_t>()); }));
CHECK(exception_of([&] { static_cast<void>(v.get<bool>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<bool>()); })));
CHECK(exception_of([&] { static_cast<void>(v.get<std::string>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::string>()); })));
CHECK(exception_of([&] { static_cast<void>(v.get<std::nullptr_t>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::nullptr_t>()); })));
if (!j.is_array())
{
CHECK(exception_of([&] { static_cast<void>(v.get<std::vector<int>>()); }) == exception_of([&] { static_cast<void>(j.get<std::vector<int>>()); }));
CHECK(exception_of([&] { static_cast<void>(v.get<std::vector<int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::vector<int>>()); })));
}
if (!j.is_object())
{
CHECK(exception_of([&] { static_cast<void>(v.get<std::map<std::string, int>>()); }) == exception_of([&] { static_cast<void>(j.get<std::map<std::string, int>>()); }));
CHECK(exception_of([&] { static_cast<void>(v.get<std::map<std::string, int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::map<std::string, int>>()); })));
}
#endif
@@ -1458,8 +1490,8 @@ TEST_CASE("json_view values")
CHECK(v[3].number_token() == "123456789012345678901234567890");
CHECK(v[4].number_token() == "-12");
CHECK(v[5].number_token() == "7");
CHECK_THROWS_WITH_AS(v[6].number_token(), json_view_test::message("type_error.302", "/6", 56, 59, "type must be number, but is string").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(v.get_string(), json_view_test::message("type_error.302", "", 0, 60, "type must be string, but is array").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(v[6].number_token(), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
CHECK_THROWS_WITH_AS(v.get_string(), "[json.exception.type_error.302] type must be string, but is array", json::type_error&);
}
SECTION("conversions")
@@ -1533,15 +1565,15 @@ TEST_CASE("json_view values")
CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0));
CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3));
#if !defined(JSON_NOEXCEPTION)
CHECK(exception_of([&] { static_cast<void>(v["o"]["x"].value("k", 0)); }) == exception_of([&] { static_cast<void>(j["o"]["x"].value("k", 0)); }));
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == exception_of([&] { static_cast<void>(j.value("s", 0)); }));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == exception_of([&] { static_cast<void>(j["n"].value("x", 0)); }));
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); }));
CHECK(exception_of([&] { static_cast<void>(v["o"]["x"].value("k", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["o"]["x"].value("k", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
// string defaults: the member is not a string, or the value is neither an object nor an array
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == exception_of([&] { static_cast<void>(j.value("n", "default")); }));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == exception_of([&] { static_cast<void>(j["n"].value("x", "default")); }));
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); }));
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); }));
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value("n", "default")); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", "default")); })));
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); })));
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); })));
#endif
// (defaults and members longer than the short string optimization)
CHECK(v.value("s", "a default longer than fifteen characters") == "text");
@@ -1600,7 +1632,7 @@ TEST_CASE("json_view JSON pointers")
{
CAPTURE(pointer)
const json::json_pointer p(pointer);
const std::string at_error = exception_of([&] { static_cast<void>(j.at(p)); });
const std::string at_error = without_path(exception_of([&] { static_cast<void>(j.at(p)); }));
CHECK(exception_of([&] { static_cast<void>(v.at(p)); }) == at_error);
if (at_error.empty())
{
@@ -1614,7 +1646,7 @@ TEST_CASE("json_view JSON pointers")
}
else
{
CHECK(exception_of([&] { static_cast<void>(v[p]); }) == exception_of([&] { static_cast<void>(j[p]); }));
CHECK(exception_of([&] { static_cast<void>(v[p]); }) == without_path(exception_of([&] { static_cast<void>(j[p]); })));
}
// (basic_json::contains() throws out_of_range.404 for an empty
// array index token, although it is not meant to throw; the view
@@ -1625,7 +1657,7 @@ TEST_CASE("json_view JSON pointers")
static_cast<void>(found);
});
CHECK(v.contains(p) == (contains_error.empty() && j.contains(p)));
CHECK(exception_of([&] { static_cast<void>(v.value(p, 5)); }) == exception_of([&] { static_cast<void>(j.value(p, 5)); }));
CHECK(exception_of([&] { static_cast<void>(v.value(p, 5)); }) == without_path(exception_of([&] { static_cast<void>(j.value(p, 5)); })));
}
}
#endif
+1 -1
View File
@@ -462,7 +462,7 @@ TEST_CASE("json_view large objects")
CHECK(v[""].get_string() == "empty key");
CHECK(v["k1"].get<int>() == 1); // the first of duplicate keys, as for small objects
CHECK(!v.contains("missing"));
CHECK_THROWS_WITH_AS(v.at("missing"), json_view_test::message("out_of_range.403", "", 0, text.size(), "key 'missing' not found").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
CHECK(v == j);
CHECK(v.materialize() == j);
}
+11 -11
View File
@@ -532,18 +532,18 @@ TEST_CASE("json_view edits: errors")
CHECK_THROWS_WITH_AS(d.set(other_editable.root(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
CHECK_THROWS_WITH_AS(d.set(json_editable_view(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
CHECK_THROWS_WITH_AS(d.set(root["a"], "k", 1), json_view_test::message("type_error.305", "/a", 21, 27, "cannot use operator[] with a string argument with array").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(d.set(root["o"], 0, 1), json_view_test::message("type_error.305", "/o", 6, 14, "cannot use operator[] with a numeric argument with object").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(d.set(root["a"], 2, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 2 is out of range").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(d.set(root["a"], -1, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index -1 is out of range").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(d.push_back(root["o"], 1), json_view_test::message("type_error.308", "/o", 6, 14, "cannot use push_back() with object").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(d.insert(root["n"], 0, 1), json_view_test::message("type_error.309", "/n", 34, 35, "cannot use insert() with number").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(d.insert(root["a"], 3, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 3 is out of range").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(d.erase(root["n"], "k"), json_view_test::message("type_error.307", "/n", 34, 35, "cannot use erase() with number").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(d.erase(root["o"], 0), json_view_test::message("type_error.307", "/o", 6, 14, "cannot use erase() with object").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(d.erase(root["a"], 2), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 2 is out of range").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(d.set(root["a"], "k", 1), "[json.exception.type_error.305] cannot use operator[] with a string argument with array", json::type_error&);
CHECK_THROWS_WITH_AS(d.set(root["o"], 0, 1), "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&);
CHECK_THROWS_WITH_AS(d.set(root["a"], 2, 1), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&);
CHECK_THROWS_WITH_AS(d.set(root["a"], -1, 1), "[json.exception.out_of_range.401] array index -1 is out of range", json::out_of_range&);
CHECK_THROWS_WITH_AS(d.push_back(root["o"], 1), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&);
CHECK_THROWS_WITH_AS(d.insert(root["n"], 0, 1), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&);
CHECK_THROWS_WITH_AS(d.insert(root["a"], 3, 1), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
CHECK_THROWS_WITH_AS(d.erase(root["n"], "k"), "[json.exception.type_error.307] cannot use erase() with number", json::type_error&);
CHECK_THROWS_WITH_AS(d.erase(root["o"], 0), "[json.exception.type_error.307] cannot use erase() with object", json::type_error&);
CHECK_THROWS_WITH_AS(d.erase(root["a"], 2), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&);
CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("")), "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("/missing/x")), json_view_test::message("out_of_range.403", "", 0, 47, "key 'missing' not found").c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("/missing/x")), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
CHECK_THROWS_WITH_AS(d.set(json::json_pointer("/a/01"), 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
CHECK_THROWS_WITH_AS(d.set(root, json_editable_view()), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
CHECK_THROWS_WITH_AS(d.set(root, json::binary({1, 2})), "[json.exception.type_error.319] cannot store a binary value in a json_document", json::type_error&);
+7 -4
View File
@@ -337,7 +337,8 @@ TEST_CASE("json_view from_json")
CHECK(d.root().get<std::unordered_map<std::string, int>>().at("a") == 3);
}
#if !defined(JSON_NOEXCEPTION)
// (the exceptions of views have neither the path nor the byte range of the value)
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
SECTION("std types: the same errors as from the materialized value")
{
const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])");
@@ -402,10 +403,10 @@ TEST_CASE("json_view from_json")
CHECK(d.root()[4].get<strict_color>() == strict_color::red);
#if !defined(JSON_NOEXCEPTION)
const json j = d.root().materialize();
#if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\"");
#endif
#if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
CHECK(exception_of_call([&] { static_cast<void>(d.root()[2].get<strict_color>()); }) == exception_of_call([&] { static_cast<void>(j[2].get<strict_color>()); }));
#endif
#endif
}
@@ -451,9 +452,11 @@ TEST_CASE("json_view from_json")
CHECK(o.root().get<shape>().center.y == 2);
}
#if !defined(JSON_NOEXCEPTION)
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
SECTION("errors are those of the same conversion of a json value")
{
// (with JSON_DIAGNOSTICS, those of json name the path of the value,
// and with JSON_DIAGNOSTIC_POSITIONS its bytes)
const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])");
const json j = d.root().materialize();
for (std::size_t i = 0; i < 3; ++i)
-226
View File
@@ -81,112 +81,6 @@ std::string range(const json& j)
{
return j.start_pos() == npos && j.end_pos() == npos ? "none" : std::to_string(j.start_pos()) + "-" + std::to_string(j.end_pos());
}
#if !defined(JSON_NOEXCEPTION)
using json_view_test::exception_of_call;
// the exceptions about every value of v are those about the value at the same
// place in j, a value of the whole document from parse() or materialize() (so
// that its parents and positions are set); members whose key repeats are
// skipped (the view finds the first one, j holds the last)
template<typename View>
void check_exceptions(const View& v, const json& j)
{
CAPTURE(j.dump())
if (!v.is_number())
{
CHECK(exception_of_call([&] { static_cast<void>(v.template get<int>()); }) == exception_of_call([&] { static_cast<void>(j.get<int>()); }));
}
if (!v.is_string())
{
CHECK(exception_of_call([&] { static_cast<void>(v.template get<std::string>()); }) == exception_of_call([&] { static_cast<void>(j.get<std::string>()); }));
}
if (!v.is_array())
{
CHECK(exception_of_call([&] { static_cast<void>(v.template get<std::vector<int>>()); }) == exception_of_call([&] { static_cast<void>(j.get<std::vector<int>>()); }));
}
CHECK(exception_of_call([&] { static_cast<void>(v.at("missing")); }) == exception_of_call([&] { static_cast<void>(j.at("missing")); }));
CHECK(exception_of_call([&] { static_cast<void>(v.at(1000)); }) == exception_of_call([&] { static_cast<void>(j.at(1000)); }));
CHECK(exception_of_call([&] { static_cast<void>(v.at(json::json_pointer("/missing"))); }) == exception_of_call([&] { static_cast<void>(j.at(json::json_pointer("/missing"))); }));
CHECK(exception_of_call([&] { static_cast<void>(v.at(json::json_pointer("/-"))); }) == exception_of_call([&] { static_cast<void>(j.at(json::json_pointer("/-"))); }));
CHECK(exception_of_call([&] { static_cast<void>(v.value("k", 0)); }) == exception_of_call([&] { static_cast<void>(j.value("k", 0)); }));
if (!v.is_object())
{
CHECK(exception_of_call([&] { static_cast<void>(v["k"]); }) == exception_of_call([&] { static_cast<void>(j["k"]); }));
// (without the context of basic_json: view iterators do not know the value they iterate over)
CHECK(exception_of_call([&] { static_cast<void>(v.begin().key()); }) == "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
}
if (!v.is_array())
{
CHECK(exception_of_call([&] { static_cast<void>(v[0]); }) == exception_of_call([&] { static_cast<void>(j[0]); }));
}
if (v.is_null())
{
CHECK(exception_of_call([&] { static_cast<void>(v.front()); }) == exception_of_call([&] { static_cast<void>(j.front()); }));
}
if (v.is_object())
{
for (auto it = v.begin(); it != v.end(); ++it)
{
const std::string key(it.key().data(), it.key().size());
if (v.count(key) == 1)
{
check_exceptions(it.value(), j.at(key));
}
}
}
else if (v.is_array())
{
for (std::size_t i = 0; i < v.size(); ++i)
{
check_exceptions(v[i], j[i]);
}
}
}
// as check_exceptions, for edits that fail (they change neither d nor j)
void check_edit_exceptions(json_editable_document& d, const json_editable_document::view_type& v, json& j)
{
CAPTURE(j.dump())
if (!v.is_object() && !v.is_null())
{
CHECK(exception_of_call([&] { d.set(v, "k", 1); }) == exception_of_call([&] { j["k"] = 1; }));
CHECK(exception_of_call([&] { d.erase(v, "k"); }) == exception_of_call([&] { j.erase("k"); }));
}
if (!v.is_array() && !v.is_null())
{
CHECK(exception_of_call([&] { d.set(v, 0, 1); }) == exception_of_call([&] { j[0] = 1; }));
CHECK(exception_of_call([&] { d.push_back(v, 1); }) == exception_of_call([&] { j.push_back(1); }));
}
if (!v.is_array())
{
CHECK(exception_of_call([&] { d.insert(v, 0, 1); }) == exception_of_call([&] { j.insert(j.cbegin(), 1); }));
CHECK(exception_of_call([&] { d.erase(v, 0); }) == exception_of_call([&] { j.erase(0); }));
}
else
{
CHECK(exception_of_call([&] { d.erase(v, 1000); }) == exception_of_call([&] { j.erase(1000); }));
}
if (v.is_object())
{
for (auto it = v.begin(); it != v.end(); ++it)
{
const std::string key(it.key().data(), it.key().size());
if (v.count(key) == 1)
{
check_edit_exceptions(d, it.value(), j.at(key));
}
}
}
else if (v.is_array())
{
for (std::size_t i = 0; i < v.size(); ++i)
{
check_edit_exceptions(d, v[i], j[i]);
}
}
}
#endif
} // namespace
TEST_CASE("json_view materialize() source positions")
@@ -451,123 +345,3 @@ TEST_CASE("json_view materialize() source positions")
}
#endif
}
#if !defined(JSON_NOEXCEPTION)
TEST_CASE("json_view exceptions name the path and the bytes of the value")
{
SECTION("as those of basic_json")
{
const std::vector<std::string> texts =
{
"null", "true", "42", "\"s\"", "[]", "{}", "[1, [2, [3, []]]]", R"({"a": {"b": [1, {"c": null}]}, "d": "e"})",
R"({"a/b": {"~": [1]}, "\u0041\n": {"x": "\t"}, "": [{}]})",
R"([1 /* ] */, {"a": [] // }
, "b": {} } , ])",
"\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n",
};
for (const auto& text : texts)
{
CAPTURE(text)
const json j = json::parse(text, nullptr, true, true, true);
const json_document d = json_document::parse(text, true, true, true);
check_exceptions(d.root(), j);
json_editable_document e = json_editable_document::parse(text, true, true, true);
check_exceptions(e.root(), j);
json m = e.root().materialize();
check_edit_exceptions(e, e.root(), m);
}
}
SECTION("generated documents")
{
json_view_test::generator gen;
for (int i = 0; i < 100; ++i)
{
std::string text;
gen.value(text, 0);
CAPTURE(text)
const json_document d = json_document::parse(text);
check_exceptions(d.root(), json::parse(text));
}
}
SECTION("edited documents: as those of materialize()")
{
const std::string text = R"({"a": [1, 2], "b": {"c": "\n", "d": true}, "e": [3, [4]], "f": "x"})";
json_editable_document d = json_editable_document::parse(text);
d.set(d.root()["f"], 5);
d.push_back(d.root()["a"], json::parse(R"({"n": [null]})"));
d.erase(d.root()["e"], 0);
d.set(d.root()["b"], "c2", d.root()["e"]);
d.insert(d.root()["e"], 0, "new");
json m = d.root().materialize();
check_exceptions(d.root(), m);
check_edit_exceptions(d, d.root(), m);
// (a value inside a new container: a path, but no bytes)
std::string expected = "[json.exception.type_error.302] ";
#if JSON_DIAGNOSTICS
expected += "(/a/2/n/0) ";
#endif
expected += "type must be number, but is null";
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"][2]["n"][0].get<int>()); }) == expected);
}
SECTION("deep documents with siblings after every nested container")
{
// the search for the path pushes and pops one frame per level (the
// stack reallocates while it grows, and shrinks again at every
// sibling): run under a sanitizer, a reference to a popped or moved
// frame would show here
std::string text = "7";
for (int level = 0; level < 40; ++level)
{
text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
}
CAPTURE(text)
const json j = json::parse(text);
const json_document d = json_document::parse(text);
check_exceptions(d.root(), j);
json_editable_document e = json_editable_document::parse(text);
e.set(e.root()[0][0], 9);
check_exceptions(e.root(), e.root().materialize());
}
SECTION("examples")
{
const std::string text = R"({"people": [{"name": "a", "age": 1}, {"name": "c", "age": "x"}], "o": {"k": 1}})";
const json_document d = json_document::parse(text);
std::string expected_age = "[json.exception.type_error.302] ";
#if JSON_DIAGNOSTICS
expected_age += "(/people/1/age) ";
#endif
expected_age += "(bytes 58-61) type must be number, but is string";
CHECK(exception_of_call([&] { static_cast<void>(d.root()["people"][1]["age"].get<int>()); }) == expected_age);
std::string expected_zz = "[json.exception.out_of_range.403] ";
#if JSON_DIAGNOSTICS
expected_zz += "(/people/0) ";
#endif
expected_zz += "(bytes 12-35) key 'zz' not found";
CHECK(exception_of_call([&] { static_cast<void>(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz);
// the root has no path, and with JSON_DIAGNOSTICS then no bytes either (as basic_json)
std::string expected_missing = "[json.exception.out_of_range.403] ";
#if !JSON_DIAGNOSTICS
expected_missing += "(bytes 0-79) ";
#endif
expected_missing += "key 'missing' not found";
CHECK(exception_of_call([&] { static_cast<void>(d.root().at("missing")); }) == expected_missing);
}
SECTION("duplicate keys: the value the view finds")
{
const json_document d = json_document::parse(R"({"x": "1", "x": "22"})");
std::string expected = "[json.exception.type_error.302] ";
#if JSON_DIAGNOSTICS
expected += "(/x) ";
#endif
expected += "(bytes 6-9) type must be number, but is string";
CHECK(exception_of_call([&] { static_cast<void>(d.root()["x"].get<int>()); }) == expected);
}
}
#endif
+97
View File
@@ -27,12 +27,15 @@ using nlohmann::json;
#include <sstream>
#include <iomanip>
#include <limits>
#include <list>
#include <set>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
#include "sax_event_loggers.hpp"
using utils::SaxCountdown;
using utils::SaxEventLogger;
TEST_CASE("MessagePack")
@@ -2212,3 +2215,97 @@ TEST_CASE("MessagePack large strings and binaries (chunked reader)")
}
}
}
TEST_CASE("MessagePack arrays of scalars from contiguous and non-contiguous input")
{
// contiguous input reads the integers, floats, booleans, and nils an
// array begins with in one loop, other input value by value; both must
// give the same events, value, or error for the complete document and for
// every truncation of it, and stop at every event a SAX handler rejects
const std::vector<std::uint8_t> msgpack =
{
0x83, // map(3)
0xA1, 'a', // "a"
0xDC, 0x00, 0x16, // array 16 (22)
0x00, 0x7F, 0xCC, 0xFF, 0xCD, 0x01, 0x00, // 0, 127, 255, 256
0xCE, 0x00, 0x01, 0x00, 0x00, // 65536
0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615
0xFF, 0xE0, 0xD0, 0x80, 0xD1, 0xFF, 0x00, // -1, -32, -128, -256
0xD2, 0xFF, 0xFE, 0xFF, 0xFF, // -65537
0xD3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -9223372036854775808
0xC2, 0xC3, 0xC0, // false, true, nil
0xCA, 0x3F, 0xC0, 0x00, 0x00, // 1.5 (float 32)
0xCB, 0xBF, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, // -0.1
0xA1, 'x', // "x" (ends the loop)
0x01, // 1
0xCB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.5
0xD9, 0x01, 'y', // "y" (str 8)
0xC0, // nil
0xA1, 'b', // "b"
0x93, // fixarray(3)
0x92, 0xCB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xCB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x92, 0xCB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xCB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x90, // []
0xA1, 'c', // "c"
0x94, 0x80, 0x01, 0xC4, 0x01, 0x05, 0x91, 0x03 // [{}, 1, bin 8, [3]]
};
const json j = json::from_msgpack(msgpack);
CHECK(j["a"].size() == 22);
CHECK(j["a"][5] == 18446744073709551615u);
CHECK(j["a"][7] == -32);
CHECK(j["a"][11] == (std::numeric_limits<std::int64_t>::min)());
CHECK(j["a"][15] == 1.5);
CHECK(j["a"][16] == -0.1);
CHECK(j["a"][17] == "x");
CHECK(j["b"] == json::parse("[[1.25, -3.75], [1.25, -3.75], []]"));
CHECK(j["c"][2].is_binary());
for (std::size_t length = 0; length <= msgpack.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(msgpack.begin(), msgpack.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_msgpack(contiguous, true, false).dump() == json::from_msgpack(bytewise.begin(), bytewise.end(), true, false).dump());
#if !defined(JSON_NOEXCEPTION)
std::string contiguous_error;
std::string bytewise_error;
try
{
static_cast<void>(json::from_msgpack(contiguous));
}
catch (const json::parse_error& e)
{
contiguous_error = e.what();
}
try
{
static_cast<void>(json::from_msgpack(bytewise.begin(), bytewise.end()));
}
catch (const json::parse_error& e)
{
bytewise_error = e.what();
}
CHECK(contiguous_error == bytewise_error);
#endif
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::msgpack)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::msgpack));
CHECK(from_contiguous.events == from_bytewise.events);
}
for (int events = 0; events <= 60; ++events)
{
CAPTURE(events)
const std::list<std::uint8_t> bytewise(msgpack.begin(), msgpack.end());
SaxCountdown from_contiguous(events);
SaxCountdown from_bytewise(events);
CHECK(json::sax_parse(msgpack, &from_contiguous, json::input_format_t::msgpack)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::msgpack));
}
}