Compare commits

..
Author SHA1 Message Date
Niels Lohmann 0299efb761 Escape characters below 0x60 from a table straight into the write buffer
When the bulk scanner in dump_escaped_impl() stops at a control
character, a quote or a backslash, that byte used to go through the
UTF-8 decoder and a switch that wrote the escape byte by byte into
string_buffer, which was then copied into write_buffer at the next run.

A byte below 0x60 at a character boundary is a complete code point, so
it now skips the decoder: its escape sequence comes from a 96-entry
table of 8-byte rows (sequence padded with zeros, length in the last
byte), and all 8 bytes are copied straight into write_buffer with one
room check. The check sits inside the existing character-boundary
block, so it runs once per scanner stop and not once per byte; testing
every byte cost about 6% on twitter.json with ensure_ascii.

The switch in the decoder's accept branch lost its cases for
characters below 0x60, which can no longer reach it.

Output is byte-identical. The string-buffer boundary tests now use
\u-escaped non-ASCII characters, since simple escapes no longer reach
string_buffer, and a new test case checks every ASCII character at
every position against a reference escaper.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 09:16:40 +02:00
Niels Lohmann 7461a199b5 Drop the class wrapper from the C++20 comparison operator signatures in the docs (#5814)
* Drop the class wrapper from the C++20 comparison operator signatures in the docs

The signature boxes of operator<=> and the C++20 half of operator== wrapped
the member declarations in an incomplete "class basic_json { ... };". The
trailing const already marks them as members, and no other page uses such a
wrapper.

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

* Drop the class wrapper from the json_pointer comparison signatures too

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-13 20:06:14 +02:00
18 changed files with 590 additions and 663 deletions

No files matched your search

@@ -6,9 +6,6 @@ constexpr bool is_number_unsigned() const noexcept;
This function returns `#!cpp true` if and only if the JSON value is an unsigned integer number. This excludes
floating-point and signed integer values.
If [`number_unsigned_t`](number_unsigned_t.md) is the same type as [`number_integer_t`](number_integer_t.md), every
integer is stored as `number_integer_t`, and this function always returns `#!cpp false`.
## Return value
@@ -25,8 +25,7 @@ the type to use.
`NumberUnsignedType`
: the type to store unsigned integers. It must be an **unsigned integral** type (`#!cpp std::is_integral`) with a
`#!cpp std::numeric_limits` specialization, and it must be able to represent the absolute value of every
[`number_integer_t`](number_integer_t.md) value. Alternatively, it can be the same type as
[`number_integer_t`](number_integer_t.md); then every integer is stored as `number_integer_t`. See
[`number_integer_t`](number_integer_t.md) value. See
[Template Parameter Requirements](../../features/types/template_parameters.md#numberintegertype-and-numberunsignedtype).
## Notes
@@ -90,4 +89,3 @@ Integer number values are stored directly inside a `basic_json` type.
## Version history
- Added in version 2.0.0.
- Can be the same type as `number_integer_t` since version 3.13.0.
@@ -11,12 +11,10 @@ template<typename ScalarType>
bool operator==(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
// since C++20
class basic_json {
bool operator==(const_reference rhs) const noexcept; // (1)
bool operator==(const_reference rhs) const noexcept; // (1)
template<typename ScalarType>
bool operator==(ScalarType rhs) const noexcept(/* see below */); // (2)
};
template<typename ScalarType>
bool operator==(ScalarType rhs) const noexcept(/* see below */); // (2)
```
1. Compares two JSON values for equality according to the following rules:
@@ -2,12 +2,10 @@
```cpp
// since C++20
class basic_json {
std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1)
std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1)
template<typename ScalarType>
std::partial_ordering operator<=>(const ScalarType rhs) const noexcept(/* see below */); // (2)
};
template<typename ScalarType>
std::partial_ordering operator<=>(const ScalarType rhs) const noexcept(/* see below */); // (2)
```
1. 3-way compares two JSON values producing a result of type `std::partial_ordering` according to the following rules:
@@ -18,13 +18,11 @@ bool operator==(
const json_pointer<RefStringTypeRhs>& rhs); // (2)
// since C++20
class json_pointer {
template<typename RefStringTypeRhs>
bool operator==(
const json_pointer<RefStringTypeRhs>& rhs) const noexcept; // (1)
template<typename RefStringTypeRhs>
bool operator==(
const json_pointer<RefStringTypeRhs>& rhs) const noexcept; // (1)
bool operator==(const string_t& rhs) const; // (2)
};
bool operator==(const string_t& rhs) const; // (2)
```
1. Compares two JSON pointers for equality by comparing their reference tokens.
@@ -2,10 +2,8 @@
```cpp
// since C++20
class json_pointer {
template<typename RefStringTypeRhs>
std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept; // *NOPAD*
};
template<typename RefStringTypeRhs>
std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept; // *NOPAD*
```
3-way compares two JSON pointers by lexicographically comparing their sequences of reference tokens: corresponding
@@ -343,11 +343,8 @@ The number types can be changed with template parameters.
in case of overflow.
- The type for unsigned integers must be convertible from `#!c unsigned long long`. The type for floating-point
numbers is used in case of overflow.
- The type for unsigned integers can also be the same signed type as the type for signed integers (e.g.,
`#!c std::int64_t` for both). Then every integer is stored as a signed integer, and
[`is_number_unsigned()`](../../api/basic_json/is_number_unsigned.md) is always `#!c false`, see
[Template Parameter Requirements](template_parameters.md#same-type-for-both).
- The type for floating-point numbers must be distinct from the integer types.
- The types for signed and unsigned integers must be distinct, see
[#2573](https://github.com/nlohmann/json/issues/2573).
- Only `#!c double`, `#!c float`, and `#!c long double` are supported for floating-point numbers.
!!! example
@@ -40,19 +40,19 @@ Requirements are split into two groups:
## Overview
| Template parameter | Default | Notable substitutes |
|-------------------------------------------------------------------|-----------------------------------|--------------------------------------------------------------------------------------------------|
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
| [`BooleanType`](#booleantype) | `bool` | none worth using |
| [`NumberIntegerType`](#numberintegertype-and-numberunsignedtype) | `std::int64_t` | any signed integer type |
| [`NumberUnsignedType`](#numberintegertype-and-numberunsignedtype) | `std::uint64_t` | any unsigned integer type at least as wide as `NumberIntegerType`, or `NumberIntegerType` itself |
| [`NumberFloatType`](#numberfloattype) | `double` | `float` (`long double`: no binary formats) |
| [`AllocatorType`](#allocatortype) | `std::allocator` | stateless allocators |
| [`JSONSerializer`](#jsonserializer) | `adl_serializer` | serializers with the same interface |
| [`BinaryType`](#binarytype) | `#!cpp std::vector<std::uint8_t>` | `#!cpp std::vector<char>` |
| [`CustomBaseClass`](#custombaseclass) | `void` | any default-constructible class |
| Template parameter | Default | Notable substitutes |
|-------------------------------------------------------------------|-----------------------------------|-----------------------------------------------------------------------|
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
| [`BooleanType`](#booleantype) | `bool` | none worth using |
| [`NumberIntegerType`](#numberintegertype-and-numberunsignedtype) | `std::int64_t` | any signed integer type |
| [`NumberUnsignedType`](#numberintegertype-and-numberunsignedtype) | `std::uint64_t` | any unsigned integer type at least as wide as `NumberIntegerType` |
| [`NumberFloatType`](#numberfloattype) | `double` | `float` (`long double`: no binary formats) |
| [`AllocatorType`](#allocatortype) | `std::allocator` | stateless allocators |
| [`JSONSerializer`](#jsonserializer) | `adl_serializer` | serializers with the same interface |
| [`BinaryType`](#binarytype) | `#!cpp std::vector<std::uint8_t>` | `#!cpp std::vector<char>` |
| [`CustomBaseClass`](#custombaseclass) | `void` | any default-constructible class |
!!! warning "Third-party containers and incomplete types"
@@ -499,14 +499,13 @@ Both types are stored **directly** inside `basic_json`'s union.
### Always required
- `#!cpp std::is_integral` must be satisfied: `NumberIntegerType` must be a **signed** integer type,
`NumberUnsignedType` an **unsigned** integer type or the same type as `NumberIntegerType` (see
[below](#same-type-for-both)). Class types are not supported -- among others, the constructors taking integer values
are constrained on `#!cpp std::is_integral`.
`NumberUnsignedType` an **unsigned** integer type. Class types are not supported -- among others, the constructors
taking integer values are constrained on `#!cpp std::is_integral`.
- Trivially default-constructible, trivially copyable, and trivially destructible (union member).
- `#!cpp std::numeric_limits` must be specialized for both types.
- An unsigned `NumberUnsignedType` must be able to represent the absolute value of every `NumberIntegerType` value;
serialization of negative numbers converts the value to `NumberUnsignedType`. A `#!cpp static_assert` requires it to
be at least as wide as `NumberIntegerType`, which is what that amounts to for the standard integer types.
- `NumberUnsignedType` must be able to represent the absolute value of every `NumberIntegerType` value; serialization
of negative numbers converts the value to `NumberUnsignedType`. A `#!cpp static_assert` requires it to be at least as
wide as `NumberIntegerType`, which is what that amounts to for the standard integer types.
- Both types must fit into the internal 64-character number buffer used by
[`dump`](../../api/basic_json/dump.md), which is the case for all standard integer types.
- [`std::hash<basic_json>`](../../api/basic_json/std_hash.md) additionally requires `#!cpp std::hash` specializations.
@@ -518,23 +517,6 @@ is stored as [`number_float_t`](../../api/basic_json/number_float_t.md) instead.
therefore silently changes parse results rather than raising an error. See
[Number Handling](number_handling.md) for details.
#### Same type for both
`NumberUnsignedType` may be the same type as `NumberIntegerType`, e.g.
`#!cpp nlohmann::json::with_integers_t<std::int64_t, std::int64_t>` (see
[`with_integers_t`](../../api/basic_json/with_t.md)), for code that only wants to handle one integer type. Such a `basic_json` stores every integer as
[`number_integer_t`](../../api/basic_json/number_integer_t.md):
- [`is_number_unsigned()`](../../api/basic_json/is_number_unsigned.md) is always `#!cpp false`, and
[`type()`](../../api/basic_json/type.md) never returns `value_t::number_unsigned`. Constructing a value from
`value_t::number_unsigned` gives a `value_t::number_integer` value.
- Values of unsigned C++ types (e.g., `#!cpp 5u`) are converted to `NumberIntegerType`, so values above its maximum
wrap around, as values outside the range of a narrower `NumberIntegerType` already do.
- The parsers store a non-negative integer above the maximum of `NumberIntegerType` (e.g., `18446744073709551615` for
`#!cpp std::int64_t`) as [`number_float_t`](../../api/basic_json/number_float_t.md), like any other integer that does
not fit.
- Converting from a `basic_json` with distinct types turns its unsigned values into `number_integer_t` values.
### Compatible types
| Type pair | Support |
@@ -543,7 +525,6 @@ therefore silently changes parse results rather than raising an error. See
| `#!cpp std::int32_t` / `#!cpp std::uint32_t`, `#!cpp long long` / `#!cpp unsigned long long` | full; narrower types change which literals the parser can represent |
| any other pair of standard signed/unsigned integer types | full |
| class types, enumerations | not usable; `#!cpp std::is_integral` must hold |
| the same signed integer type for both, e.g. `#!cpp std::int64_t` / `#!cpp std::int64_t` | full; every integer is stored as `number_integer_t`, see [above](#same-type-for-both) |
| `#!cpp bool`, or a type already used for another member of the union | not usable; `#!cpp std::is_integral<bool>` is in fact `#!cpp true`, but the `get_impl_ptr` overloads for `boolean_t`, `number_integer_t`, `number_unsigned_t` and `number_float_t` would collide |
## `NumberFloatType`
@@ -80,11 +80,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{
case value_t::number_unsigned:
{
// never null: a basic_json whose number_unsigned_t is number_integer_t
// stores no number_unsigned values (#2573)
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
JSON_ASSERT(ptr != nullptr);
val = static_cast<ArithmeticType>(*ptr);
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
break;
}
case value_t::number_integer:
@@ -154,7 +150,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_flo
}
template<typename BasicJsonType>
inline void from_json(const BasicJsonType& j, number_unsigned_overload_t<BasicJsonType>& val)
inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
{
get_arithmetic_value(j, val);
}
@@ -410,11 +406,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val)
{
case value_t::number_unsigned:
{
// never null: a basic_json whose number_unsigned_t is number_integer_t
// stores no number_unsigned values (#2573)
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
JSON_ASSERT(ptr != nullptr);
val = static_cast<ArithmeticType>(*ptr);
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
break;
}
case value_t::number_integer:
@@ -303,14 +303,14 @@ inline void to_json(BasicJsonType& j, FloatType val) noexcept
}
template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
enable_if_t<is_compatible_integer_type<number_unsigned_overload_t<BasicJsonType>, CompatibleNumberUnsignedType>::value, int> = 0>
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
{
external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
}
template<typename BasicJsonType, typename CompatibleNumberIntegerType,
enable_if_t<is_compatible_number_integer_type<BasicJsonType, CompatibleNumberIntegerType>::value, int> = 0>
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
{
external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
+2 -19
View File
@@ -18,7 +18,6 @@
#include <cstring> // memcpy
#include <limits> // numeric_limits
#include <string> // string
#include <type_traits> // is_signed, true_type, false_type
#include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/input/pow5_table.hpp>
@@ -45,20 +44,6 @@ NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
// whether an integer of a signed type is negative; the overload for other types
// avoids comparing an unsigned value with zero
template<typename NumberType>
constexpr bool is_negative_integer(NumberType x, std::true_type /*is_signed*/) noexcept
{
return x < 0;
}
template<typename NumberType>
constexpr bool is_negative_integer(NumberType /*unused*/, std::false_type /*is_signed*/) noexcept
{
return false;
}
/*!
@brief fast integer parser for an already-validated unsigned integer
@@ -92,10 +77,8 @@ bool parse_integer_unsigned(const char* first, const char* last, NumberUnsignedT
x = (x * 10u) + digit;
}
value = static_cast<NumberUnsignedType>(x);
// reject values that do not round-trip into a narrower NumberUnsignedType,
// and values that wrap around to negative numbers if it is a signed type
// (number_unsigned_t may be the same type as number_integer_t, #2573)
return static_cast<std::uint64_t>(value) == x && !is_negative_integer(value, std::is_signed<NumberUnsignedType>());
// reject values that do not round-trip into a narrower NumberUnsignedType
return static_cast<std::uint64_t>(value) == x;
}
/*!
@@ -683,22 +683,6 @@ struct is_compatible_integer_type
: is_compatible_integer_type_impl<RealIntegerType,
CompatibleNumberIntegerType> {};
// whether to_json() stores an integer of type CompatibleNumberIntegerType as
// number_integer
template<typename BasicJsonType, typename CompatibleNumberIntegerType, typename = void>
struct is_compatible_number_integer_type
: is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
// if number_unsigned_t is the same type as number_integer_t, unsigned integers
// are stored as number_integer as well (#2573)
template<typename BasicJsonType, typename CompatibleNumberIntegerType>
struct is_compatible_number_integer_type < BasicJsonType, CompatibleNumberIntegerType,
enable_if_t < std::is_same<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>::value&&
std::is_integral<CompatibleNumberIntegerType>::value&&
std::is_unsigned<CompatibleNumberIntegerType>::value&&
!std::is_same<bool, CompatibleNumberIntegerType>::value >>
: std::is_constructible<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
template<typename BasicJsonType, typename CompatibleType, typename = void>
struct is_compatible_type_impl: std::false_type {};
@@ -1036,27 +1020,6 @@ constexpr bool value_in_range_of(T val)
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
// Stands in for number_unsigned_t in overload sets if it is the same type as
// number_integer_t, so that the unsigned overloads do not collide with the
// integer ones. Never constructed. Such a basic_json stores every integer as
// number_integer (#2573).
struct same_as_number_integer_type {};
// NumberUnsignedType, or same_as_number_integer_type if it is the same type as
// NumberIntegerType; basic_json uses this one, as it cannot name its own member
// types through an incomplete basic_json (MSVC reports a recursive alias, and
// old GCC versions crash)
template<typename NumberIntegerType, typename NumberUnsignedType>
using number_unsigned_overload_type = typename std::conditional <
std::is_same<NumberIntegerType, NumberUnsignedType>::value,
same_as_number_integer_type,
NumberUnsignedType >::type;
// number_unsigned_t of BasicJsonType, or same_as_number_integer_type if it is
// the same type as number_integer_t
template<typename BasicJsonType>
using number_unsigned_overload_t = number_unsigned_overload_type<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>;
///////////////////////////////////////////////////////////////////////////////
// is_c_string
///////////////////////////////////////////////////////////////////////////////
@@ -221,7 +221,7 @@ class binary_writer
case value_t::number_unsigned:
{
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
break;
}
@@ -1601,7 +1601,7 @@ class binary_writer
return calc_bson_integer_size(j.m_data.m_value.number_integer);
case value_t::number_unsigned:
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
case value_t::string:
return calc_bson_string_size(*j.m_data.m_value.string, j);
@@ -1643,7 +1643,7 @@ class binary_writer
return write_bson_integer(name, j.m_data.m_value.number_integer);
case value_t::number_unsigned:
return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
case value_t::string:
return write_bson_string(name, *j.m_data.m_value.string);
+183 -89
View File
@@ -49,12 +49,6 @@ class serializer
using number_float_t = typename BasicJsonType::number_float_t;
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
/// type of the absolute value of an integer: number_unsigned_t, or its
/// unsigned counterpart if it is signed (it may be number_integer_t, #2573)
using number_magnitude_t = typename std::conditional <
std::is_integral<number_unsigned_t>::value && std::is_signed<number_unsigned_t>::value,
std::make_unsigned<number_unsigned_t>,
std::enable_if<true, number_unsigned_t >>::type::type;
using binary_char_t = typename BasicJsonType::binary_t::value_type;
public:
@@ -778,6 +772,33 @@ class serializer
break;
}
}
// A byte below 0x60 is a complete code point and needs no
// UTF-8 decoding. The scanner stops only at a control
// character, a quote, or a backslash in that range, so look up
// its escape sequence and write all 8 bytes of the table entry
// straight into the write buffer; only the length stored in the
// last byte of the entry is kept.
if (data[i] < 0x60)
{
// emit any bytes still pending in string_buffer first to
// preserve output order
if (bytes != 0)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
const auto& escape = ascii_escapes()[data[i]];
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
{
flush();
}
std::memcpy(write_buffer.data() + write_buffer_pos, escape.data(), escape.size());
write_buffer_pos += static_cast<std::size_t>(escape[7]);
bytes_after_last_accept = 0;
undumped_chars = 0;
continue;
}
}
const auto byte = static_cast<std::uint8_t>(s[i]);
@@ -786,92 +807,39 @@ class serializer
{
case UTF8_ACCEPT: // decode found a new code point
{
switch (codepoint)
// the code point is at least 0x60 (smaller ones are handled
// above) and is not printable ASCII (which the scanner
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
// character
if (EnsureAscii)
{
case 0x08: // backspace
JSON_ASSERT(codepoint >= 0x7F);
if (error_handler == error_handler_t::keep)
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 'b';
break;
// this character was buffered as raw bytes below
// in case it turned out to be part of an
// ill-formed sequence (which is kept as is); now
// that it decoded to a well-formed code point,
// undo that and \u-escape it like any other
// character instead
bytes = bytes_after_last_accept;
}
case 0x09: // horizontal tab
if (codepoint <= 0xFFFF)
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 't';
break;
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
}
case 0x0A: // newline
else
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 'n';
break;
}
case 0x0C: // formfeed
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 'f';
break;
}
case 0x0D: // carriage return
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 'r';
break;
}
case 0x22: // quotation mark
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = '"';
break;
}
case 0x5C: // reverse solidus
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = '\\';
break;
}
default:
{
// escape control characters (0x00..0x1F) or, if
// EnsureAscii parameter is used, non-ASCII characters
if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F)))
{
if (EnsureAscii && error_handler == error_handler_t::keep)
{
// this character was buffered as raw bytes
// below in case it turned out to be part of
// an ill-formed sequence (which is kept as
// is); now that it decoded to a well-formed
// code point, undo that and \u-escape it
// like any other character instead
bytes = bytes_after_last_accept;
}
if (codepoint <= 0xFFFF)
{
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
}
else
{
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
}
}
else
{
// copy byte to buffer (all previous bytes
// been copied have in default case above)
string_buffer[bytes++] = s[i];
}
break;
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
}
}
else
{
// copy byte to buffer (all previous bytes of the
// sequence have been copied in the default case below)
string_buffer[bytes++] = s[i];
}
// write buffer and reset index; there must be 13 bytes
// left, as this is the maximal number of bytes to be
@@ -1233,7 +1201,7 @@ class serializer
@param[in] x unsigned integer number to count its digits
@return number of decimal digits
*/
unsigned int count_digits(number_magnitude_t x) noexcept
unsigned int count_digits(number_unsigned_t x) noexcept
{
unsigned int n_digits = 1;
for (;;)
@@ -1259,6 +1227,121 @@ class serializer
}
}
/*!
@brief the JSON form of each character below 0x60
Entry c holds the escape sequence of character c - the short form for
\\b, \\t, \\n, \\f, \\r, \\" and \\\\, \\u00xx for the other control characters -
or c itself if it needs no escaping, padded with zeros to 7 bytes. The last
byte is the length of the sequence. The 8 bytes of an entry are copied at
once, so writing an escape needs neither a branch nor a byte-wise loop.
*/
static const std::array<std::array<char, 8>, 0x60>& ascii_escapes() noexcept
{
static const std::array<std::array<char, 8>, 0x60> escapes =
{
{
{{'\\', 'u', '0', '0', '0', '0', 0, 6}}, // 0x00
{{'\\', 'u', '0', '0', '0', '1', 0, 6}}, // 0x01
{{'\\', 'u', '0', '0', '0', '2', 0, 6}}, // 0x02
{{'\\', 'u', '0', '0', '0', '3', 0, 6}}, // 0x03
{{'\\', 'u', '0', '0', '0', '4', 0, 6}}, // 0x04
{{'\\', 'u', '0', '0', '0', '5', 0, 6}}, // 0x05
{{'\\', 'u', '0', '0', '0', '6', 0, 6}}, // 0x06
{{'\\', 'u', '0', '0', '0', '7', 0, 6}}, // 0x07
{{'\\', 'b', 0, 0, 0, 0, 0, 2}}, // 0x08
{{'\\', 't', 0, 0, 0, 0, 0, 2}}, // 0x09
{{'\\', 'n', 0, 0, 0, 0, 0, 2}}, // 0x0A
{{'\\', 'u', '0', '0', '0', 'b', 0, 6}}, // 0x0B
{{'\\', 'f', 0, 0, 0, 0, 0, 2}}, // 0x0C
{{'\\', 'r', 0, 0, 0, 0, 0, 2}}, // 0x0D
{{'\\', 'u', '0', '0', '0', 'e', 0, 6}}, // 0x0E
{{'\\', 'u', '0', '0', '0', 'f', 0, 6}}, // 0x0F
{{'\\', 'u', '0', '0', '1', '0', 0, 6}}, // 0x10
{{'\\', 'u', '0', '0', '1', '1', 0, 6}}, // 0x11
{{'\\', 'u', '0', '0', '1', '2', 0, 6}}, // 0x12
{{'\\', 'u', '0', '0', '1', '3', 0, 6}}, // 0x13
{{'\\', 'u', '0', '0', '1', '4', 0, 6}}, // 0x14
{{'\\', 'u', '0', '0', '1', '5', 0, 6}}, // 0x15
{{'\\', 'u', '0', '0', '1', '6', 0, 6}}, // 0x16
{{'\\', 'u', '0', '0', '1', '7', 0, 6}}, // 0x17
{{'\\', 'u', '0', '0', '1', '8', 0, 6}}, // 0x18
{{'\\', 'u', '0', '0', '1', '9', 0, 6}}, // 0x19
{{'\\', 'u', '0', '0', '1', 'a', 0, 6}}, // 0x1A
{{'\\', 'u', '0', '0', '1', 'b', 0, 6}}, // 0x1B
{{'\\', 'u', '0', '0', '1', 'c', 0, 6}}, // 0x1C
{{'\\', 'u', '0', '0', '1', 'd', 0, 6}}, // 0x1D
{{'\\', 'u', '0', '0', '1', 'e', 0, 6}}, // 0x1E
{{'\\', 'u', '0', '0', '1', 'f', 0, 6}}, // 0x1F
{{' ', 0, 0, 0, 0, 0, 0, 1}}, // 0x20
{{'!', 0, 0, 0, 0, 0, 0, 1}}, // 0x21
{{'\\', '"', 0, 0, 0, 0, 0, 2}}, // 0x22
{{'#', 0, 0, 0, 0, 0, 0, 1}}, // 0x23
{{'$', 0, 0, 0, 0, 0, 0, 1}}, // 0x24
{{'%', 0, 0, 0, 0, 0, 0, 1}}, // 0x25
{{'&', 0, 0, 0, 0, 0, 0, 1}}, // 0x26
{{'\'', 0, 0, 0, 0, 0, 0, 1}}, // 0x27
{{'(', 0, 0, 0, 0, 0, 0, 1}}, // 0x28
{{')', 0, 0, 0, 0, 0, 0, 1}}, // 0x29
{{'*', 0, 0, 0, 0, 0, 0, 1}}, // 0x2A
{{'+', 0, 0, 0, 0, 0, 0, 1}}, // 0x2B
{{',', 0, 0, 0, 0, 0, 0, 1}}, // 0x2C
{{'-', 0, 0, 0, 0, 0, 0, 1}}, // 0x2D
{{'.', 0, 0, 0, 0, 0, 0, 1}}, // 0x2E
{{'/', 0, 0, 0, 0, 0, 0, 1}}, // 0x2F
{{'0', 0, 0, 0, 0, 0, 0, 1}}, // 0x30
{{'1', 0, 0, 0, 0, 0, 0, 1}}, // 0x31
{{'2', 0, 0, 0, 0, 0, 0, 1}}, // 0x32
{{'3', 0, 0, 0, 0, 0, 0, 1}}, // 0x33
{{'4', 0, 0, 0, 0, 0, 0, 1}}, // 0x34
{{'5', 0, 0, 0, 0, 0, 0, 1}}, // 0x35
{{'6', 0, 0, 0, 0, 0, 0, 1}}, // 0x36
{{'7', 0, 0, 0, 0, 0, 0, 1}}, // 0x37
{{'8', 0, 0, 0, 0, 0, 0, 1}}, // 0x38
{{'9', 0, 0, 0, 0, 0, 0, 1}}, // 0x39
{{':', 0, 0, 0, 0, 0, 0, 1}}, // 0x3A
{{';', 0, 0, 0, 0, 0, 0, 1}}, // 0x3B
{{'<', 0, 0, 0, 0, 0, 0, 1}}, // 0x3C
{{'=', 0, 0, 0, 0, 0, 0, 1}}, // 0x3D
{{'>', 0, 0, 0, 0, 0, 0, 1}}, // 0x3E
{{'?', 0, 0, 0, 0, 0, 0, 1}}, // 0x3F
{{'@', 0, 0, 0, 0, 0, 0, 1}}, // 0x40
{{'A', 0, 0, 0, 0, 0, 0, 1}}, // 0x41
{{'B', 0, 0, 0, 0, 0, 0, 1}}, // 0x42
{{'C', 0, 0, 0, 0, 0, 0, 1}}, // 0x43
{{'D', 0, 0, 0, 0, 0, 0, 1}}, // 0x44
{{'E', 0, 0, 0, 0, 0, 0, 1}}, // 0x45
{{'F', 0, 0, 0, 0, 0, 0, 1}}, // 0x46
{{'G', 0, 0, 0, 0, 0, 0, 1}}, // 0x47
{{'H', 0, 0, 0, 0, 0, 0, 1}}, // 0x48
{{'I', 0, 0, 0, 0, 0, 0, 1}}, // 0x49
{{'J', 0, 0, 0, 0, 0, 0, 1}}, // 0x4A
{{'K', 0, 0, 0, 0, 0, 0, 1}}, // 0x4B
{{'L', 0, 0, 0, 0, 0, 0, 1}}, // 0x4C
{{'M', 0, 0, 0, 0, 0, 0, 1}}, // 0x4D
{{'N', 0, 0, 0, 0, 0, 0, 1}}, // 0x4E
{{'O', 0, 0, 0, 0, 0, 0, 1}}, // 0x4F
{{'P', 0, 0, 0, 0, 0, 0, 1}}, // 0x50
{{'Q', 0, 0, 0, 0, 0, 0, 1}}, // 0x51
{{'R', 0, 0, 0, 0, 0, 0, 1}}, // 0x52
{{'S', 0, 0, 0, 0, 0, 0, 1}}, // 0x53
{{'T', 0, 0, 0, 0, 0, 0, 1}}, // 0x54
{{'U', 0, 0, 0, 0, 0, 0, 1}}, // 0x55
{{'V', 0, 0, 0, 0, 0, 0, 1}}, // 0x56
{{'W', 0, 0, 0, 0, 0, 0, 1}}, // 0x57
{{'X', 0, 0, 0, 0, 0, 0, 1}}, // 0x58
{{'Y', 0, 0, 0, 0, 0, 0, 1}}, // 0x59
{{'Z', 0, 0, 0, 0, 0, 0, 1}}, // 0x5A
{{'[', 0, 0, 0, 0, 0, 0, 1}}, // 0x5B
{{'\\', '\\', 0, 0, 0, 0, 0, 2}}, // 0x5C
{{']', 0, 0, 0, 0, 0, 0, 1}}, // 0x5D
{{'^', 0, 0, 0, 0, 0, 0, 1}}, // 0x5E
{{'_', 0, 0, 0, 0, 0, 0, 1}}, // 0x5F
}
};
return escapes;
}
/*!
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
*
@@ -1411,7 +1494,7 @@ class serializer
// use a pointer to fill the buffer
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)
number_magnitude_t abs_value;
number_unsigned_t abs_value;
unsigned int n_chars{};
@@ -1425,7 +1508,7 @@ class serializer
}
else
{
abs_value = static_cast<number_magnitude_t>(x);
abs_value = static_cast<number_unsigned_t>(x);
n_chars = count_digits(abs_value);
}
@@ -1568,6 +1651,17 @@ class serializer
}
}
/*
* Overload to make the compiler happy while it is instantiating
* dump_integer for number_unsigned_t.
* Must never be called.
*/
number_unsigned_t remove_sign(number_unsigned_t x)
{
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
return x; // LCOV_EXCL_LINE
}
/*
* Helper function for dump_integer
*
@@ -1577,10 +1671,10 @@ class serializer
* absolute values of INT_MIN and INT_MAX are usually not the same. See
* #1708 for details.
*/
number_magnitude_t remove_sign(number_integer_t x) noexcept
number_unsigned_t remove_sign(number_integer_t x) noexcept
{
JSON_ASSERT(x < 0);
return static_cast<number_magnitude_t>(-(x + 1)) + 1;
return static_cast<number_unsigned_t>(-(x + 1)) + 1;
}
private:
+8 -24
View File
@@ -500,22 +500,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
"NumberUnsignedType must be at least as wide as NumberIntegerType, "
"because it has to hold the absolute value of every NumberIntegerType value");
// NumberUnsignedType may be the same type as NumberIntegerType (#2573), but
// a repeated NumberFloatType would give overloads that collide, so it gets
// a readable diagnostic instead.
static_assert(!std::is_same<NumberFloatType, NumberIntegerType>::value && !std::is_same<NumberFloatType, NumberUnsignedType>::value,
"NumberFloatType must be a different type than NumberIntegerType and NumberUnsignedType");
private:
/// the kind a value of kind @a t is stored as: if number_unsigned_t is the
/// same type as number_integer_t, every integer is stored as number_integer
/// (#2573); every other kind is stored as itself
static constexpr value_t stored_kind(const value_t t) noexcept
{
return (std::is_same<number_integer_t, number_unsigned_t>::value && t == value_t::number_unsigned) ? value_t::number_integer : t;
}
/// helper for exception-safe object creation
template<typename T, typename... Args>
JSON_HEDLEY_RETURNS_NON_NULL
@@ -589,8 +575,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
json_value(boolean_t v) noexcept : boolean(v) {}
/// constructor for numbers (integer)
json_value(number_integer_t v) noexcept : number_integer(v) {}
/// constructor for numbers (unsigned); takes a placeholder if number_unsigned_t is number_integer_t
json_value(detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType> v) noexcept : number_unsigned(v) {}
/// constructor for numbers (unsigned)
json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}
/// constructor for numbers (floating-point)
json_value(number_float_t v) noexcept : number_float(v) {}
/// constructor for empty values of a given type
@@ -2195,7 +2181,7 @@ public:
/// @brief create an empty value with a given type
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
basic_json(const value_t v)
: m_data(stored_kind(v))
: m_data(v)
{
assert_invariant();
}
@@ -2276,7 +2262,7 @@ public:
{
convert_leaf(val);
}
JSON_ASSERT(m_data.m_type == stored_kind(val.type()));
JSON_ASSERT(m_data.m_type == val.type());
set_parents();
assert_invariant();
@@ -2861,16 +2847,14 @@ public:
return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr;
}
/// get a pointer to the value (unsigned number); takes a placeholder if
/// number_unsigned_t is number_integer_t (the integer overload handles it then)
number_unsigned_t* get_impl_ptr(detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType>* /*unused*/) noexcept
/// get a pointer to the value (unsigned number)
number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept
{
return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
}
/// get a pointer to the value (unsigned number); takes a placeholder if
/// number_unsigned_t is number_integer_t (the integer overload handles it then)
constexpr const number_unsigned_t* get_impl_ptr(const detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType>* /*unused*/) const noexcept
/// get a pointer to the value (unsigned number)
constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept
{
return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
}
+201 -185
View File
@@ -4687,22 +4687,6 @@ struct is_compatible_integer_type
: is_compatible_integer_type_impl<RealIntegerType,
CompatibleNumberIntegerType> {};
// whether to_json() stores an integer of type CompatibleNumberIntegerType as
// number_integer
template<typename BasicJsonType, typename CompatibleNumberIntegerType, typename = void>
struct is_compatible_number_integer_type
: is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
// if number_unsigned_t is the same type as number_integer_t, unsigned integers
// are stored as number_integer as well (#2573)
template<typename BasicJsonType, typename CompatibleNumberIntegerType>
struct is_compatible_number_integer_type < BasicJsonType, CompatibleNumberIntegerType,
enable_if_t < std::is_same<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>::value&&
std::is_integral<CompatibleNumberIntegerType>::value&&
std::is_unsigned<CompatibleNumberIntegerType>::value&&
!std::is_same<bool, CompatibleNumberIntegerType>::value >>
: std::is_constructible<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
template<typename BasicJsonType, typename CompatibleType, typename = void>
struct is_compatible_type_impl: std::false_type {};
@@ -5040,27 +5024,6 @@ constexpr bool value_in_range_of(T val)
template<bool Value>
using bool_constant = std::integral_constant<bool, Value>;
// Stands in for number_unsigned_t in overload sets if it is the same type as
// number_integer_t, so that the unsigned overloads do not collide with the
// integer ones. Never constructed. Such a basic_json stores every integer as
// number_integer (#2573).
struct same_as_number_integer_type {};
// NumberUnsignedType, or same_as_number_integer_type if it is the same type as
// NumberIntegerType; basic_json uses this one, as it cannot name its own member
// types through an incomplete basic_json (MSVC reports a recursive alias, and
// old GCC versions crash)
template<typename NumberIntegerType, typename NumberUnsignedType>
using number_unsigned_overload_type = typename std::conditional <
std::is_same<NumberIntegerType, NumberUnsignedType>::value,
same_as_number_integer_type,
NumberUnsignedType >::type;
// number_unsigned_t of BasicJsonType, or same_as_number_integer_type if it is
// the same type as number_integer_t
template<typename BasicJsonType>
using number_unsigned_overload_t = number_unsigned_overload_type<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>;
///////////////////////////////////////////////////////////////////////////////
// is_c_string
///////////////////////////////////////////////////////////////////////////////
@@ -5813,11 +5776,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{
case value_t::number_unsigned:
{
// never null: a basic_json whose number_unsigned_t is number_integer_t
// stores no number_unsigned values (#2573)
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
JSON_ASSERT(ptr != nullptr);
val = static_cast<ArithmeticType>(*ptr);
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
break;
}
case value_t::number_integer:
@@ -5887,7 +5846,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_flo
}
template<typename BasicJsonType>
inline void from_json(const BasicJsonType& j, number_unsigned_overload_t<BasicJsonType>& val)
inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
{
get_arithmetic_value(j, val);
}
@@ -6143,11 +6102,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val)
{
case value_t::number_unsigned:
{
// never null: a basic_json whose number_unsigned_t is number_integer_t
// stores no number_unsigned values (#2573)
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
JSON_ASSERT(ptr != nullptr);
val = static_cast<ArithmeticType>(*ptr);
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
break;
}
case value_t::number_integer:
@@ -7295,14 +7250,14 @@ inline void to_json(BasicJsonType& j, FloatType val) noexcept
}
template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
enable_if_t<is_compatible_integer_type<number_unsigned_overload_t<BasicJsonType>, CompatibleNumberUnsignedType>::value, int> = 0>
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
{
external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
}
template<typename BasicJsonType, typename CompatibleNumberIntegerType,
enable_if_t<is_compatible_number_integer_type<BasicJsonType, CompatibleNumberIntegerType>::value, int> = 0>
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
{
external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
@@ -9110,7 +9065,6 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstring> // memcpy
#include <limits> // numeric_limits
#include <string> // string
#include <type_traits> // is_signed, true_type, false_type
// #include <nlohmann/detail/bit_ops.hpp>
@@ -9512,20 +9466,6 @@ NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
// whether an integer of a signed type is negative; the overload for other types
// avoids comparing an unsigned value with zero
template<typename NumberType>
constexpr bool is_negative_integer(NumberType x, std::true_type /*is_signed*/) noexcept
{
return x < 0;
}
template<typename NumberType>
constexpr bool is_negative_integer(NumberType /*unused*/, std::false_type /*is_signed*/) noexcept
{
return false;
}
/*!
@brief fast integer parser for an already-validated unsigned integer
@@ -9559,10 +9499,8 @@ bool parse_integer_unsigned(const char* first, const char* last, NumberUnsignedT
x = (x * 10u) + digit;
}
value = static_cast<NumberUnsignedType>(x);
// reject values that do not round-trip into a narrower NumberUnsignedType,
// and values that wrap around to negative numbers if it is a signed type
// (number_unsigned_t may be the same type as number_integer_t, #2573)
return static_cast<std::uint64_t>(value) == x && !is_negative_integer(value, std::is_signed<NumberUnsignedType>());
// reject values that do not round-trip into a narrower NumberUnsignedType
return static_cast<std::uint64_t>(value) == x;
}
/*!
@@ -22184,7 +22122,7 @@ class binary_writer
case value_t::number_unsigned:
{
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
break;
}
@@ -23564,7 +23502,7 @@ class binary_writer
return calc_bson_integer_size(j.m_data.m_value.number_integer);
case value_t::number_unsigned:
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
case value_t::string:
return calc_bson_string_size(*j.m_data.m_value.string, j);
@@ -23606,7 +23544,7 @@ class binary_writer
return write_bson_integer(name, j.m_data.m_value.number_integer);
case value_t::number_unsigned:
return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
case value_t::string:
return write_bson_string(name, *j.m_data.m_value.string);
@@ -26426,12 +26364,6 @@ class serializer
using number_float_t = typename BasicJsonType::number_float_t;
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
/// type of the absolute value of an integer: number_unsigned_t, or its
/// unsigned counterpart if it is signed (it may be number_integer_t, #2573)
using number_magnitude_t = typename std::conditional <
std::is_integral<number_unsigned_t>::value && std::is_signed<number_unsigned_t>::value,
std::make_unsigned<number_unsigned_t>,
std::enable_if<true, number_unsigned_t >>::type::type;
using binary_char_t = typename BasicJsonType::binary_t::value_type;
public:
@@ -27155,6 +27087,33 @@ class serializer
break;
}
}
// A byte below 0x60 is a complete code point and needs no
// UTF-8 decoding. The scanner stops only at a control
// character, a quote, or a backslash in that range, so look up
// its escape sequence and write all 8 bytes of the table entry
// straight into the write buffer; only the length stored in the
// last byte of the entry is kept.
if (data[i] < 0x60)
{
// emit any bytes still pending in string_buffer first to
// preserve output order
if (bytes != 0)
{
put_buffer(string_buffer, bytes);
bytes = 0;
}
const auto& escape = ascii_escapes()[data[i]];
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
{
flush();
}
std::memcpy(write_buffer.data() + write_buffer_pos, escape.data(), escape.size());
write_buffer_pos += static_cast<std::size_t>(escape[7]);
bytes_after_last_accept = 0;
undumped_chars = 0;
continue;
}
}
const auto byte = static_cast<std::uint8_t>(s[i]);
@@ -27163,92 +27122,39 @@ class serializer
{
case UTF8_ACCEPT: // decode found a new code point
{
switch (codepoint)
// the code point is at least 0x60 (smaller ones are handled
// above) and is not printable ASCII (which the scanner
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
// character
if (EnsureAscii)
{
case 0x08: // backspace
JSON_ASSERT(codepoint >= 0x7F);
if (error_handler == error_handler_t::keep)
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 'b';
break;
// this character was buffered as raw bytes below
// in case it turned out to be part of an
// ill-formed sequence (which is kept as is); now
// that it decoded to a well-formed code point,
// undo that and \u-escape it like any other
// character instead
bytes = bytes_after_last_accept;
}
case 0x09: // horizontal tab
if (codepoint <= 0xFFFF)
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 't';
break;
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
}
case 0x0A: // newline
else
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 'n';
break;
}
case 0x0C: // formfeed
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 'f';
break;
}
case 0x0D: // carriage return
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = 'r';
break;
}
case 0x22: // quotation mark
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = '"';
break;
}
case 0x5C: // reverse solidus
{
string_buffer[bytes++] = '\\';
string_buffer[bytes++] = '\\';
break;
}
default:
{
// escape control characters (0x00..0x1F) or, if
// EnsureAscii parameter is used, non-ASCII characters
if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F)))
{
if (EnsureAscii && error_handler == error_handler_t::keep)
{
// this character was buffered as raw bytes
// below in case it turned out to be part of
// an ill-formed sequence (which is kept as
// is); now that it decoded to a well-formed
// code point, undo that and \u-escape it
// like any other character instead
bytes = bytes_after_last_accept;
}
if (codepoint <= 0xFFFF)
{
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
}
else
{
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
}
}
else
{
// copy byte to buffer (all previous bytes
// been copied have in default case above)
string_buffer[bytes++] = s[i];
}
break;
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
}
}
else
{
// copy byte to buffer (all previous bytes of the
// sequence have been copied in the default case below)
string_buffer[bytes++] = s[i];
}
// write buffer and reset index; there must be 13 bytes
// left, as this is the maximal number of bytes to be
@@ -27610,7 +27516,7 @@ class serializer
@param[in] x unsigned integer number to count its digits
@return number of decimal digits
*/
unsigned int count_digits(number_magnitude_t x) noexcept
unsigned int count_digits(number_unsigned_t x) noexcept
{
unsigned int n_digits = 1;
for (;;)
@@ -27636,6 +27542,121 @@ class serializer
}
}
/*!
@brief the JSON form of each character below 0x60
Entry c holds the escape sequence of character c - the short form for
\\b, \\t, \\n, \\f, \\r, \\" and \\\\, \\u00xx for the other control characters -
or c itself if it needs no escaping, padded with zeros to 7 bytes. The last
byte is the length of the sequence. The 8 bytes of an entry are copied at
once, so writing an escape needs neither a branch nor a byte-wise loop.
*/
static const std::array<std::array<char, 8>, 0x60>& ascii_escapes() noexcept
{
static const std::array<std::array<char, 8>, 0x60> escapes =
{
{
{{'\\', 'u', '0', '0', '0', '0', 0, 6}}, // 0x00
{{'\\', 'u', '0', '0', '0', '1', 0, 6}}, // 0x01
{{'\\', 'u', '0', '0', '0', '2', 0, 6}}, // 0x02
{{'\\', 'u', '0', '0', '0', '3', 0, 6}}, // 0x03
{{'\\', 'u', '0', '0', '0', '4', 0, 6}}, // 0x04
{{'\\', 'u', '0', '0', '0', '5', 0, 6}}, // 0x05
{{'\\', 'u', '0', '0', '0', '6', 0, 6}}, // 0x06
{{'\\', 'u', '0', '0', '0', '7', 0, 6}}, // 0x07
{{'\\', 'b', 0, 0, 0, 0, 0, 2}}, // 0x08
{{'\\', 't', 0, 0, 0, 0, 0, 2}}, // 0x09
{{'\\', 'n', 0, 0, 0, 0, 0, 2}}, // 0x0A
{{'\\', 'u', '0', '0', '0', 'b', 0, 6}}, // 0x0B
{{'\\', 'f', 0, 0, 0, 0, 0, 2}}, // 0x0C
{{'\\', 'r', 0, 0, 0, 0, 0, 2}}, // 0x0D
{{'\\', 'u', '0', '0', '0', 'e', 0, 6}}, // 0x0E
{{'\\', 'u', '0', '0', '0', 'f', 0, 6}}, // 0x0F
{{'\\', 'u', '0', '0', '1', '0', 0, 6}}, // 0x10
{{'\\', 'u', '0', '0', '1', '1', 0, 6}}, // 0x11
{{'\\', 'u', '0', '0', '1', '2', 0, 6}}, // 0x12
{{'\\', 'u', '0', '0', '1', '3', 0, 6}}, // 0x13
{{'\\', 'u', '0', '0', '1', '4', 0, 6}}, // 0x14
{{'\\', 'u', '0', '0', '1', '5', 0, 6}}, // 0x15
{{'\\', 'u', '0', '0', '1', '6', 0, 6}}, // 0x16
{{'\\', 'u', '0', '0', '1', '7', 0, 6}}, // 0x17
{{'\\', 'u', '0', '0', '1', '8', 0, 6}}, // 0x18
{{'\\', 'u', '0', '0', '1', '9', 0, 6}}, // 0x19
{{'\\', 'u', '0', '0', '1', 'a', 0, 6}}, // 0x1A
{{'\\', 'u', '0', '0', '1', 'b', 0, 6}}, // 0x1B
{{'\\', 'u', '0', '0', '1', 'c', 0, 6}}, // 0x1C
{{'\\', 'u', '0', '0', '1', 'd', 0, 6}}, // 0x1D
{{'\\', 'u', '0', '0', '1', 'e', 0, 6}}, // 0x1E
{{'\\', 'u', '0', '0', '1', 'f', 0, 6}}, // 0x1F
{{' ', 0, 0, 0, 0, 0, 0, 1}}, // 0x20
{{'!', 0, 0, 0, 0, 0, 0, 1}}, // 0x21
{{'\\', '"', 0, 0, 0, 0, 0, 2}}, // 0x22
{{'#', 0, 0, 0, 0, 0, 0, 1}}, // 0x23
{{'$', 0, 0, 0, 0, 0, 0, 1}}, // 0x24
{{'%', 0, 0, 0, 0, 0, 0, 1}}, // 0x25
{{'&', 0, 0, 0, 0, 0, 0, 1}}, // 0x26
{{'\'', 0, 0, 0, 0, 0, 0, 1}}, // 0x27
{{'(', 0, 0, 0, 0, 0, 0, 1}}, // 0x28
{{')', 0, 0, 0, 0, 0, 0, 1}}, // 0x29
{{'*', 0, 0, 0, 0, 0, 0, 1}}, // 0x2A
{{'+', 0, 0, 0, 0, 0, 0, 1}}, // 0x2B
{{',', 0, 0, 0, 0, 0, 0, 1}}, // 0x2C
{{'-', 0, 0, 0, 0, 0, 0, 1}}, // 0x2D
{{'.', 0, 0, 0, 0, 0, 0, 1}}, // 0x2E
{{'/', 0, 0, 0, 0, 0, 0, 1}}, // 0x2F
{{'0', 0, 0, 0, 0, 0, 0, 1}}, // 0x30
{{'1', 0, 0, 0, 0, 0, 0, 1}}, // 0x31
{{'2', 0, 0, 0, 0, 0, 0, 1}}, // 0x32
{{'3', 0, 0, 0, 0, 0, 0, 1}}, // 0x33
{{'4', 0, 0, 0, 0, 0, 0, 1}}, // 0x34
{{'5', 0, 0, 0, 0, 0, 0, 1}}, // 0x35
{{'6', 0, 0, 0, 0, 0, 0, 1}}, // 0x36
{{'7', 0, 0, 0, 0, 0, 0, 1}}, // 0x37
{{'8', 0, 0, 0, 0, 0, 0, 1}}, // 0x38
{{'9', 0, 0, 0, 0, 0, 0, 1}}, // 0x39
{{':', 0, 0, 0, 0, 0, 0, 1}}, // 0x3A
{{';', 0, 0, 0, 0, 0, 0, 1}}, // 0x3B
{{'<', 0, 0, 0, 0, 0, 0, 1}}, // 0x3C
{{'=', 0, 0, 0, 0, 0, 0, 1}}, // 0x3D
{{'>', 0, 0, 0, 0, 0, 0, 1}}, // 0x3E
{{'?', 0, 0, 0, 0, 0, 0, 1}}, // 0x3F
{{'@', 0, 0, 0, 0, 0, 0, 1}}, // 0x40
{{'A', 0, 0, 0, 0, 0, 0, 1}}, // 0x41
{{'B', 0, 0, 0, 0, 0, 0, 1}}, // 0x42
{{'C', 0, 0, 0, 0, 0, 0, 1}}, // 0x43
{{'D', 0, 0, 0, 0, 0, 0, 1}}, // 0x44
{{'E', 0, 0, 0, 0, 0, 0, 1}}, // 0x45
{{'F', 0, 0, 0, 0, 0, 0, 1}}, // 0x46
{{'G', 0, 0, 0, 0, 0, 0, 1}}, // 0x47
{{'H', 0, 0, 0, 0, 0, 0, 1}}, // 0x48
{{'I', 0, 0, 0, 0, 0, 0, 1}}, // 0x49
{{'J', 0, 0, 0, 0, 0, 0, 1}}, // 0x4A
{{'K', 0, 0, 0, 0, 0, 0, 1}}, // 0x4B
{{'L', 0, 0, 0, 0, 0, 0, 1}}, // 0x4C
{{'M', 0, 0, 0, 0, 0, 0, 1}}, // 0x4D
{{'N', 0, 0, 0, 0, 0, 0, 1}}, // 0x4E
{{'O', 0, 0, 0, 0, 0, 0, 1}}, // 0x4F
{{'P', 0, 0, 0, 0, 0, 0, 1}}, // 0x50
{{'Q', 0, 0, 0, 0, 0, 0, 1}}, // 0x51
{{'R', 0, 0, 0, 0, 0, 0, 1}}, // 0x52
{{'S', 0, 0, 0, 0, 0, 0, 1}}, // 0x53
{{'T', 0, 0, 0, 0, 0, 0, 1}}, // 0x54
{{'U', 0, 0, 0, 0, 0, 0, 1}}, // 0x55
{{'V', 0, 0, 0, 0, 0, 0, 1}}, // 0x56
{{'W', 0, 0, 0, 0, 0, 0, 1}}, // 0x57
{{'X', 0, 0, 0, 0, 0, 0, 1}}, // 0x58
{{'Y', 0, 0, 0, 0, 0, 0, 1}}, // 0x59
{{'Z', 0, 0, 0, 0, 0, 0, 1}}, // 0x5A
{{'[', 0, 0, 0, 0, 0, 0, 1}}, // 0x5B
{{'\\', '\\', 0, 0, 0, 0, 0, 2}}, // 0x5C
{{']', 0, 0, 0, 0, 0, 0, 1}}, // 0x5D
{{'^', 0, 0, 0, 0, 0, 0, 1}}, // 0x5E
{{'_', 0, 0, 0, 0, 0, 0, 1}}, // 0x5F
}
};
return escapes;
}
/*!
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
*
@@ -27788,7 +27809,7 @@ class serializer
// use a pointer to fill the buffer
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)
number_magnitude_t abs_value;
number_unsigned_t abs_value;
unsigned int n_chars{};
@@ -27802,7 +27823,7 @@ class serializer
}
else
{
abs_value = static_cast<number_magnitude_t>(x);
abs_value = static_cast<number_unsigned_t>(x);
n_chars = count_digits(abs_value);
}
@@ -27945,6 +27966,17 @@ class serializer
}
}
/*
* Overload to make the compiler happy while it is instantiating
* dump_integer for number_unsigned_t.
* Must never be called.
*/
number_unsigned_t remove_sign(number_unsigned_t x)
{
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
return x; // LCOV_EXCL_LINE
}
/*
* Helper function for dump_integer
*
@@ -27954,10 +27986,10 @@ class serializer
* absolute values of INT_MIN and INT_MAX are usually not the same. See
* #1708 for details.
*/
number_magnitude_t remove_sign(number_integer_t x) noexcept
number_unsigned_t remove_sign(number_integer_t x) noexcept
{
JSON_ASSERT(x < 0);
return static_cast<number_magnitude_t>(-(x + 1)) + 1;
return static_cast<number_unsigned_t>(-(x + 1)) + 1;
}
private:
@@ -28841,22 +28873,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
"NumberUnsignedType must be at least as wide as NumberIntegerType, "
"because it has to hold the absolute value of every NumberIntegerType value");
// NumberUnsignedType may be the same type as NumberIntegerType (#2573), but
// a repeated NumberFloatType would give overloads that collide, so it gets
// a readable diagnostic instead.
static_assert(!std::is_same<NumberFloatType, NumberIntegerType>::value && !std::is_same<NumberFloatType, NumberUnsignedType>::value,
"NumberFloatType must be a different type than NumberIntegerType and NumberUnsignedType");
private:
/// the kind a value of kind @a t is stored as: if number_unsigned_t is the
/// same type as number_integer_t, every integer is stored as number_integer
/// (#2573); every other kind is stored as itself
static constexpr value_t stored_kind(const value_t t) noexcept
{
return (std::is_same<number_integer_t, number_unsigned_t>::value && t == value_t::number_unsigned) ? value_t::number_integer : t;
}
/// helper for exception-safe object creation
template<typename T, typename... Args>
JSON_HEDLEY_RETURNS_NON_NULL
@@ -28930,8 +28948,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
json_value(boolean_t v) noexcept : boolean(v) {}
/// constructor for numbers (integer)
json_value(number_integer_t v) noexcept : number_integer(v) {}
/// constructor for numbers (unsigned); takes a placeholder if number_unsigned_t is number_integer_t
json_value(detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType> v) noexcept : number_unsigned(v) {}
/// constructor for numbers (unsigned)
json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}
/// constructor for numbers (floating-point)
json_value(number_float_t v) noexcept : number_float(v) {}
/// constructor for empty values of a given type
@@ -30536,7 +30554,7 @@ public:
/// @brief create an empty value with a given type
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
basic_json(const value_t v)
: m_data(stored_kind(v))
: m_data(v)
{
assert_invariant();
}
@@ -30617,7 +30635,7 @@ public:
{
convert_leaf(val);
}
JSON_ASSERT(m_data.m_type == stored_kind(val.type()));
JSON_ASSERT(m_data.m_type == val.type());
set_parents();
assert_invariant();
@@ -31202,16 +31220,14 @@ public:
return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr;
}
/// get a pointer to the value (unsigned number); takes a placeholder if
/// number_unsigned_t is number_integer_t (the integer overload handles it then)
number_unsigned_t* get_impl_ptr(detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType>* /*unused*/) noexcept
/// get a pointer to the value (unsigned number)
number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept
{
return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
}
/// get a pointer to the value (unsigned number); takes a placeholder if
/// number_unsigned_t is number_integer_t (the integer overload handles it then)
constexpr const number_unsigned_t* get_impl_ptr(const detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType>* /*unused*/) const noexcept
/// get a pointer to the value (unsigned number)
constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept
{
return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
}
-201
View File
@@ -1,201 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#include <cstdint>
#include <functional>
#include <limits>
#include <string>
#include <vector>
// basic_json with the same type for number_integer_t and number_unsigned_t
// (#2573): every integer is stored as number_integer
namespace
{
template<typename IntegerType>
using same_types_json = nlohmann::json::with_integers_t<IntegerType, IntegerType>;
using json64 = same_types_json<std::int64_t>;
using json32 = same_types_json<std::int32_t>;
} // namespace
TEST_CASE_TEMPLATE("same type for number_integer_t and number_unsigned_t", Json, json64, json32)
{
using integer_t = typename Json::number_integer_t;
using limits = std::numeric_limits<integer_t>;
SECTION("parsing stores integers as number_integer")
{
const Json j = Json::parse("[0, 1, -1, 2147483647, -2147483648]");
for (const auto& element : j)
{
CHECK(element.is_number_integer());
CHECK(!element.is_number_unsigned());
CHECK(element.type() == nlohmann::detail::value_t::number_integer);
}
CHECK(j[1].template get<integer_t>() == 1);
CHECK(j[2].template get<integer_t>() == -1);
CHECK(j[3].template get<integer_t>() == 2147483647);
CHECK(j[4].template get<std::int64_t>() == -2147483648LL);
}
SECTION("integers above the maximum are parsed as floats")
{
// a positive literal is parsed as unsigned first; it must not wrap
// around to a negative number_integer_t
const auto above_max = std::to_string(static_cast<std::uint64_t>(limits::max()) + 1);
const Json j1 = Json::parse(above_max);
CHECK(j1.is_number_float());
CHECK(j1.template get<double>() == static_cast<double>(static_cast<std::uint64_t>(limits::max()) + 1));
const Json j2 = Json::parse("18446744073709551615");
CHECK(j2.is_number_float());
CHECK(j2.template get<double>() == 18446744073709551615.0);
const Json j3 = Json::parse(std::to_string(limits::max()));
CHECK(j3.is_number_integer());
CHECK(j3.template get<integer_t>() == limits::max());
}
SECTION("serialization of the extremes")
{
CHECK(Json(limits::max()).dump() == std::to_string(limits::max()));
// the absolute value of the minimum does not fit into number_unsigned_t
CHECK(Json(limits::min()).dump() == std::to_string(limits::min()));
CHECK(Json::parse(std::to_string(limits::min())) == Json(limits::min()));
CHECK(Json(0).dump() == "0");
}
SECTION("construction from unsigned types")
{
const Json j1 = 5u;
CHECK(j1.is_number_integer());
CHECK(!j1.is_number_unsigned());
CHECK(j1 == 5);
const Json j2 = static_cast<std::uint8_t>(200);
CHECK(j2.is_number_integer());
CHECK(j2.template get<integer_t>() == 200);
const Json j3 = std::vector<unsigned> {1, 2, 3};
CHECK(j3.dump() == "[1,2,3]");
CHECK(j3[0].is_number_integer());
const Json j4 = true;
CHECK(j4.is_boolean());
}
SECTION("value_t::number_unsigned creates a number_integer")
{
const Json j(nlohmann::detail::value_t::number_unsigned);
CHECK(j.is_number_integer());
CHECK(!j.is_number_unsigned());
CHECK(j == 0);
CHECK(j.dump() == "0");
}
SECTION("conversions")
{
Json j = 42;
CHECK(j.template get<unsigned>() == 42u);
CHECK(j.template get<std::uint64_t>() == 42u);
CHECK(j.template get<int>() == 42);
CHECK(j.template get<double>() == 42.0);
CHECK(j.template get_ptr<integer_t*>() != nullptr);
CHECK(*j.template get_ptr<integer_t*>() == 42);
CHECK(j.template get_ref<integer_t&>() == 42);
j.template get_ref<integer_t&>() = 43;
CHECK(j == 43);
integer_t i = 0;
j.get_to(i);
CHECK(i == 43);
const Json object = {{"a", 1}};
CHECK(object.value("a", 0u) == 1u);
CHECK(object.value("b", 7u) == 7u);
}
SECTION("conversion from a basic_json with distinct types")
{
const nlohmann::json j = {1, 2u, -3, 4.5};
CHECK(j[1].is_number_unsigned());
const Json converted = j;
CHECK(converted.dump() == "[1,2,-3,4.5]");
CHECK(converted[1].is_number_integer());
CHECK(!converted[1].is_number_unsigned());
const nlohmann::json back = converted;
CHECK(back == j);
}
SECTION("comparison and hashing")
{
const Json j1 = 1;
const Json j2 = 2u;
CHECK(j1 < j2);
CHECK(j1 != j2);
CHECK(j2 == 2);
CHECK(j2 == 2u);
CHECK(j1 < 1.5);
CHECK(Json(-1) < Json(0u));
CHECK(std::hash<Json> {}(Json(5u)) == std::hash<Json> {}(Json(5)));
}
SECTION("JSON Patch, diff, and flatten")
{
const Json source = Json::parse(R"({"a": [1, 2, 3], "b": 4})");
const Json target = Json::parse(R"({"a": [1, 5], "c": 6})");
CHECK(source.patch(Json::diff(source, target)) == target);
CHECK(source.flatten().unflatten() == source);
}
SECTION("binary formats")
{
const Json j = {{"min", limits::min()}, {"max", limits::max()}, {"zero", 0}, {"small", 100}, {"negative", -100}, {"float", 1.5}};
CHECK(Json::from_cbor(Json::to_cbor(j)) == j);
CHECK(Json::from_msgpack(Json::to_msgpack(j)) == j);
CHECK(Json::from_ubjson(Json::to_ubjson(j)) == j);
CHECK(Json::from_bjdata(Json::to_bjdata(j)) == j);
CHECK(Json::from_bson(Json::to_bson(j)) == j);
CHECK(Json::from_bon8(Json::to_bon8(j)) == j);
for (const auto& element : Json::from_cbor(Json::to_cbor(j)))
{
CHECK(!element.is_number_unsigned());
}
// CBOR unsigned integer 2^64-1 does not fit and is read as float
const std::vector<std::uint8_t> cbor_max = {0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
const Json from_cbor = Json::from_cbor(cbor_max);
CHECK(from_cbor.is_number_float());
CHECK(from_cbor.template get<double>() == 18446744073709551615.0);
// MessagePack uint 8 is read as number_integer
const std::vector<std::uint8_t> msgpack_uint8 = {0xCC, 0xC8};
const Json from_msgpack = Json::from_msgpack(msgpack_uint8);
CHECK(from_msgpack.is_number_integer());
CHECK(from_msgpack == 200);
}
}
TEST_CASE("same type for number_integer_t and number_unsigned_t: type traits")
{
CHECK(std::is_same<nlohmann::detail::number_unsigned_overload_t<json64>, nlohmann::detail::same_as_number_integer_type>::value);
CHECK(std::is_same<nlohmann::detail::number_unsigned_overload_t<nlohmann::json>, std::uint64_t>::value);
CHECK(nlohmann::detail::is_compatible_number_integer_type<json64, unsigned>::value);
CHECK(!nlohmann::detail::is_compatible_number_integer_type<nlohmann::json, unsigned>::value);
CHECK(!nlohmann::detail::is_compatible_number_integer_type<json64, bool>::value);
}
+155 -24
View File
@@ -923,66 +923,68 @@ TEST_CASE("serialization boundary values for the string buffer")
// mid-string once fewer than 13 bytes remain (`string_buffer.size() - bytes
// < 13`), 13 being one more than the most a single code point can ever
// write at once (a surrogate pair: two back-to-back "\uXXXX" escapes, 12
// bytes). Every write into string_buffer that this check protects happens
// in steps of 2 (a simple "\\x" escape) or 6 (one "\uXXXX" unit), so
// `bytes` only ever takes even values at the point the check runs - the
// tightest values actually reachable are therefore 498 (512 - 498 == 14,
// one simple escape away from the threshold) and 500 (512 - 500 == 12,
// where the flush fires immediately and resets bytes to 0).
// bytes). Characters below 0x60 (control characters, '"' and '\\') are
// escaped straight into the write buffer and never reach string_buffer, so
// under ensure_ascii it fills in steps of 6 (one "\uXXXX" unit for a
// character below U+10000) or 12 (a surrogate pair). With 2-byte characters
// such as U+00E4, `bytes` takes multiples of 6 at the point the check runs;
// the tightest values reachable are therefore 498 (512 - 498 == 14, after
// 83 characters) and 504 (512 - 504 == 8, after 84 characters, where the
// flush fires immediately and resets bytes to 0).
SECTION("a run of 2-byte escapes lands bytes on, and one step past, the flush threshold")
SECTION("a run of 6-byte escapes lands bytes on, and one step past, the flush threshold")
{
for (const int count :
{
249, 250, 251
83, 84, 85
})
{
CAPTURE(count)
const json j = std::string(static_cast<std::size_t>(count), '\n');
std::string umlauts;
std::string expected = "\"";
for (int i = 0; i < count; ++i)
{
expected += "\\n";
umlauts += "\xC3\xA4";
expected += "\\u00e4";
}
expected += '"';
CHECK(j.dump() == expected);
CHECK(json(umlauts).dump(-1, ' ', true) == expected);
}
}
SECTION("an ASCII prefix leaves the tightest reachable margin before a 12-byte surrogate pair")
SECTION("a prefix leaves the tightest reachable margin before a 12-byte surrogate pair")
{
// U+1F600 (the "\xF0\x9F\x98\x80" UTF-8 bytes) is dumped under
// ensure_ascii as the 12-byte surrogate pair "\ud83d\ude00"; that
// write happens in a single step with no intermediate flush check, so
// it is the write most exposed by an off-by-one in the "< 13" guard.
// A prefix of 249 newlines leaves exactly 14 bytes of headroom
// A prefix of 83 U+00E4 characters leaves exactly 14 bytes of headroom
// (512 - 498), the smallest margin the guard ever actually allows
// into a new code point; 250 newlines instead trigger the guard's own
// into a new code point; 84 characters instead trigger the guard's own
// flush first, so the emoji starts from a freshly emptied (512-byte)
// buffer, and 251 repeats that with one more escape already past the
// buffer, and 85 repeats that with one more escape already past the
// reset. Together they cover the margin the guard allows landing on,
// one step before, and one step after - all must still produce the
// identical, correct escapes.
for (const int prefix_count :
{
249, 250, 251
83, 84, 85
})
{
CAPTURE(prefix_count)
const std::string prefix(static_cast<std::size_t>(prefix_count), '\n');
const std::string emoji = "\xF0\x9F\x98\x80";
const json j = prefix + emoji;
std::string prefix;
std::string expected_prefix;
for (int i = 0; i < prefix_count; ++i)
{
expected_prefix += "\\n";
prefix += "\xC3\xA4";
expected_prefix += "\\u00e4";
}
const std::string emoji = "\xF0\x9F\x98\x80";
const json j = prefix + emoji;
// newline escaping does not depend on ensure_ascii: only the
// emoji differs (raw UTF-8 bytes vs. a \u-escaped surrogate pair)
// without ensure_ascii, the whole string is copied unchanged
std::string expected_raw = "\"";
expected_raw += expected_prefix;
expected_raw += prefix;
expected_raw += emoji;
expected_raw += '"';
std::string expected_ascii = "\"";
@@ -1027,3 +1029,132 @@ TEST_CASE("serialization boundary values for the string buffer")
}
}
}
namespace
{
// reference escaper for ASCII strings, written independently of the table in
// serializer::ascii_escapes()
std::string reference_escape_ascii(const std::string& s, const bool ensure_ascii)
{
std::string result = "\"";
for (const char c : s)
{
const auto byte = static_cast<unsigned char>(c);
switch (byte)
{
case '\b':
result += "\\b";
break;
case '\t':
result += "\\t";
break;
case '\n':
result += "\\n";
break;
case '\f':
result += "\\f";
break;
case '\r':
result += "\\r";
break;
case '"':
result += "\\\"";
break;
case '\\':
result += "\\\\";
break;
default:
if (byte < 0x20 || (ensure_ascii && byte == 0x7F))
{
// byte < 0x80, so the first two hex digits are 0
const char* const hex = "0123456789abcdef";
result += "\\u00";
result += hex[byte >> 4U];
result += hex[byte & 0x0FU];
}
else
{
result += c;
}
break;
}
}
result += '"';
return result;
}
} // namespace
TEST_CASE("characters below 0x60 are escaped from a table straight into the write buffer")
{
SECTION("every ASCII character at every position")
{
// lengths up to 17 cover the 8-byte strides of the bulk scanner on
// both sides of the character, so the scanner stops at it from every
// offset
for (std::size_t length = 1; length <= 17; ++length)
{
for (std::size_t pos = 0; pos < length; ++pos)
{
for (unsigned int c = 0; c < 0x80; ++c)
{
std::string s(length, 'a');
s[pos] = static_cast<char>(c);
CAPTURE(length)
CAPTURE(pos)
CAPTURE(c)
const json j = s;
CHECK(j.dump(-1, ' ', false) == reference_escape_ascii(s, false));
CHECK(j.dump(-1, ' ', true) == reference_escape_ascii(s, true));
}
}
}
}
SECTION("runs of escaped characters")
{
std::string s;
for (unsigned int c = 0; c < 0x20; ++c)
{
s += static_cast<char>(c);
}
s += R"("\"\)";
CHECK(json(s).dump() == reference_escape_ascii(s, false));
CHECK(json(s).dump(-1, ' ', true) == reference_escape_ascii(s, true));
}
SECTION("escapes straddle the end of the write buffer")
{
// the 8-byte store of a table entry needs room for all 8 bytes, even
// when the escape itself is shorter; a prefix of k plain characters
// shifts the 6-byte escapes across every offset of the 1024-byte
// write buffer
for (std::size_t k = 0; k < 8; ++k)
{
CAPTURE(k)
const std::string s = std::string(k, 'a') + std::string(400, '\x01');
CHECK(json(s).dump() == reference_escape_ascii(s, false));
CHECK(json::array({s, s}).dump() == "[" + reference_escape_ascii(s, false) + "," + reference_escape_ascii(s, false) + "]");
}
}
SECTION("pending \\u escapes of non-ASCII characters are written first")
{
// under ensure_ascii, U+00E4 is \u-escaped into the string buffer;
// a following escape from the table must not overtake it
const json j = "\xC3\xA4\n\xC3\xA4\"\xF0\x9F\x98\x80\\";
CHECK(j.dump(-1, ' ', true) == R"("\u00e4\n\u00e4\"\ud83d\ude00\\")");
CHECK(j.dump(-1, ' ', false) == "\"\xC3\xA4\\n\xC3\xA4\\\"\xF0\x9F\x98\x80\\\\\"");
}
SECTION("escapes after invalid UTF-8 for every error handler")
{
const json j = "a\xFF\n\xC3\"";
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xFF", json::type_error&);
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == R"("a\n\"")");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::ignore) == R"("a\n\"")");
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"a\xEF\xBF\xBD\\n\xEF\xBF\xBD\\\"\"");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == R"("a\ufffd\n\ufffd\"")");
CHECK(j.dump(-1, ' ', false, json::error_handler_t::keep) == "\"a\xFF\\n\xC3\\\"\"");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::keep) == "\"a\xFF\\n\xC3\\\"\"");
}
}