mirror of
https://github.com/nlohmann/json.git
synced 2026-09-27 18:20:32 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6508d1d0e1 | ||
|
|
df9a000e3a | ||
|
|
f682cd2ef1 | ||
|
|
6bd106893a | ||
|
|
98e00d22e5 | ||
|
|
f7972970a4 | ||
|
|
6178982b8d | ||
|
|
85f8b21e1c | ||
|
|
4fa95d9810 | ||
|
|
fe4a544c7e |
@@ -363,7 +363,7 @@ std::cout << j_string << " == " << serialized_string << std::endl;
|
||||
|
||||
[`.dump()`](https://json.nlohmann.me/api/basic_json/dump/) returns the originally stored string value.
|
||||
|
||||
Note the library only supports UTF-8. When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace`, `json::error_handler_t::ignore`, or `json::error_handler_t::keep` are used as error handlers.
|
||||
Note the library only supports UTF-8. When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers.
|
||||
|
||||
#### To/from streams (e.g., files, string streams)
|
||||
|
||||
@@ -1907,7 +1907,7 @@ The library supports **Unicode input** as follows:
|
||||
- [Unicode noncharacters](https://www.unicode.org/faq/private_use.html#nonchar1) will not be replaced by the library.
|
||||
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
|
||||
- The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs.
|
||||
- When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace`, `json::error_handler_t::ignore`, or `json::error_handler_t::keep` are used as error handlers.
|
||||
- When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers.
|
||||
- To store wide strings (e.g., `std::wstring`), you need to convert them to a UTF-8 encoded `std::string` before, see [an example](https://json.nlohmann.me/home/faq/#wide-string-handling).
|
||||
|
||||
### Comments in JSON
|
||||
|
||||
@@ -18,7 +18,7 @@ ignore
|
||||
: ignore tags
|
||||
|
||||
store
|
||||
: store tagged values as binary container with subtype (for bytes 0xd8..0xdb)
|
||||
: store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -25,15 +25,10 @@ and `ensure_ascii` parameters.
|
||||
result consists of ASCII characters only.
|
||||
|
||||
`error_handler` (in)
|
||||
: how to react on decoding errors; there are four possible values (see [`error_handler_t`](error_handler_t.md)):
|
||||
|
||||
- `strict`: throw a [`type_error`](../../home/exceptions.md#type-errors) exception in case a decoding error occurs
|
||||
(default),
|
||||
- `replace`: replace invalid UTF-8 sequences with U+FFFD (� REPLACEMENT CHARACTER),
|
||||
- `ignore`: ignore invalid UTF-8 sequences during serialization; all valid bytes are copied to the output unchanged,
|
||||
and invalid bytes are dropped, and
|
||||
- `keep`: keep invalid UTF-8 sequences during serialization; all bytes are copied to the output unchanged, so the
|
||||
result is not valid UTF-8.
|
||||
: how to react on decoding errors; there are three possible values (see [`error_handler_t`](error_handler_t.md):
|
||||
`strict` (throws an exception in case a decoding error occurs; default), `replace` (replace invalid UTF-8 sequences
|
||||
with U+FFFD), and `ignore` (ignore invalid UTF-8 sequences during serialization; all valid bytes are copied to the
|
||||
output unchanged, and invalid bytes are dropped)).
|
||||
|
||||
## Return value
|
||||
|
||||
@@ -99,4 +94,3 @@ Binary values are serialized as an object containing two keys:
|
||||
- Indentation character `indent_char`, option `ensure_ascii` and exceptions added in version 3.0.0.
|
||||
- Error handlers added in version 3.4.0.
|
||||
- Serialization of binary values added in version 3.8.0.
|
||||
- Error handler value `keep` added in version 3.13.0.
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
enum class error_handler_t {
|
||||
strict,
|
||||
replace,
|
||||
ignore,
|
||||
keep
|
||||
ignore
|
||||
};
|
||||
```
|
||||
|
||||
This enumeration is used in the [`dump`](dump.md) function to choose how to treat decoding errors while serializing a
|
||||
`basic_json` value. Four values are differentiated:
|
||||
`basic_json` value. Three values are differentiated:
|
||||
|
||||
strict
|
||||
: throw a `type_error` exception in case of invalid UTF-8
|
||||
@@ -21,12 +20,6 @@ replace
|
||||
ignore
|
||||
: ignore invalid UTF-8 sequences; all valid bytes are copied to the output unchanged, and invalid bytes are dropped
|
||||
|
||||
keep
|
||||
: keep invalid UTF-8 sequences; all bytes are copied to the output unchanged. Valid characters are still escaped as
|
||||
usual (e.g., `"`, `\\`, and control characters), so the result has valid JSON syntax, but it is not valid UTF-8.
|
||||
In particular, [`parse`](parse.md) rejects it, and with `ensure_ascii` set to `true`, the invalid bytes are the
|
||||
only non-ASCII bytes of the output.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -47,4 +40,3 @@ keep
|
||||
## Version history
|
||||
|
||||
- Added in version 3.4.0.
|
||||
- Added value `keep` in version 3.13.0.
|
||||
|
||||
@@ -80,8 +80,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
the end of the file was not reached when `strict` was set to true
|
||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
|
||||
used in the given input or if the input is not valid CBOR
|
||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
|
||||
but not found
|
||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other
|
||||
types are not supported, as JSON object keys are always strings) or a string is malformed
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
the end of the file was not reached when `strict` was set to true
|
||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from
|
||||
MessagePack were used in the given input or if the input is not valid MessagePack
|
||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
|
||||
but not found
|
||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other
|
||||
types are not supported, as JSON object keys are always strings) or a string is malformed
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -34,6 +34,15 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
||||
|
||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a string, binary
|
||||
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
|
||||
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
|
||||
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
|
||||
exceeds 255, the maximum of the MessagePack ext type; example:
|
||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the size of the JSON value `j`.
|
||||
@@ -65,3 +74,4 @@ Linear in the size of the JSON value `j`.
|
||||
## Version history
|
||||
|
||||
- Added in version 2.0.9.
|
||||
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -22,12 +21,4 @@ int main()
|
||||
<< "\nstring with ignored invalid characters: "
|
||||
<< j_invalid.dump(-1, ' ', false, json::error_handler_t::ignore)
|
||||
<< '\n';
|
||||
|
||||
// the invalid byte is kept; print the result byte-wise to make it visible
|
||||
std::cout << "string with kept invalid characters:";
|
||||
for (const unsigned char c : j_invalid.dump(-1, ' ', false, json::error_handler_t::keep))
|
||||
{
|
||||
std::cout << ' ' << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c);
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9
|
||||
string with replaced invalid characters: "ä�ü"
|
||||
string with ignored invalid characters: "äü"
|
||||
string with kept invalid characters: 22 c3 a4 a9 c3 bc 22
|
||||
|
||||
@@ -174,7 +174,20 @@ The library maps CBOR types to JSON value types as follows:
|
||||
|
||||
!!! warning "Object keys"
|
||||
|
||||
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected.
|
||||
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps
|
||||
with keys other than text strings (major type 3) are rejected with a
|
||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions` set
|
||||
to `false`, a discarded value) naming the type of the key that was found, for instance:
|
||||
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
|
||||
```
|
||||
|
||||
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
|
||||
on. This is a deliberate restriction of the library's JSON value model, not an oversight: formats built on CBOR
|
||||
maps with integer keys, such as COSE ([RFC 9052](https://www.rfc-editor.org/rfc/rfc9052.html)) or CWT
|
||||
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
|
||||
general-purpose CBOR library instead.
|
||||
|
||||
!!! warning "UTF-8 validation of text strings"
|
||||
|
||||
@@ -188,7 +201,7 @@ The library maps CBOR types to JSON value types as follows:
|
||||
|
||||
!!! warning "Tagged items"
|
||||
|
||||
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. They can be stored by passing `cbor_tag_handler_t::store` to function `from_cbor`. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
|
||||
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
|
||||
|
||||
??? example
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ specification:
|
||||
- arrays with more than 4294967295 elements
|
||||
- objects with more than 4294967295 elements
|
||||
|
||||
Serializing such a value throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412).
|
||||
|
||||
!!! info "NaN/infinity handling"
|
||||
|
||||
`NaN`, `Infinity`, and `-Infinity` are serialized as a MessagePack float 32 (type 0xCA, 5 bytes total),
|
||||
@@ -136,6 +138,21 @@ The library maps MessagePack types to JSON value types as follows:
|
||||
|
||||
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
||||
|
||||
!!! warning "Object keys"
|
||||
|
||||
MessagePack allows map keys of any type, whereas JSON only allows strings as keys in object values. Like the
|
||||
JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in the
|
||||
MessagePack specification, this library restricts map keys to `str` values. Maps with keys of any other type are
|
||||
rejected with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
||||
`allow_exceptions` set to `false`, a discarded value) naming the type of the key that was found, for instance:
|
||||
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
|
||||
```
|
||||
|
||||
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
|
||||
on. Such input needs a general-purpose MessagePack library instead.
|
||||
|
||||
!!! warning "UTF-8 validation of string values"
|
||||
|
||||
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
|
||||
|
||||
@@ -64,7 +64,6 @@ serialization fails by default. The fourth argument of `dump` selects an
|
||||
- `strict` (default) — throw a [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) exception.
|
||||
- `replace` — replace invalid bytes with the Unicode replacement character U+FFFD (`�`).
|
||||
- `ignore` — silently drop invalid bytes.
|
||||
- `keep` — copy invalid bytes to the output unchanged; the result is not valid UTF-8.
|
||||
|
||||
??? example
|
||||
|
||||
|
||||
@@ -343,13 +343,20 @@ A string could not be read from a [binary format](../features/binary_formats/ind
|
||||
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
||||
the string's bytes are not valid UTF-8.
|
||||
|
||||
CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other
|
||||
type (for instance integers or `null`) are therefore not supported; see the notes on
|
||||
[CBOR](../features/binary_formats/cbor.md) and [MessagePack](../features/binary_formats/messagepack.md).
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
|
||||
@@ -755,7 +762,6 @@ The `dump()` function only works with UTF-8 encoded strings; that is, if you ass
|
||||
- Pass an error handler as last parameter to the `dump()` function to avoid this exception:
|
||||
- `json::error_handler_t::replace` will replace invalid bytes sequences with `U+FFFD`
|
||||
- `json::error_handler_t::ignore` will silently ignore invalid byte sequences
|
||||
- `json::error_handler_t::keep` will copy invalid byte sequences to the output unchanged
|
||||
|
||||
### json.exception.type_error.317
|
||||
|
||||
@@ -933,19 +939,25 @@ A JSON Patch `add` operation cannot be applied because the target location's par
|
||||
|
||||
### json.exception.out_of_range.412
|
||||
|
||||
BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer. This exception is thrown when a value is too large to be described by such a length field.
|
||||
BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer, and MessagePack
|
||||
stores the length of strings, binary values, arrays, and objects in at most an unsigned 32-bit integer. This exception
|
||||
is thrown when a value is too large to be described by such a length field.
|
||||
|
||||
!!! failure "Example message"
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
BSON length 2147483661 exceeds maximum of 2147483647
|
||||
```
|
||||
```
|
||||
MessagePack length 4294967296 exceeds maximum of 4294967295
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This exception was added in version 3.13.0. Before that, the length was silently truncated, and
|
||||
This exception was added in version 3.13.0. Before that, the BSON length was silently truncated, and
|
||||
[`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
|
||||
[`from_bson`](../api/basic_json/from_bson.md) rejected.
|
||||
[`from_bson`](../api/basic_json/from_bson.md) rejected; [`to_msgpack`](../api/basic_json/to_msgpack.md) wrote such
|
||||
a value without any length, producing output that could not be read back.
|
||||
|
||||
### json.exception.out_of_range.413
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ The library supports **Unicode input** as follows:
|
||||
- The library will not replace [Unicode noncharacters](http://www.unicode.org/faq/private_use.html#nonchar1).
|
||||
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
|
||||
- The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs.
|
||||
- When you store strings with different encodings in the library, calling [`dump()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a50ec80b02d0f3f51130d4abb5d1cfdc5.html#a50ec80b02d0f3f51130d4abb5d1cfdc5) may throw an exception unless `json::error_handler_t::replace`, `json::error_handler_t::ignore`, or `json::error_handler_t::keep` are used as error handlers.
|
||||
- When you store strings with different encodings in the library, calling [`dump()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a50ec80b02d0f3f51130d4abb5d1cfdc5.html#a50ec80b02d0f3f51130d4abb5d1cfdc5) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers.
|
||||
|
||||
In most cases, the parser is right to complain, because the input is not UTF-8 encoded. This is especially true for Microsoft Windows, where Latin-1 or ISO 8859-1 is often the standard encoding.
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ enum class cbor_tag_handler_t
|
||||
{
|
||||
error, ///< throw a parse_error exception in case of a tag
|
||||
ignore, ///< ignore tags
|
||||
store ///< store tags as binary type
|
||||
store ///< store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -592,14 +592,18 @@ class binary_reader
|
||||
input (true) or whether the last read character should
|
||||
be considered instead (false)
|
||||
@param[in] tag_handler how CBOR tags should be treated
|
||||
@param[out] tag_pending whether a tag was parsed and its value follows
|
||||
@param[out] item_read whether the tagged value's initial byte is already in current
|
||||
|
||||
@return whether a valid CBOR value was passed to the SAX parser
|
||||
*/
|
||||
bool parse_cbor_value(const bool get_char,
|
||||
const cbor_tag_handler_t tag_handler,
|
||||
bool& tag_pending)
|
||||
bool& tag_pending,
|
||||
bool& item_read)
|
||||
{
|
||||
tag_pending = false;
|
||||
item_read = false;
|
||||
|
||||
switch (get_char ? get() : current)
|
||||
{
|
||||
@@ -1021,7 +1025,17 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
get();
|
||||
return get_cbor_binary(b) && sax->binary(b);
|
||||
// a byte string (the heads accepted by get_cbor_binary) keeps the tag as subtype
|
||||
if ((current >= 0x40 && current <= 0x5B) || current == 0x5F)
|
||||
{
|
||||
return get_cbor_binary(b) && sax->binary(b);
|
||||
}
|
||||
|
||||
// not a byte string: the tagged value, whose first byte
|
||||
// was just read, is read by the caller like for ignore
|
||||
tag_pending = true;
|
||||
item_read = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
default: // LCOV_EXCL_LINE
|
||||
@@ -1249,6 +1263,80 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR object key
|
||||
|
||||
RFC 8949 allows any data item as a map key, but only strings have a
|
||||
counterpart in JSON. A key of any other type is rejected with a message
|
||||
naming that type, rather than the one @ref get_cbor_string gives for a
|
||||
malformed string.
|
||||
|
||||
@param[out] result created key
|
||||
|
||||
@return whether key creation completed
|
||||
*/
|
||||
bool get_cbor_object_key(string_t& result)
|
||||
{
|
||||
// EOF and major type 3 (text string) are left to get_cbor_string
|
||||
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
|
||||
{
|
||||
return get_cbor_string(result);
|
||||
}
|
||||
|
||||
const char* found = nullptr;
|
||||
switch (static_cast<unsigned int>(current) >> 5u)
|
||||
{
|
||||
case 0:
|
||||
found = "an unsigned integer";
|
||||
break;
|
||||
case 1:
|
||||
found = "a negative integer";
|
||||
break;
|
||||
case 2:
|
||||
found = "a byte string";
|
||||
break;
|
||||
case 4:
|
||||
found = "an array";
|
||||
break;
|
||||
case 5:
|
||||
found = "a map";
|
||||
break;
|
||||
case 6:
|
||||
found = "a tag";
|
||||
break;
|
||||
default: // major type 7
|
||||
switch (current)
|
||||
{
|
||||
case 0xF4:
|
||||
case 0xF5:
|
||||
found = "a boolean";
|
||||
break;
|
||||
case 0xF6:
|
||||
found = "null";
|
||||
break;
|
||||
case 0xF7:
|
||||
found = "undefined";
|
||||
break;
|
||||
case 0xF9:
|
||||
case 0xFA:
|
||||
case 0xFB:
|
||||
found = "a floating-point number";
|
||||
break;
|
||||
case 0xFF:
|
||||
found = "a break stop code";
|
||||
break;
|
||||
default:
|
||||
found = "a simple value";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||
exception_message(input_format_t::cbor, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a definite-length CBOR byte array
|
||||
|
||||
@@ -1493,7 +1581,7 @@ class binary_reader
|
||||
if (top.is_object)
|
||||
{
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1503,13 +1591,14 @@ class binary_reader
|
||||
|
||||
// a tag is not a value of its own: read on until the tagged value
|
||||
bool tag_pending = false;
|
||||
bool item_read = false;
|
||||
do
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending, item_read)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fetch = true;
|
||||
fetch = !item_read;
|
||||
}
|
||||
while (tag_pending);
|
||||
|
||||
@@ -1993,6 +2082,98 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a MessagePack object key
|
||||
|
||||
The MessagePack specification allows any type as a map key, but only
|
||||
strings have a counterpart in JSON. A key of any other type is rejected
|
||||
with a message naming that type, rather than the one @ref
|
||||
get_msgpack_string gives for a malformed string.
|
||||
|
||||
@param[out] result created key
|
||||
|
||||
@return whether key creation completed
|
||||
*/
|
||||
bool get_msgpack_object_key(string_t& result)
|
||||
{
|
||||
const char* found = nullptr;
|
||||
switch (current)
|
||||
{
|
||||
case 0xC0:
|
||||
found = "nil";
|
||||
break;
|
||||
case 0xC2:
|
||||
case 0xC3:
|
||||
found = "a boolean";
|
||||
break;
|
||||
case 0xCA:
|
||||
case 0xCB:
|
||||
found = "a float";
|
||||
break;
|
||||
case 0xC4:
|
||||
case 0xC5:
|
||||
case 0xC6:
|
||||
found = "a bin";
|
||||
break;
|
||||
case 0xC7:
|
||||
case 0xC8:
|
||||
case 0xC9:
|
||||
case 0xD4:
|
||||
case 0xD5:
|
||||
case 0xD6:
|
||||
case 0xD7:
|
||||
case 0xD8:
|
||||
found = "an ext";
|
||||
break;
|
||||
case 0xCC:
|
||||
case 0xCD:
|
||||
case 0xCE:
|
||||
case 0xCF:
|
||||
case 0xD0:
|
||||
case 0xD1:
|
||||
case 0xD2:
|
||||
case 0xD3:
|
||||
found = "an integer";
|
||||
break;
|
||||
case 0xDC:
|
||||
case 0xDD:
|
||||
found = "an array";
|
||||
break;
|
||||
case 0xDE:
|
||||
case 0xDF:
|
||||
found = "a map";
|
||||
break;
|
||||
default:
|
||||
// fixint, fixmap, and fixarray; strings, EOF, and the unused
|
||||
// byte 0xC1 are left to get_msgpack_string
|
||||
if (current == char_traits<char_type>::eof())
|
||||
{
|
||||
return get_msgpack_string(result);
|
||||
}
|
||||
if (current <= 0x7F || current >= 0xE0)
|
||||
{
|
||||
found = "an integer";
|
||||
}
|
||||
else if (current <= 0x8F)
|
||||
{
|
||||
found = "a map";
|
||||
}
|
||||
else if (current <= 0x9F)
|
||||
{
|
||||
found = "an array";
|
||||
}
|
||||
else
|
||||
{
|
||||
return get_msgpack_string(result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||
exception_message(input_format_t::msgpack, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a MessagePack byte array
|
||||
|
||||
@@ -2155,7 +2336,7 @@ class binary_reader
|
||||
{
|
||||
get();
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -168,92 +168,20 @@ class binary_writer
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers. Therefore, we used the
|
||||
// code from the value_t::number_unsigned case here.
|
||||
if (j.m_data.m_value.number_integer <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x18));
|
||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x19));
|
||||
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x1A));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_character(to_char_type(0x1B));
|
||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
// integers and unsigned integers
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The conversions below encode the sign in the first
|
||||
// byte, and the value is converted to a positive number.
|
||||
const auto positive_number = -1 - j.m_data.m_value.number_integer;
|
||||
if (j.m_data.m_value.number_integer >= -24)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0x20 + positive_number));
|
||||
}
|
||||
else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x38));
|
||||
write_number(static_cast<std::uint8_t>(positive_number));
|
||||
}
|
||||
else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x39));
|
||||
write_number(static_cast<std::uint16_t>(positive_number));
|
||||
}
|
||||
else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x3A));
|
||||
write_number(static_cast<std::uint32_t>(positive_number));
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_character(to_char_type(0x3B));
|
||||
write_number(static_cast<std::uint64_t>(positive_number));
|
||||
}
|
||||
// a negative integer n is encoded as -1 - n
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (j.m_data.m_value.number_unsigned <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x18));
|
||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x19));
|
||||
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x1A));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_character(to_char_type(0x1B));
|
||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -283,33 +211,7 @@ class binary_writer
|
||||
case value_t::string:
|
||||
{
|
||||
// step 1: write control byte and the string length
|
||||
const auto N = j.m_data.m_value.string->size();
|
||||
if (N <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0x60 + N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x78));
|
||||
write_number(static_cast<std::uint8_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x79));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x7A));
|
||||
write_number(static_cast<std::uint32_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x7B));
|
||||
write_number(static_cast<std::uint64_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
write_cbor_head(0x60, j.m_data.m_value.string->size());
|
||||
|
||||
// step 2: write the string
|
||||
oa.write_characters(
|
||||
@@ -321,33 +223,7 @@ class binary_writer
|
||||
case value_t::array:
|
||||
{
|
||||
// step 1: write control byte and the array size
|
||||
const auto N = j.m_data.m_value.array->size();
|
||||
if (N <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0x80 + N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x98));
|
||||
write_number(static_cast<std::uint8_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x99));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x9A));
|
||||
write_number(static_cast<std::uint32_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x9B));
|
||||
write_number(static_cast<std::uint64_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
|
||||
// step 2: write each element
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
@@ -376,7 +252,7 @@ class binary_writer
|
||||
write_number(static_cast<std::uint8_t>(0xda));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));
|
||||
}
|
||||
else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
else
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0xdb));
|
||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));
|
||||
@@ -385,32 +261,7 @@ class binary_writer
|
||||
|
||||
// step 1: write control byte and the binary array size
|
||||
const auto N = j.m_data.m_value.binary->size();
|
||||
if (N <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0x40 + N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x58));
|
||||
write_number(static_cast<std::uint8_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x59));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x5A));
|
||||
write_number(static_cast<std::uint32_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x5B));
|
||||
write_number(static_cast<std::uint64_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
write_cbor_head(0x40, N);
|
||||
|
||||
// step 2: write each element
|
||||
oa.write_characters(
|
||||
@@ -423,33 +274,7 @@ class binary_writer
|
||||
case value_t::object:
|
||||
{
|
||||
// step 1: write control byte and the object size
|
||||
const auto N = j.m_data.m_value.object->size();
|
||||
if (N <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0xA0 + N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0xB8));
|
||||
write_number(static_cast<std::uint8_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0xB9));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0xBA));
|
||||
write_number(static_cast<std::uint32_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0xBB));
|
||||
write_number(static_cast<std::uint64_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||
|
||||
// step 2: write each element
|
||||
for (const auto& el : *j.m_data.m_value.object)
|
||||
@@ -466,6 +291,23 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check that @a length fits into the 32 bits that MessagePack stores
|
||||
the length of a string, binary value, array, or object in
|
||||
@return the length as an unsigned 32-bit integer
|
||||
@throw out_of_range.412 if @a length exceeds the range of std::uint32_t
|
||||
*/
|
||||
static std::uint32_t to_msgpack_length(const std::size_t length, const BasicJsonType& j)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::uint32_t>(length)))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
|
||||
}
|
||||
|
||||
static_cast<void>(j);
|
||||
return static_cast<std::uint32_t>(length);
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
*/
|
||||
@@ -517,7 +359,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xCE));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
else
|
||||
{
|
||||
// uint 64
|
||||
oa.write_character(to_char_type(0xCF));
|
||||
@@ -552,8 +394,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
@@ -588,7 +429,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xCE));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
else
|
||||
{
|
||||
// uint 64
|
||||
oa.write_character(to_char_type(0xCF));
|
||||
@@ -606,7 +447,7 @@ class binary_writer
|
||||
case value_t::string:
|
||||
{
|
||||
// step 1: write control byte and the string length
|
||||
const auto N = j.m_data.m_value.string->size();
|
||||
const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j);
|
||||
if (N <= 31)
|
||||
{
|
||||
// fixstr
|
||||
@@ -624,7 +465,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xDA));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
else
|
||||
{
|
||||
// str 32
|
||||
oa.write_character(to_char_type(0xDB));
|
||||
@@ -641,7 +482,7 @@ class binary_writer
|
||||
case value_t::array:
|
||||
{
|
||||
// step 1: write control byte and the array size
|
||||
const auto N = j.m_data.m_value.array->size();
|
||||
const auto N = to_msgpack_length(j.m_data.m_value.array->size(), j);
|
||||
if (N <= 15)
|
||||
{
|
||||
// fixarray
|
||||
@@ -653,7 +494,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xDC));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
else
|
||||
{
|
||||
// array 32
|
||||
oa.write_character(to_char_type(0xDD));
|
||||
@@ -675,7 +516,7 @@ class binary_writer
|
||||
const bool use_ext = j.m_data.m_value.binary->has_subtype();
|
||||
|
||||
// step 1: write control byte and the byte string length
|
||||
const auto N = j.m_data.m_value.binary->size();
|
||||
const auto N = to_msgpack_length(j.m_data.m_value.binary->size(), j);
|
||||
if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
std::uint8_t output_type{};
|
||||
@@ -727,7 +568,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(output_type));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
else
|
||||
{
|
||||
const std::uint8_t output_type = use_ext
|
||||
? 0xC9 // ext 32
|
||||
@@ -759,7 +600,7 @@ class binary_writer
|
||||
case value_t::object:
|
||||
{
|
||||
// step 1: write control byte and the object size
|
||||
const auto N = j.m_data.m_value.object->size();
|
||||
const auto N = to_msgpack_length(j.m_data.m_value.object->size(), j);
|
||||
if (N <= 15)
|
||||
{
|
||||
// fixmap
|
||||
@@ -771,7 +612,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xDE));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
else
|
||||
{
|
||||
// map 32
|
||||
oa.write_character(to_char_type(0xDF));
|
||||
@@ -1526,6 +1367,46 @@ class binary_writer
|
||||
// CBOR //
|
||||
//////////
|
||||
|
||||
/*!
|
||||
@brief write the head of a CBOR data item
|
||||
|
||||
The head is the major type in the upper three bits of the first byte and
|
||||
an argument - an unsigned integer, the length of a string, the number of
|
||||
elements of a container - in the shortest of its encodings: in the lower
|
||||
five bits of the first byte itself if it is at most 23, otherwise in the
|
||||
1, 2, 4, or 8 bytes that follow (RFC 8949, section 3).
|
||||
|
||||
@param[in] major_type the major type, shifted into the upper three bits
|
||||
@param[in] argument the argument of the data item
|
||||
*/
|
||||
void write_cbor_head(const std::uint8_t major_type, const std::uint64_t argument)
|
||||
{
|
||||
if (argument <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(major_type + argument));
|
||||
}
|
||||
else if (argument <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x18)));
|
||||
write_number(static_cast<std::uint8_t>(argument));
|
||||
}
|
||||
else if (argument <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x19)));
|
||||
write_number(static_cast<std::uint16_t>(argument));
|
||||
}
|
||||
else if (argument <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1A)));
|
||||
write_number(static_cast<std::uint32_t>(argument));
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1B)));
|
||||
write_number(argument);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr CharType get_cbor_float_prefix(float /*unused*/)
|
||||
{
|
||||
return to_char_type(0xFA); // Single-Precision Float
|
||||
@@ -1631,7 +1512,7 @@ class binary_writer
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)())
|
||||
else if (use_bjdata)
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
@@ -1711,30 +1592,59 @@ class binary_writer
|
||||
}
|
||||
write_number(static_cast<uint32_t>(n), use_bjdata);
|
||||
}
|
||||
else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('L')); // int64
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('H')); // high-precision number
|
||||
}
|
||||
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
}
|
||||
// every value of an integer type of at most 64 bits fits into an
|
||||
// int64; only a wider type needs a range check
|
||||
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata,
|
||||
std::integral_constant < bool, std::numeric_limits<NumberType>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/)
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('L')); // int64
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/)
|
||||
{
|
||||
if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
|
||||
{
|
||||
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('H')); // high-precision number
|
||||
}
|
||||
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept
|
||||
{
|
||||
// anything outside of the range of an int64 is treated as a
|
||||
// high-precision number
|
||||
return ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)()) ? 'L' : 'H';
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1776,12 +1686,10 @@ class binary_writer
|
||||
{
|
||||
return 'm';
|
||||
}
|
||||
if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
// anything else is treated as a high-precision number
|
||||
return 'H'; // LCOV_EXCL_LINE
|
||||
// every value of an integer type of at most 64 bits fits into
|
||||
// an int64; only a wider type needs a range check
|
||||
return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer,
|
||||
std::integral_constant < bool, std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
@@ -1814,12 +1722,12 @@ class binary_writer
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
if (use_bjdata)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
// anything else is treated as a high-precision number
|
||||
return 'H'; // LCOV_EXCL_LINE
|
||||
return 'H';
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
|
||||
@@ -48,8 +48,7 @@ enum class error_handler_t
|
||||
{
|
||||
strict, ///< throw a type_error exception in case of invalid UTF-8
|
||||
replace, ///< replace invalid UTF-8 sequences with U+FFFD
|
||||
ignore, ///< ignore invalid UTF-8 sequences
|
||||
keep ///< keep invalid UTF-8 sequences; their bytes are copied unchanged
|
||||
ignore ///< ignore invalid UTF-8 sequences
|
||||
};
|
||||
|
||||
template<typename BasicJsonType>
|
||||
@@ -1020,47 +1019,6 @@ class serializer
|
||||
break;
|
||||
}
|
||||
|
||||
case error_handler_t::keep:
|
||||
{
|
||||
// drop whatever the incomplete sequence left in
|
||||
// the buffer (only copied if !EnsureAscii) and copy
|
||||
// the ill-formed bytes from the input instead
|
||||
bytes = bytes_after_last_accept;
|
||||
|
||||
if (undumped_chars > 0)
|
||||
{
|
||||
// the pending bytes of the incomplete sequence
|
||||
// are ill-formed; the current byte may be OK for
|
||||
// itself, so we would like to read it again
|
||||
for (std::size_t j = i - undumped_chars; j < i; ++j)
|
||||
{
|
||||
string_buffer[bytes++] = s[j];
|
||||
}
|
||||
--i;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the current byte cannot start any sequence
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
bytes_after_last_accept = bytes;
|
||||
undumped_chars = 0;
|
||||
|
||||
// continue processing the string
|
||||
state = UTF8_ACCEPT;
|
||||
break;
|
||||
}
|
||||
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -1106,15 +1064,6 @@ class serializer
|
||||
break;
|
||||
}
|
||||
|
||||
case error_handler_t::keep:
|
||||
{
|
||||
// write all accepted bytes
|
||||
put_buffer(string_buffer, bytes_after_last_accept);
|
||||
// copy the bytes of the incomplete sequence unchanged
|
||||
put_string(s, s.size() - undumped_chars, s.size());
|
||||
break;
|
||||
}
|
||||
|
||||
case error_handler_t::replace:
|
||||
{
|
||||
// write all accepted bytes
|
||||
|
||||
+20
-16
@@ -1492,13 +1492,28 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
compare_keys(current.lhs_object_it->first, current.rhs_object_it->first,
|
||||
std::integral_constant<bool, Ordered> {});
|
||||
|
||||
if (key_result != compare_result::equal)
|
||||
{
|
||||
return key_result;
|
||||
}
|
||||
|
||||
left = &(current.lhs_object_it->second);
|
||||
right = &(current.rhs_object_it->second);
|
||||
|
||||
if (key_result != compare_result::equal)
|
||||
{
|
||||
// An object type without a fixed order of its entries -
|
||||
// std::unordered_map, say - may enumerate two equal
|
||||
// objects differently, and its operator== does not care.
|
||||
// Equality then finds the entry by its key; an ordering,
|
||||
// or an object type that compares its entries in
|
||||
// sequence (ordered_map), is decided by the key itself.
|
||||
const auto* rhs_object = current.rhs_value->m_data.m_value.object;
|
||||
const auto found = (!Ordered && !detail::is_ordered_map<object_t>::value)
|
||||
? rhs_object->find(current.lhs_object_it->first)
|
||||
: rhs_object->cend();
|
||||
if (found == rhs_object->cend())
|
||||
{
|
||||
return key_result;
|
||||
}
|
||||
right = &(found->second);
|
||||
}
|
||||
|
||||
++current.lhs_object_it;
|
||||
++current.rhs_object_it;
|
||||
}
|
||||
@@ -2157,17 +2172,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// value access //
|
||||
//////////////////
|
||||
|
||||
/// get a boolean (explicit)
|
||||
boolean_t get_impl(boolean_t* /*unused*/) const
|
||||
{
|
||||
if (JSON_HEDLEY_LIKELY(is_boolean()))
|
||||
{
|
||||
return m_data.m_value.boolean;
|
||||
}
|
||||
|
||||
JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this));
|
||||
}
|
||||
|
||||
/// get a pointer to the value (object)
|
||||
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
|
||||
{
|
||||
|
||||
+344
-302
@@ -12741,7 +12741,7 @@ enum class cbor_tag_handler_t
|
||||
{
|
||||
error, ///< throw a parse_error exception in case of a tag
|
||||
ignore, ///< ignore tags
|
||||
store ///< store tags as binary type
|
||||
store ///< store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -13289,14 +13289,18 @@ class binary_reader
|
||||
input (true) or whether the last read character should
|
||||
be considered instead (false)
|
||||
@param[in] tag_handler how CBOR tags should be treated
|
||||
@param[out] tag_pending whether a tag was parsed and its value follows
|
||||
@param[out] item_read whether the tagged value's initial byte is already in current
|
||||
|
||||
@return whether a valid CBOR value was passed to the SAX parser
|
||||
*/
|
||||
bool parse_cbor_value(const bool get_char,
|
||||
const cbor_tag_handler_t tag_handler,
|
||||
bool& tag_pending)
|
||||
bool& tag_pending,
|
||||
bool& item_read)
|
||||
{
|
||||
tag_pending = false;
|
||||
item_read = false;
|
||||
|
||||
switch (get_char ? get() : current)
|
||||
{
|
||||
@@ -13718,7 +13722,17 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
get();
|
||||
return get_cbor_binary(b) && sax->binary(b);
|
||||
// a byte string (the heads accepted by get_cbor_binary) keeps the tag as subtype
|
||||
if ((current >= 0x40 && current <= 0x5B) || current == 0x5F)
|
||||
{
|
||||
return get_cbor_binary(b) && sax->binary(b);
|
||||
}
|
||||
|
||||
// not a byte string: the tagged value, whose first byte
|
||||
// was just read, is read by the caller like for ignore
|
||||
tag_pending = true;
|
||||
item_read = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
default: // LCOV_EXCL_LINE
|
||||
@@ -13946,6 +13960,80 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a CBOR object key
|
||||
|
||||
RFC 8949 allows any data item as a map key, but only strings have a
|
||||
counterpart in JSON. A key of any other type is rejected with a message
|
||||
naming that type, rather than the one @ref get_cbor_string gives for a
|
||||
malformed string.
|
||||
|
||||
@param[out] result created key
|
||||
|
||||
@return whether key creation completed
|
||||
*/
|
||||
bool get_cbor_object_key(string_t& result)
|
||||
{
|
||||
// EOF and major type 3 (text string) are left to get_cbor_string
|
||||
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
|
||||
{
|
||||
return get_cbor_string(result);
|
||||
}
|
||||
|
||||
const char* found = nullptr;
|
||||
switch (static_cast<unsigned int>(current) >> 5u)
|
||||
{
|
||||
case 0:
|
||||
found = "an unsigned integer";
|
||||
break;
|
||||
case 1:
|
||||
found = "a negative integer";
|
||||
break;
|
||||
case 2:
|
||||
found = "a byte string";
|
||||
break;
|
||||
case 4:
|
||||
found = "an array";
|
||||
break;
|
||||
case 5:
|
||||
found = "a map";
|
||||
break;
|
||||
case 6:
|
||||
found = "a tag";
|
||||
break;
|
||||
default: // major type 7
|
||||
switch (current)
|
||||
{
|
||||
case 0xF4:
|
||||
case 0xF5:
|
||||
found = "a boolean";
|
||||
break;
|
||||
case 0xF6:
|
||||
found = "null";
|
||||
break;
|
||||
case 0xF7:
|
||||
found = "undefined";
|
||||
break;
|
||||
case 0xF9:
|
||||
case 0xFA:
|
||||
case 0xFB:
|
||||
found = "a floating-point number";
|
||||
break;
|
||||
case 0xFF:
|
||||
found = "a break stop code";
|
||||
break;
|
||||
default:
|
||||
found = "a simple value";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||
exception_message(input_format_t::cbor, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a definite-length CBOR byte array
|
||||
|
||||
@@ -14190,7 +14278,7 @@ class binary_reader
|
||||
if (top.is_object)
|
||||
{
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -14200,13 +14288,14 @@ class binary_reader
|
||||
|
||||
// a tag is not a value of its own: read on until the tagged value
|
||||
bool tag_pending = false;
|
||||
bool item_read = false;
|
||||
do
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending, item_read)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fetch = true;
|
||||
fetch = !item_read;
|
||||
}
|
||||
while (tag_pending);
|
||||
|
||||
@@ -14690,6 +14779,98 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a MessagePack object key
|
||||
|
||||
The MessagePack specification allows any type as a map key, but only
|
||||
strings have a counterpart in JSON. A key of any other type is rejected
|
||||
with a message naming that type, rather than the one @ref
|
||||
get_msgpack_string gives for a malformed string.
|
||||
|
||||
@param[out] result created key
|
||||
|
||||
@return whether key creation completed
|
||||
*/
|
||||
bool get_msgpack_object_key(string_t& result)
|
||||
{
|
||||
const char* found = nullptr;
|
||||
switch (current)
|
||||
{
|
||||
case 0xC0:
|
||||
found = "nil";
|
||||
break;
|
||||
case 0xC2:
|
||||
case 0xC3:
|
||||
found = "a boolean";
|
||||
break;
|
||||
case 0xCA:
|
||||
case 0xCB:
|
||||
found = "a float";
|
||||
break;
|
||||
case 0xC4:
|
||||
case 0xC5:
|
||||
case 0xC6:
|
||||
found = "a bin";
|
||||
break;
|
||||
case 0xC7:
|
||||
case 0xC8:
|
||||
case 0xC9:
|
||||
case 0xD4:
|
||||
case 0xD5:
|
||||
case 0xD6:
|
||||
case 0xD7:
|
||||
case 0xD8:
|
||||
found = "an ext";
|
||||
break;
|
||||
case 0xCC:
|
||||
case 0xCD:
|
||||
case 0xCE:
|
||||
case 0xCF:
|
||||
case 0xD0:
|
||||
case 0xD1:
|
||||
case 0xD2:
|
||||
case 0xD3:
|
||||
found = "an integer";
|
||||
break;
|
||||
case 0xDC:
|
||||
case 0xDD:
|
||||
found = "an array";
|
||||
break;
|
||||
case 0xDE:
|
||||
case 0xDF:
|
||||
found = "a map";
|
||||
break;
|
||||
default:
|
||||
// fixint, fixmap, and fixarray; strings, EOF, and the unused
|
||||
// byte 0xC1 are left to get_msgpack_string
|
||||
if (current == char_traits<char_type>::eof())
|
||||
{
|
||||
return get_msgpack_string(result);
|
||||
}
|
||||
if (current <= 0x7F || current >= 0xE0)
|
||||
{
|
||||
found = "an integer";
|
||||
}
|
||||
else if (current <= 0x8F)
|
||||
{
|
||||
found = "a map";
|
||||
}
|
||||
else if (current <= 0x9F)
|
||||
{
|
||||
found = "an array";
|
||||
}
|
||||
else
|
||||
{
|
||||
return get_msgpack_string(result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||
exception_message(input_format_t::msgpack, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reads a MessagePack byte array
|
||||
|
||||
@@ -14852,7 +15033,7 @@ class binary_reader
|
||||
{
|
||||
get();
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -19633,92 +19814,20 @@ class binary_writer
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers. Therefore, we used the
|
||||
// code from the value_t::number_unsigned case here.
|
||||
if (j.m_data.m_value.number_integer <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x18));
|
||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x19));
|
||||
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x1A));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_character(to_char_type(0x1B));
|
||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
// integers and unsigned integers
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The conversions below encode the sign in the first
|
||||
// byte, and the value is converted to a positive number.
|
||||
const auto positive_number = -1 - j.m_data.m_value.number_integer;
|
||||
if (j.m_data.m_value.number_integer >= -24)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0x20 + positive_number));
|
||||
}
|
||||
else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x38));
|
||||
write_number(static_cast<std::uint8_t>(positive_number));
|
||||
}
|
||||
else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x39));
|
||||
write_number(static_cast<std::uint16_t>(positive_number));
|
||||
}
|
||||
else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x3A));
|
||||
write_number(static_cast<std::uint32_t>(positive_number));
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_character(to_char_type(0x3B));
|
||||
write_number(static_cast<std::uint64_t>(positive_number));
|
||||
}
|
||||
// a negative integer n is encoded as -1 - n
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (j.m_data.m_value.number_unsigned <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x18));
|
||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x19));
|
||||
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x1A));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_character(to_char_type(0x1B));
|
||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
}
|
||||
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -19748,33 +19857,7 @@ class binary_writer
|
||||
case value_t::string:
|
||||
{
|
||||
// step 1: write control byte and the string length
|
||||
const auto N = j.m_data.m_value.string->size();
|
||||
if (N <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0x60 + N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x78));
|
||||
write_number(static_cast<std::uint8_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x79));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x7A));
|
||||
write_number(static_cast<std::uint32_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x7B));
|
||||
write_number(static_cast<std::uint64_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
write_cbor_head(0x60, j.m_data.m_value.string->size());
|
||||
|
||||
// step 2: write the string
|
||||
oa.write_characters(
|
||||
@@ -19786,33 +19869,7 @@ class binary_writer
|
||||
case value_t::array:
|
||||
{
|
||||
// step 1: write control byte and the array size
|
||||
const auto N = j.m_data.m_value.array->size();
|
||||
if (N <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0x80 + N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x98));
|
||||
write_number(static_cast<std::uint8_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x99));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x9A));
|
||||
write_number(static_cast<std::uint32_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x9B));
|
||||
write_number(static_cast<std::uint64_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
|
||||
// step 2: write each element
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
@@ -19841,7 +19898,7 @@ class binary_writer
|
||||
write_number(static_cast<std::uint8_t>(0xda));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));
|
||||
}
|
||||
else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
else
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0xdb));
|
||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));
|
||||
@@ -19850,32 +19907,7 @@ class binary_writer
|
||||
|
||||
// step 1: write control byte and the binary array size
|
||||
const auto N = j.m_data.m_value.binary->size();
|
||||
if (N <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0x40 + N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x58));
|
||||
write_number(static_cast<std::uint8_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x59));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x5A));
|
||||
write_number(static_cast<std::uint32_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x5B));
|
||||
write_number(static_cast<std::uint64_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
write_cbor_head(0x40, N);
|
||||
|
||||
// step 2: write each element
|
||||
oa.write_characters(
|
||||
@@ -19888,33 +19920,7 @@ class binary_writer
|
||||
case value_t::object:
|
||||
{
|
||||
// step 1: write control byte and the object size
|
||||
const auto N = j.m_data.m_value.object->size();
|
||||
if (N <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(0xA0 + N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0xB8));
|
||||
write_number(static_cast<std::uint8_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0xB9));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0xBA));
|
||||
write_number(static_cast<std::uint32_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0xBB));
|
||||
write_number(static_cast<std::uint64_t>(N));
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||
|
||||
// step 2: write each element
|
||||
for (const auto& el : *j.m_data.m_value.object)
|
||||
@@ -19931,6 +19937,23 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check that @a length fits into the 32 bits that MessagePack stores
|
||||
the length of a string, binary value, array, or object in
|
||||
@return the length as an unsigned 32-bit integer
|
||||
@throw out_of_range.412 if @a length exceeds the range of std::uint32_t
|
||||
*/
|
||||
static std::uint32_t to_msgpack_length(const std::size_t length, const BasicJsonType& j)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::uint32_t>(length)))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
|
||||
}
|
||||
|
||||
static_cast<void>(j);
|
||||
return static_cast<std::uint32_t>(length);
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
*/
|
||||
@@ -19982,7 +20005,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xCE));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
else
|
||||
{
|
||||
// uint 64
|
||||
oa.write_character(to_char_type(0xCF));
|
||||
@@ -20017,8 +20040,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
@@ -20053,7 +20075,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xCE));
|
||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
else
|
||||
{
|
||||
// uint 64
|
||||
oa.write_character(to_char_type(0xCF));
|
||||
@@ -20071,7 +20093,7 @@ class binary_writer
|
||||
case value_t::string:
|
||||
{
|
||||
// step 1: write control byte and the string length
|
||||
const auto N = j.m_data.m_value.string->size();
|
||||
const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j);
|
||||
if (N <= 31)
|
||||
{
|
||||
// fixstr
|
||||
@@ -20089,7 +20111,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xDA));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
else
|
||||
{
|
||||
// str 32
|
||||
oa.write_character(to_char_type(0xDB));
|
||||
@@ -20106,7 +20128,7 @@ class binary_writer
|
||||
case value_t::array:
|
||||
{
|
||||
// step 1: write control byte and the array size
|
||||
const auto N = j.m_data.m_value.array->size();
|
||||
const auto N = to_msgpack_length(j.m_data.m_value.array->size(), j);
|
||||
if (N <= 15)
|
||||
{
|
||||
// fixarray
|
||||
@@ -20118,7 +20140,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xDC));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
else
|
||||
{
|
||||
// array 32
|
||||
oa.write_character(to_char_type(0xDD));
|
||||
@@ -20140,7 +20162,7 @@ class binary_writer
|
||||
const bool use_ext = j.m_data.m_value.binary->has_subtype();
|
||||
|
||||
// step 1: write control byte and the byte string length
|
||||
const auto N = j.m_data.m_value.binary->size();
|
||||
const auto N = to_msgpack_length(j.m_data.m_value.binary->size(), j);
|
||||
if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
std::uint8_t output_type{};
|
||||
@@ -20192,7 +20214,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(output_type));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
else
|
||||
{
|
||||
const std::uint8_t output_type = use_ext
|
||||
? 0xC9 // ext 32
|
||||
@@ -20224,7 +20246,7 @@ class binary_writer
|
||||
case value_t::object:
|
||||
{
|
||||
// step 1: write control byte and the object size
|
||||
const auto N = j.m_data.m_value.object->size();
|
||||
const auto N = to_msgpack_length(j.m_data.m_value.object->size(), j);
|
||||
if (N <= 15)
|
||||
{
|
||||
// fixmap
|
||||
@@ -20236,7 +20258,7 @@ class binary_writer
|
||||
oa.write_character(to_char_type(0xDE));
|
||||
write_number(static_cast<std::uint16_t>(N));
|
||||
}
|
||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
else
|
||||
{
|
||||
// map 32
|
||||
oa.write_character(to_char_type(0xDF));
|
||||
@@ -20991,6 +21013,46 @@ class binary_writer
|
||||
// CBOR //
|
||||
//////////
|
||||
|
||||
/*!
|
||||
@brief write the head of a CBOR data item
|
||||
|
||||
The head is the major type in the upper three bits of the first byte and
|
||||
an argument - an unsigned integer, the length of a string, the number of
|
||||
elements of a container - in the shortest of its encodings: in the lower
|
||||
five bits of the first byte itself if it is at most 23, otherwise in the
|
||||
1, 2, 4, or 8 bytes that follow (RFC 8949, section 3).
|
||||
|
||||
@param[in] major_type the major type, shifted into the upper three bits
|
||||
@param[in] argument the argument of the data item
|
||||
*/
|
||||
void write_cbor_head(const std::uint8_t major_type, const std::uint64_t argument)
|
||||
{
|
||||
if (argument <= 0x17)
|
||||
{
|
||||
write_number(static_cast<std::uint8_t>(major_type + argument));
|
||||
}
|
||||
else if (argument <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x18)));
|
||||
write_number(static_cast<std::uint8_t>(argument));
|
||||
}
|
||||
else if (argument <= (std::numeric_limits<std::uint16_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x19)));
|
||||
write_number(static_cast<std::uint16_t>(argument));
|
||||
}
|
||||
else if (argument <= (std::numeric_limits<std::uint32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1A)));
|
||||
write_number(static_cast<std::uint32_t>(argument));
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1B)));
|
||||
write_number(argument);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr CharType get_cbor_float_prefix(float /*unused*/)
|
||||
{
|
||||
return to_char_type(0xFA); // Single-Precision Float
|
||||
@@ -21096,7 +21158,7 @@ class binary_writer
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)())
|
||||
else if (use_bjdata)
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
@@ -21176,30 +21238,59 @@ class binary_writer
|
||||
}
|
||||
write_number(static_cast<uint32_t>(n), use_bjdata);
|
||||
}
|
||||
else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('L')); // int64
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('H')); // high-precision number
|
||||
}
|
||||
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
}
|
||||
// every value of an integer type of at most 64 bits fits into an
|
||||
// int64; only a wider type needs a range check
|
||||
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata,
|
||||
std::integral_constant < bool, std::numeric_limits<NumberType>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/)
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('L')); // int64
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/)
|
||||
{
|
||||
if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
|
||||
{
|
||||
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('H')); // high-precision number
|
||||
}
|
||||
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept
|
||||
{
|
||||
// anything outside of the range of an int64 is treated as a
|
||||
// high-precision number
|
||||
return ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)()) ? 'L' : 'H';
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -21241,12 +21332,10 @@ class binary_writer
|
||||
{
|
||||
return 'm';
|
||||
}
|
||||
if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
// anything else is treated as a high-precision number
|
||||
return 'H'; // LCOV_EXCL_LINE
|
||||
// every value of an integer type of at most 64 bits fits into
|
||||
// an int64; only a wider type needs a range check
|
||||
return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer,
|
||||
std::integral_constant < bool, std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
@@ -21279,12 +21368,12 @@ class binary_writer
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
||||
if (use_bjdata)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
// anything else is treated as a high-precision number
|
||||
return 'H'; // LCOV_EXCL_LINE
|
||||
return 'H';
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
@@ -23006,8 +23095,7 @@ enum class error_handler_t
|
||||
{
|
||||
strict, ///< throw a type_error exception in case of invalid UTF-8
|
||||
replace, ///< replace invalid UTF-8 sequences with U+FFFD
|
||||
ignore, ///< ignore invalid UTF-8 sequences
|
||||
keep ///< keep invalid UTF-8 sequences; their bytes are copied unchanged
|
||||
ignore ///< ignore invalid UTF-8 sequences
|
||||
};
|
||||
|
||||
template<typename BasicJsonType>
|
||||
@@ -23978,47 +24066,6 @@ class serializer
|
||||
break;
|
||||
}
|
||||
|
||||
case error_handler_t::keep:
|
||||
{
|
||||
// drop whatever the incomplete sequence left in
|
||||
// the buffer (only copied if !EnsureAscii) and copy
|
||||
// the ill-formed bytes from the input instead
|
||||
bytes = bytes_after_last_accept;
|
||||
|
||||
if (undumped_chars > 0)
|
||||
{
|
||||
// the pending bytes of the incomplete sequence
|
||||
// are ill-formed; the current byte may be OK for
|
||||
// itself, so we would like to read it again
|
||||
for (std::size_t j = i - undumped_chars; j < i; ++j)
|
||||
{
|
||||
string_buffer[bytes++] = s[j];
|
||||
}
|
||||
--i;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the current byte cannot start any sequence
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
bytes_after_last_accept = bytes;
|
||||
undumped_chars = 0;
|
||||
|
||||
// continue processing the string
|
||||
state = UTF8_ACCEPT;
|
||||
break;
|
||||
}
|
||||
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -24064,15 +24111,6 @@ class serializer
|
||||
break;
|
||||
}
|
||||
|
||||
case error_handler_t::keep:
|
||||
{
|
||||
// write all accepted bytes
|
||||
put_buffer(string_buffer, bytes_after_last_accept);
|
||||
// copy the bytes of the incomplete sequence unchanged
|
||||
put_string(s, s.size() - undumped_chars, s.size());
|
||||
break;
|
||||
}
|
||||
|
||||
case error_handler_t::replace:
|
||||
{
|
||||
// write all accepted bytes
|
||||
@@ -26501,13 +26539,28 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
compare_keys(current.lhs_object_it->first, current.rhs_object_it->first,
|
||||
std::integral_constant<bool, Ordered> {});
|
||||
|
||||
if (key_result != compare_result::equal)
|
||||
{
|
||||
return key_result;
|
||||
}
|
||||
|
||||
left = &(current.lhs_object_it->second);
|
||||
right = &(current.rhs_object_it->second);
|
||||
|
||||
if (key_result != compare_result::equal)
|
||||
{
|
||||
// An object type without a fixed order of its entries -
|
||||
// std::unordered_map, say - may enumerate two equal
|
||||
// objects differently, and its operator== does not care.
|
||||
// Equality then finds the entry by its key; an ordering,
|
||||
// or an object type that compares its entries in
|
||||
// sequence (ordered_map), is decided by the key itself.
|
||||
const auto* rhs_object = current.rhs_value->m_data.m_value.object;
|
||||
const auto found = (!Ordered && !detail::is_ordered_map<object_t>::value)
|
||||
? rhs_object->find(current.lhs_object_it->first)
|
||||
: rhs_object->cend();
|
||||
if (found == rhs_object->cend())
|
||||
{
|
||||
return key_result;
|
||||
}
|
||||
right = &(found->second);
|
||||
}
|
||||
|
||||
++current.lhs_object_it;
|
||||
++current.rhs_object_it;
|
||||
}
|
||||
@@ -27166,17 +27219,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// value access //
|
||||
//////////////////
|
||||
|
||||
/// get a boolean (explicit)
|
||||
boolean_t get_impl(boolean_t* /*unused*/) const
|
||||
{
|
||||
if (JSON_HEDLEY_LIKELY(is_boolean()))
|
||||
{
|
||||
return m_data.m_value.boolean;
|
||||
}
|
||||
|
||||
JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this));
|
||||
}
|
||||
|
||||
/// get a pointer to the value (object)
|
||||
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <fstream> // ifstream, istreambuf_iterator, ios
|
||||
#include <vector> // vector
|
||||
|
||||
@@ -24,6 +25,23 @@ namespace utils
|
||||
template<typename T>
|
||||
inline void ignore_return_value(T&& /*unused*/) noexcept {}
|
||||
|
||||
// Advance i toward last (inclusive) by stride, always visiting last.
|
||||
// stride 7 is coprime to 256, so every low-byte residue is still hit.
|
||||
template<typename T>
|
||||
T next_integer_sample(T i, T last, T stride)
|
||||
{
|
||||
if (i >= last)
|
||||
{
|
||||
return static_cast<T>(last + 1);
|
||||
}
|
||||
if (stride > 0 && i > static_cast<T>(last - stride))
|
||||
{
|
||||
return last;
|
||||
}
|
||||
const T n = static_cast<T>(i + stride);
|
||||
return n < last ? n : last;
|
||||
}
|
||||
|
||||
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
||||
{
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
|
||||
@@ -37,6 +37,12 @@ struct bad_allocator : std::allocator<T>
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("get_allocator")
|
||||
{
|
||||
const auto alloc = nlohmann::json::get_allocator();
|
||||
CHECK(alloc == std::allocator<nlohmann::json>());
|
||||
}
|
||||
|
||||
TEST_CASE("bad_alloc")
|
||||
{
|
||||
SECTION("bad_alloc")
|
||||
|
||||
+120
-25
@@ -418,7 +418,7 @@ TEST_CASE("BJData")
|
||||
|
||||
SECTION("-32768..-129 (int16)")
|
||||
{
|
||||
for (int32_t i = -32768; i <= -129; ++i)
|
||||
for (int32_t i = -32768; i <= -129; i = utils::next_integer_sample(i, -129, 7))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -578,7 +578,7 @@ TEST_CASE("BJData")
|
||||
|
||||
SECTION("256..32767 (int16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 32767; ++i)
|
||||
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -911,7 +911,7 @@ TEST_CASE("BJData")
|
||||
|
||||
SECTION("256..32767 (int16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 32767; ++i)
|
||||
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -3763,6 +3763,49 @@ TEST_CASE("BJData")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_bjdata(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_bjdata(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_bjdata(input, true, false).is_discarded());
|
||||
CHECK(json::from_bjdata(input.begin(), input.end(), true, false).is_discarded());
|
||||
}
|
||||
|
||||
TEST_CASE("BJData SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::bjdata))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_bjdata(j)) == 20);
|
||||
CHECK(count_events(json::to_bjdata(j, true)) == 20);
|
||||
CHECK(count_events(json::to_bjdata(j, true, true)) == 20);
|
||||
|
||||
// an ND-array is announced as an annotated object: start_object, then
|
||||
// _ArrayType_, _ArraySize_ and _ArrayData_ with its elements
|
||||
const json ndarray = json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})");
|
||||
CHECK(count_events(json::to_bjdata(ndarray, true, true)) == 16);
|
||||
}
|
||||
|
||||
TEST_CASE("issue #5405 - array reserve for definite-length BJData arrays")
|
||||
{
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
@@ -4247,6 +4290,65 @@ TEST_CASE("all BJData first bytes")
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("BJData and UBJSON can be written to a string")
|
||||
{
|
||||
const std::vector<json> values =
|
||||
{
|
||||
{{"a", {1, 2.5, "x", nullptr}}, {"b", json::binary({1, 2})}},
|
||||
// an annotated ND-array, and objects that only look like one
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})"),
|
||||
json::parse(R"({"_ArrayType_": 1, "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})"),
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": 4, "_ArrayData_": [1, 2, 3, 4]})"),
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, -2], "_ArrayData_": [1, 2, 3, 4]})"),
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3]})"),
|
||||
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": 1})"),
|
||||
};
|
||||
|
||||
// compared byte by byte: building a std::string from the bytes would
|
||||
// convert them implicitly, which -fsanitize=integer reports for bytes of
|
||||
// 0x80 and above
|
||||
const auto same_bytes = [](const std::vector<std::uint8_t>& bytes, const std::string & text)
|
||||
{
|
||||
return bytes.size() == text.size() && std::equal(bytes.begin(), bytes.end(), text.begin(), [](std::uint8_t byte, char c)
|
||||
{
|
||||
return byte == static_cast<std::uint8_t>(c);
|
||||
});
|
||||
};
|
||||
|
||||
for (const auto& j : values)
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
for (const bool use_type :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
if (use_type && !use_size)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CAPTURE(use_size);
|
||||
CAPTURE(use_type);
|
||||
|
||||
const auto bjdata = json::to_bjdata(j, use_size, use_type);
|
||||
std::string bjdata_string;
|
||||
json::to_bjdata(j, bjdata_string, use_size, use_type);
|
||||
CHECK(same_bytes(bjdata, bjdata_string));
|
||||
|
||||
const auto ubjson = json::to_ubjson(j, use_size, use_type);
|
||||
std::string ubjson_string;
|
||||
json::to_ubjson(j, ubjson_string, use_size, use_type);
|
||||
CHECK(same_bytes(ubjson, ubjson_string));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData use_type requires use_size")
|
||||
{
|
||||
SECTION("non-empty object throws other_error.502")
|
||||
@@ -4265,6 +4367,17 @@ TEST_CASE("BJData use_type requires use_size")
|
||||
json::other_error&);
|
||||
}
|
||||
|
||||
SECTION("non-empty binary value throws other_error.502")
|
||||
{
|
||||
const json j = json::binary({1, 2, 3});
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j, false, true),
|
||||
"[json.exception.other_error.502] use_type requires use_size = true",
|
||||
json::other_error&);
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, false, true),
|
||||
"[json.exception.other_error.502] use_type requires use_size = true",
|
||||
json::other_error&);
|
||||
}
|
||||
|
||||
SECTION("scalars do not throw with use_type=true, use_count=false")
|
||||
{
|
||||
CHECK_NOTHROW(json::to_bjdata(42, false, true));
|
||||
@@ -4428,45 +4541,27 @@ TEST_CASE("BJData roundtrips" * doctest::skip())
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
auto packed = utils::read_binary_file(filename + ".bjdata");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse BJData file
|
||||
auto packed = utils::read_binary_file(filename + ".bjdata");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bjdata(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse BJData file
|
||||
std::ifstream f_bjdata(filename + ".bjdata", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bjdata(f_bjdata));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output to output adapters");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse BJData file
|
||||
auto packed = utils::read_binary_file(filename + ".bjdata");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
|
||||
std::vector<uint8_t> vec;
|
||||
|
||||
@@ -1244,6 +1244,44 @@ TEST_CASE("BSON nesting does not consume the call stack")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BSON input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_bson(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||
CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded());
|
||||
CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded());
|
||||
CHECK(json::from_bson({input.data(), input.size()}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
TEST_CASE("BSON SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::bson))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_bson(j)) == 20);
|
||||
}
|
||||
|
||||
TEST_CASE("BSON numerical data")
|
||||
{
|
||||
SECTION("number")
|
||||
|
||||
+183
-36
@@ -15,6 +15,7 @@ using nlohmann::json;
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "test_utils.hpp"
|
||||
@@ -290,7 +291,7 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("-65536..-257")
|
||||
{
|
||||
for (int32_t i = -65536; i <= -257; ++i)
|
||||
for (int32_t i = -65536; i <= -257; i = utils::next_integer_sample(i, -257, 7))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -478,7 +479,7 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("256..65535")
|
||||
{
|
||||
for (size_t i = 256; i <= 65535; ++i)
|
||||
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -613,7 +614,7 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("-32768..-129 (int 16)")
|
||||
{
|
||||
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
|
||||
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); i = utils::next_integer_sample(i, static_cast<int16_t>(-129), static_cast<int16_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -718,7 +719,7 @@ TEST_CASE("CBOR")
|
||||
|
||||
SECTION("256..65535 (two-byte uint16_t)")
|
||||
{
|
||||
for (size_t i = 256; i <= 65535; ++i)
|
||||
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -1829,10 +1830,51 @@ TEST_CASE("CBOR")
|
||||
SECTION("invalid string in map")
|
||||
{
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
|
||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("non-string key (see #2766 and #3381)")
|
||||
{
|
||||
// only text strings map to JSON object keys; any other key is
|
||||
// rejected with a message naming its type
|
||||
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
|
||||
{
|
||||
{{0xA1, 0x01, 0x01}, "an unsigned integer; last byte: 0x01"},
|
||||
{{0xA1, 0x20, 0x01}, "a negative integer; last byte: 0x20"},
|
||||
{{0xA1, 0x41, 0x61, 0x01}, "a byte string; last byte: 0x41"},
|
||||
{{0xA1, 0x80, 0x01}, "an array; last byte: 0x80"},
|
||||
{{0xA1, 0xA0, 0x01}, "a map; last byte: 0xA0"},
|
||||
{{0xA1, 0xC0, 0x61, 0x61, 0x01}, "a tag; last byte: 0xC0"},
|
||||
{{0xA1, 0xF4, 0x01}, "a boolean; last byte: 0xF4"},
|
||||
{{0xA1, 0xF5, 0x01}, "a boolean; last byte: 0xF5"},
|
||||
{{0xA1, 0xF6, 0x01}, "null; last byte: 0xF6"},
|
||||
{{0xA1, 0xF7, 0x01}, "undefined; last byte: 0xF7"},
|
||||
{{0xA1, 0xF9, 0x3C, 0x00, 0x01}, "a floating-point number; last byte: 0xF9"},
|
||||
{{0xA1, 0xFA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFA"},
|
||||
{{0xA1, 0xFB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFB"},
|
||||
{{0xA1, 0xE0, 0x01}, "a simple value; last byte: 0xE0"},
|
||||
{{0xA1, 0xF8, 0x20, 0x01}, "a simple value; last byte: 0xF8"},
|
||||
// indefinite-length map
|
||||
{{0xBF, 0x01, 0x01, 0xFF}, "an unsigned integer; last byte: 0x01"},
|
||||
};
|
||||
|
||||
for (const auto& c : cases)
|
||||
{
|
||||
CAPTURE(c.first)
|
||||
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found " + c.second;
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(c.first), expected.c_str(), json::parse_error&);
|
||||
CHECK(json::from_cbor(c.first, true, false).is_discarded());
|
||||
}
|
||||
|
||||
// a key of major type 3 with a reserved length is still reported as
|
||||
// a malformed string, and a missing key as the end of input
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x7C, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 in string (see #5529)")
|
||||
{
|
||||
// a two-character text string (major type 3) whose bytes are not
|
||||
@@ -2123,6 +2165,20 @@ TEST_CASE("CBOR nesting does not consume the call stack")
|
||||
CHECK(json::from_cbor(input, true, false, json::cbor_tag_handler_t::ignore).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("stored tags")
|
||||
{
|
||||
// a tag over something other than a byte string is read like for
|
||||
// ignore, so a chain of them must not recurse either (#5316)
|
||||
std::vector<uint8_t> input;
|
||||
for (std::size_t i = 0; i < 500000; ++i)
|
||||
{
|
||||
input.push_back(0xD8);
|
||||
input.push_back(0x18);
|
||||
}
|
||||
input.push_back(0x01);
|
||||
CHECK(json::from_cbor(input, true, true, json::cbor_tag_handler_t::store) == 1);
|
||||
}
|
||||
|
||||
SECTION("a well-formed deep value is read through the SAX interface")
|
||||
{
|
||||
std::vector<uint8_t> input(200000, 0x9F);
|
||||
@@ -2175,6 +2231,52 @@ TEST_CASE("CBOR nesting does not consume the call stack")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_cbor(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||
CHECK(json::from_cbor(input.begin(), input.end(), true, false).is_discarded());
|
||||
CHECK(json::from_cbor(input.data(), input.size(), true, false).is_discarded());
|
||||
CHECK(json::from_cbor({input.data(), input.size()}, true, false).is_discarded());
|
||||
|
||||
// a string that ends early, read through iterators that are not
|
||||
// contiguous and have to be copied from one element at a time
|
||||
const std::list<std::uint8_t> truncated_string = {0x63, 'a', 'b'};
|
||||
CHECK(json::from_cbor(truncated_string.begin(), truncated_string.end(), true, false).is_discarded());
|
||||
const std::list<std::uint8_t> complete_string = {0x63, 'a', 'b', 'c'};
|
||||
CHECK(json::from_cbor(complete_string.begin(), complete_string.end()) == "abc");
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::cbor))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_cbor(j)) == 20);
|
||||
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
||||
{
|
||||
// Reading an indefinite-length string or byte array used to call itself
|
||||
@@ -2223,7 +2325,7 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
||||
SECTION("a break marker outside an indefinite-length string is not a string")
|
||||
{
|
||||
// 0xFF only closes a string that was opened; on its own it is not one
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2482,60 +2584,34 @@ TEST_CASE("CBOR roundtrips" * doctest::skip())
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
const auto packed = utils::read_binary_file(filename + ".cbor");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse CBOR file
|
||||
const auto packed = utils::read_binary_file(filename + ".cbor");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_cbor(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse CBOR file
|
||||
std::ifstream f_cbor(filename + ".cbor", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_cbor(f_cbor));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": uint8_t* and size");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse CBOR file
|
||||
const auto packed = utils::read_binary_file(filename + ".cbor");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_cbor({packed.data(), packed.size()}));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output to output adapters");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse CBOR file
|
||||
const auto packed = utils::read_binary_file(filename + ".cbor");
|
||||
|
||||
if (exclude_packed.count(filename) == 0u)
|
||||
{
|
||||
{
|
||||
@@ -3033,6 +3109,77 @@ TEST_CASE("Tagged values")
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
|
||||
}
|
||||
|
||||
SECTION("issue #5316 - cbor_tag_handler_t::store on non-binary tagged items")
|
||||
{
|
||||
// 55799({"a": 1}) -- CBOR self-describe magic followed by a map
|
||||
const std::vector<std::uint8_t> v_map{0xD9, 0xD9, 0xF7, 0xA1, 0x61, 0x61, 0x01};
|
||||
CHECK(json::from_cbor(v_map, true, true, json::cbor_tag_handler_t::ignore) == json({{"a", 1}}));
|
||||
CHECK(json::from_cbor(v_map, true, true, json::cbor_tag_handler_t::store) == json({{"a", 1}}));
|
||||
|
||||
// Tag 24 over unsigned integer 5
|
||||
const std::vector<std::uint8_t> v_int{0xD8, 0x18, 0x05};
|
||||
CHECK(json::from_cbor(v_int, true, true, json::cbor_tag_handler_t::ignore) == 5);
|
||||
CHECK(json::from_cbor(v_int, true, true, json::cbor_tag_handler_t::store) == 5);
|
||||
|
||||
// Tag 24 over text string "foo"
|
||||
const std::vector<std::uint8_t> v_str{0xD8, 0x18, 0x63, 'f', 'o', 'o'};
|
||||
CHECK(json::from_cbor(v_str, true, true, json::cbor_tag_handler_t::ignore) == "foo");
|
||||
CHECK(json::from_cbor(v_str, true, true, json::cbor_tag_handler_t::store) == "foo");
|
||||
|
||||
// Tag 24 over array [1, 2]
|
||||
const std::vector<std::uint8_t> v_arr{0xD8, 0x18, 0x82, 0x01, 0x02};
|
||||
CHECK(json::from_cbor(v_arr, true, true, json::cbor_tag_handler_t::ignore) == json({1, 2}));
|
||||
CHECK(json::from_cbor(v_arr, true, true, json::cbor_tag_handler_t::store) == json({1, 2}));
|
||||
|
||||
// Tag 24 over boolean true
|
||||
const std::vector<std::uint8_t> v_bool{0xD8, 0x18, 0xF5};
|
||||
CHECK(json::from_cbor(v_bool, true, true, json::cbor_tag_handler_t::ignore) == true);
|
||||
CHECK(json::from_cbor(v_bool, true, true, json::cbor_tag_handler_t::store) == true);
|
||||
|
||||
// Tag 24 over null
|
||||
const std::vector<std::uint8_t> v_null{0xD8, 0x18, 0xF6};
|
||||
CHECK(json::from_cbor(v_null, true, true, json::cbor_tag_handler_t::ignore) == nullptr);
|
||||
CHECK(json::from_cbor(v_null, true, true, json::cbor_tag_handler_t::store) == nullptr);
|
||||
|
||||
// Nested tags: tag 55799 over tag 24 over integer 42
|
||||
const std::vector<std::uint8_t> v_nested{0xD9, 0xD9, 0xF7, 0xD8, 0x18, 0x18, 0x2A};
|
||||
CHECK(json::from_cbor(v_nested, true, true, json::cbor_tag_handler_t::ignore) == 42);
|
||||
CHECK(json::from_cbor(v_nested, true, true, json::cbor_tag_handler_t::store) == 42);
|
||||
|
||||
// Tag 24 over byte string continues to store subtype as before
|
||||
const std::vector<std::uint8_t> v_bin{0xD8, 0x18, 0x42, 0xCA, 0xFE};
|
||||
auto j_bin_store = json::from_cbor(v_bin, true, true, json::cbor_tag_handler_t::store);
|
||||
CHECK(j_bin_store.is_binary());
|
||||
CHECK(j_bin_store.get_binary().has_subtype());
|
||||
CHECK(j_bin_store.get_binary().subtype() == 24);
|
||||
CHECK(j_bin_store.get_binary() == json::binary({0xCA, 0xFE}, 24).get_binary());
|
||||
|
||||
// Tagged values inside a container under store: [24(1), 25(h'0001')]
|
||||
const std::vector<std::uint8_t> v_container{0x82, 0xD8, 0x18, 0x01, 0xD8, 0x19, 0x42, 0x00, 0x01};
|
||||
auto j_container_store = json::from_cbor(v_container, true, true, json::cbor_tag_handler_t::store);
|
||||
CHECK(j_container_store.is_array());
|
||||
CHECK(j_container_store.size() == 2);
|
||||
CHECK(j_container_store[0] == 1);
|
||||
CHECK(j_container_store[1].is_binary());
|
||||
CHECK(j_container_store[1].get_binary().has_subtype());
|
||||
CHECK(j_container_store[1].get_binary().subtype() == 25);
|
||||
CHECK(j_container_store[1].get_binary() == json::binary({0x00, 0x01}, 25).get_binary());
|
||||
|
||||
// Tagged values as object values under store: {"a": 55799(1), "b": 24(h'01')}
|
||||
const std::vector<std::uint8_t> v_object{0xA2, 0x61, 'a', 0xD9, 0xD9, 0xF7, 0x01, 0x61, 'b', 0xD8, 0x18, 0x41, 0x01};
|
||||
CHECK(json::from_cbor(v_object, true, true, json::cbor_tag_handler_t::store) == json({{"a", 1}, {"b", json::binary({0x01}, 24)}}));
|
||||
|
||||
// two tags in a row before a byte string: the inner tag is stored
|
||||
// (this uses item_read and then the byte-string path)
|
||||
const std::vector<std::uint8_t> v_nested_byte_string{0xD8, 0x18, 0xD8, 0x19, 0x42, 0x00, 0x01};
|
||||
CHECK(json::from_cbor(v_nested_byte_string, true, true, json::cbor_tag_handler_t::store) == json::binary({0x00, 0x01}, 25));
|
||||
|
||||
// errors after a stored tag are now the same as with ignore
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0xD8, 0x18}, true, true, json::cbor_tag_handler_t::store), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0xD8, 0x18, 0x1C}, true, true, json::cbor_tag_handler_t::store), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing CBOR value: invalid byte: 0x1C", json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("negative integer overflow")
|
||||
|
||||
@@ -43,6 +43,13 @@ TEST_CASE("const_iterator class")
|
||||
json::const_iterator const it(&j);
|
||||
json::const_iterator it2(&j);
|
||||
it2 = it;
|
||||
|
||||
// assigning an iterator to itself leaves it unchanged
|
||||
json const a = {1, 2, 3};
|
||||
json::const_iterator it3 = a.cbegin() + 1;
|
||||
const json::const_iterator& same = it3;
|
||||
it3 = same;
|
||||
CHECK(*it3 == 2);
|
||||
}
|
||||
|
||||
SECTION("copy constructor from non-const iterator")
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cfloat> // FLT_EVAL_METHOD
|
||||
#include <cstdlib> // strtod
|
||||
#include <sstream> // stringstream
|
||||
#include <string> // string
|
||||
@@ -657,3 +658,45 @@ TEST_CASE("lexer string fast path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("parse_float_fast declines what it cannot convert exactly")
|
||||
{
|
||||
// The lexer only hands well-formed numbers to parse_float_fast, so the
|
||||
// malformed ones below can only be passed to it directly. Declining is
|
||||
// always safe: the caller then falls back to a slower, exact conversion.
|
||||
const auto fast = [](const std::string & s, double & out)
|
||||
{
|
||||
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), '.', out);
|
||||
};
|
||||
double out = 0;
|
||||
|
||||
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
||||
// without true double precision, the fast path declines everything
|
||||
CHECK_FALSE(fast("1.5", out));
|
||||
#else
|
||||
CHECK(fast("1.5", out));
|
||||
CHECK(out == 1.5);
|
||||
CHECK(fast("+2.5e1", out));
|
||||
CHECK(out == 25.0);
|
||||
CHECK(fast("-25E-1", out));
|
||||
CHECK(out == -2.5);
|
||||
CHECK(fast("1e", out));
|
||||
CHECK(out == 1.0);
|
||||
#endif
|
||||
|
||||
// not a number
|
||||
CHECK_FALSE(fast("", out));
|
||||
CHECK_FALSE(fast("-", out));
|
||||
CHECK_FALSE(fast(".", out));
|
||||
CHECK_FALSE(fast("1.2.3", out));
|
||||
CHECK_FALSE(fast("1x", out));
|
||||
CHECK_FALSE(fast("1e+", out));
|
||||
CHECK_FALSE(fast("1e1x", out));
|
||||
|
||||
// numbers that are not represented exactly on the fast path
|
||||
CHECK_FALSE(fast("12345678901234567890", out));
|
||||
CHECK_FALSE(fast("1e10000", out));
|
||||
CHECK_FALSE(fast("9007199254740993", out));
|
||||
CHECK_FALSE(fast("1e23", out));
|
||||
CHECK_FALSE(fast("1e-23", out));
|
||||
}
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define JSON_TESTS_PRIVATE
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -359,6 +365,15 @@ TEST_CASE("lexicographical comparison operators")
|
||||
CHECK(json(1) < json(1.5));
|
||||
CHECK(json(1.5) < json(2));
|
||||
CHECK(json(2) > json(1.5));
|
||||
CHECK(json(-1) > json(-1.5));
|
||||
CHECK(json(-1.5) < json(-1));
|
||||
CHECK(json(-2) < json(-1.5));
|
||||
|
||||
// a float below the range of the integer type
|
||||
CHECK(json(0) > json(-1e30));
|
||||
CHECK(json(-1e30) < json(0));
|
||||
CHECK(json(0u) > json(-0.5));
|
||||
CHECK(json(-0.5) < json(0u));
|
||||
|
||||
// a NaN operand stays unordered against either integer kind
|
||||
CHECK_FALSE(json(1) == json(nan));
|
||||
@@ -735,3 +750,205 @@ TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P24
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// orders keys ascending or descending, as chosen when a map is created
|
||||
template<class Key>
|
||||
class directed_less
|
||||
{
|
||||
public:
|
||||
directed_less() = default;
|
||||
|
||||
explicit directed_less(const bool descending) noexcept
|
||||
: m_descending(descending)
|
||||
{}
|
||||
|
||||
bool operator()(const Key& lhs, const Key& rhs) const
|
||||
{
|
||||
return m_descending ? rhs < lhs : lhs < rhs;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_descending = false;
|
||||
};
|
||||
|
||||
// An object type that, like std::unordered_map, enumerates its entries in no
|
||||
// fixed order - ascending or descending by key, depending on how the map was
|
||||
// created - and whose operator== does not depend on that order.
|
||||
// std::unordered_map itself cannot be used here: the standard does not
|
||||
// require it to accept an incomplete mapped type such as basic_json, and
|
||||
// libstdc++ 6 to 9 as well as the EDG front ends of icpc and nvc++ reject
|
||||
// basic_json<std::unordered_map>. std::map, the default object type, works
|
||||
// with all supported compilers.
|
||||
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||
struct unordered_object_t : std::map<Key, Value, directed_less<Key>, Allocator>
|
||||
{
|
||||
using base_type = std::map<Key, Value, directed_less<Key>, Allocator>;
|
||||
using base_type::base_type;
|
||||
|
||||
friend bool operator==(const unordered_object_t& lhs, const unordered_object_t& rhs)
|
||||
{
|
||||
return lhs.size() == rhs.size() && std::all_of(lhs.begin(), lhs.end(), [&rhs](const std::pair<const Key, Value>& entry)
|
||||
{
|
||||
const auto it = rhs.find(entry.first);
|
||||
return it != rhs.end() && it->second == entry.second;
|
||||
});
|
||||
}
|
||||
|
||||
friend bool operator!=(const unordered_object_t& lhs, const unordered_object_t& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
using unordered_json = nlohmann::basic_json<unordered_object_t>;
|
||||
|
||||
// the entries "0" to "9", enumerated in ascending or in descending order
|
||||
unordered_json make_unordered_object(const bool descending)
|
||||
{
|
||||
unordered_json j = unordered_json::object_t(directed_less<std::string>(descending));
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
j[std::to_string(i)] = i;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
template<typename Json>
|
||||
Json nest(Json j, const std::size_t depth)
|
||||
{
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
Json outer = Json::object();
|
||||
outer["x"] = std::move(j);
|
||||
j = std::move(outer);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("equality of objects whose entries have no fixed order")
|
||||
{
|
||||
// Values nested deeper than a bound are compared without the call stack,
|
||||
// entry by entry. That must agree with the object type's own operator==,
|
||||
// which for unordered_object_t (as for std::unordered_map) does not
|
||||
// depend on the order of the entries, and for ordered_map does.
|
||||
REQUIRE(make_unordered_object(true).begin().key() == "9");
|
||||
REQUIRE(make_unordered_object(false).begin().key() == "0");
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
|
||||
const unordered_json descending = nest(make_unordered_object(true), depth);
|
||||
const unordered_json ascending = nest(make_unordered_object(false), depth);
|
||||
CHECK(descending == ascending);
|
||||
CHECK_FALSE(descending != ascending);
|
||||
|
||||
// a copy is equal to its original
|
||||
const unordered_json copy = descending; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(copy == descending);
|
||||
|
||||
// a different value, a different key, or another entry still count
|
||||
unordered_json other_value = make_unordered_object(true);
|
||||
other_value["5"] = 42;
|
||||
CHECK_FALSE(nest(other_value, depth) == ascending);
|
||||
|
||||
unordered_json other_key = make_unordered_object(true);
|
||||
other_key.erase("5");
|
||||
other_key["50"] = 5;
|
||||
CHECK_FALSE(nest(other_key, depth) == ascending);
|
||||
|
||||
unordered_json more_entries = make_unordered_object(true);
|
||||
more_entries["10"] = 10;
|
||||
CHECK_FALSE(nest(more_entries, depth) == ascending);
|
||||
CHECK_FALSE(ascending == nest(more_entries, depth));
|
||||
|
||||
// ordered_json compares its entries in sequence
|
||||
const nlohmann::ordered_json ab = nest(nlohmann::ordered_json({{"a", 1}, {"b", 2}}), depth);
|
||||
const nlohmann::ordered_json ba = nest(nlohmann::ordered_json({{"b", 2}, {"a", 1}}), depth);
|
||||
CHECK_FALSE(ab == ba);
|
||||
CHECK(ab != ba);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("containers are compared element by element")
|
||||
{
|
||||
// Containers nested deeper than a bound are compared without the call
|
||||
// stack, by code of their own; every relation is checked both at the top
|
||||
// level and below that bound.
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
|
||||
// objects with different keys
|
||||
{
|
||||
const json a = deep({{"a", 1}}, depth);
|
||||
const json b = deep({{"b", 1}}, depth);
|
||||
CHECK_FALSE(a == b);
|
||||
CHECK(a != b);
|
||||
CHECK(a < b);
|
||||
CHECK(b > a);
|
||||
CHECK_FALSE(b < a);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
// a container that is a prefix of the other one
|
||||
{
|
||||
// the one that runs out of elements first is the smaller one
|
||||
const json shorter = deep({1}, depth);
|
||||
const json longer = deep({1, 2}, depth);
|
||||
CHECK(shorter < longer);
|
||||
CHECK(longer > shorter);
|
||||
CHECK_FALSE(longer < shorter);
|
||||
CHECK_FALSE(shorter == longer);
|
||||
|
||||
const json smaller_object = deep({{"a", 1}}, depth);
|
||||
const json larger_object = deep({{"a", 1}, {"b", 2}}, depth);
|
||||
CHECK(smaller_object < larger_object);
|
||||
CHECK(larger_object > smaller_object);
|
||||
CHECK_FALSE(smaller_object == larger_object);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
// elements that cannot be ordered
|
||||
{
|
||||
const double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
const json lhs = deep({nan, 1}, depth);
|
||||
const json rhs = deep({nan, 2}, depth);
|
||||
|
||||
CHECK_FALSE(lhs == lhs);
|
||||
CHECK_FALSE(rhs < lhs);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
// operator<=> stops there, as std::lexicographical_compare_three_way
|
||||
// does, and operator< is derived from it
|
||||
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK_FALSE(lhs < rhs);
|
||||
#else
|
||||
// operator< skips a pair of elements that cannot be ordered, as
|
||||
// std::lexicographical_compare does, and the next pair decides
|
||||
CHECK(lhs < rhs);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,16 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
CHECK(char_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||
}
|
||||
|
||||
SECTION("a value is converted to the binary type if it is binary or an array")
|
||||
{
|
||||
const std::vector<char> chars{'\0', '\x01', '\x7F'};
|
||||
CHECK(char_binary_json::binary(chars).get<std::vector<char>>() == chars);
|
||||
CHECK(char_binary_json({0, 1, 127}).get<std::vector<char>>() == chars);
|
||||
CHECK_THROWS_WITH_AS(char_binary_json(1).get<std::vector<char>>(),
|
||||
"[json.exception.type_error.302] type must be binary or array, but is number",
|
||||
char_binary_json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("the default binary type is unchanged")
|
||||
{
|
||||
CHECK(nlohmann::json::binary({0, 1, 255}, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
|
||||
@@ -156,3 +156,38 @@ TEST_CASE("Better diagnostics with positions")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("values read from a binary format have no positions")
|
||||
{
|
||||
// only the JSON lexer knows where a value started and ended
|
||||
const json source = {{"a", {1, "x", json::binary({1})}}, {"b", {{"c", true}}}, {"d", nullptr}, {"e", 1.5}};
|
||||
const std::vector<std::uint8_t> cbor = json::to_cbor(source);
|
||||
|
||||
const auto check_no_positions = [](const json & j)
|
||||
{
|
||||
CHECK(j.start_pos() == std::string::npos);
|
||||
CHECK(j.end_pos() == std::string::npos);
|
||||
CHECK(j.at("a").start_pos() == std::string::npos);
|
||||
CHECK(j.at("a").at(1).end_pos() == std::string::npos);
|
||||
CHECK(j.at("b").at("c").start_pos() == std::string::npos);
|
||||
};
|
||||
|
||||
SECTION("DOM parser")
|
||||
{
|
||||
const json j = json::from_cbor(cbor);
|
||||
CHECK(j == source);
|
||||
check_no_positions(j);
|
||||
}
|
||||
|
||||
SECTION("DOM parser with a callback")
|
||||
{
|
||||
json j;
|
||||
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(cbor))> sdp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
CHECK(json::sax_parse(cbor, &sdp, json::input_format_t::cbor));
|
||||
CHECK(j == source);
|
||||
check_no_positions(j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1517,6 +1517,16 @@ TEST_CASE_TEMPLATE("element access 2 (throwing tests)", Json, nlohmann::json, nl
|
||||
CHECK(j.value("/not/existing"_json_pointer, Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
|
||||
CHECK(j.value("/not/existing"_json_pointer, Json({10, 100})) == Json({10, 100}));
|
||||
|
||||
// an array index that is out of range, too large to be
|
||||
// represented, or "-", and a token below a scalar
|
||||
CHECK(j.value("/array/3"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/array/-"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/array/99999999999999999999999999"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/integer/0"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/string/x"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/null/x"_json_pointer, 2) == 2);
|
||||
CHECK(j.value("/array/0"_json_pointer, 2) == 1);
|
||||
|
||||
CHECK(j_const.value("/not/existing"_json_pointer, 2) == 2);
|
||||
CHECK(j_const.value("/not/existing"_json_pointer, 2u) == 2u);
|
||||
CHECK(j_const.value("/not/existing"_json_pointer, false) == false);
|
||||
|
||||
@@ -1751,3 +1751,98 @@ TEST_CASE("JSON patch - diff emits array removals in descending index order")
|
||||
CHECK(source.patch(patch) == target);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - every operation on ordered_json")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
const ordered_json doc = {{"foo", "bar"}, {"arr", {1, 2, 3}}, {"obj", {{"a", 1}}}};
|
||||
|
||||
SECTION("successful operations")
|
||||
{
|
||||
const ordered_json patch = ordered_json::parse(R"([
|
||||
{"op": "add", "path": "/obj/b", "value": 2},
|
||||
{"op": "add", "path": "/arr/1", "value": 9},
|
||||
{"op": "add", "path": "/arr/-", "value": 4},
|
||||
{"op": "remove", "path": "/arr/0"},
|
||||
{"op": "remove", "path": "/obj/a"},
|
||||
{"op": "replace", "path": "/foo", "value": "baz"},
|
||||
{"op": "move", "from": "/foo", "path": "/moved"},
|
||||
{"op": "copy", "from": "/obj", "path": "/copied"},
|
||||
{"op": "test", "path": "/copied/b", "value": 2}
|
||||
])");
|
||||
|
||||
const ordered_json expected = ordered_json::parse(R"({
|
||||
"arr": [9, 2, 3, 4], "obj": {"b": 2}, "moved": "baz", "copied": {"b": 2}
|
||||
})");
|
||||
|
||||
CHECK(doc.patch(patch) == expected);
|
||||
|
||||
// adding to the root replaces the document
|
||||
CHECK(doc.patch(ordered_json::parse(R"([{"op": "add", "path": "", "value": [1]}])")) == ordered_json({1}));
|
||||
}
|
||||
|
||||
SECTION("failing operations")
|
||||
{
|
||||
ordered_json _;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/arr/4", "value": 1}])")),
|
||||
"[json.exception.out_of_range.401] (/arr) array index 4 is out of range", ordered_json::out_of_range&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/arr/4", "value": 1}])")),
|
||||
"[json.exception.out_of_range.401] array index 4 is out of range", ordered_json::out_of_range&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/nope/x", "value": 1}])")),
|
||||
"[json.exception.out_of_range.403] key 'nope' not found", ordered_json::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/obj/nope"}])")),
|
||||
"[json.exception.out_of_range.403] key 'nope' not found", ordered_json::out_of_range&);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/arr/3"}])")),
|
||||
"[json.exception.out_of_range.401] (/arr) array index 3 is out of range", ordered_json::out_of_range&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/arr/3"}])")),
|
||||
"[json.exception.out_of_range.401] array index 3 is out of range", ordered_json::out_of_range&);
|
||||
#endif
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
|
||||
"[json.exception.other_error.501] (/0) unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
|
||||
#elif JSON_DIAGNOSTIC_POSITIONS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
|
||||
"[json.exception.other_error.501] (bytes 1-47) unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
|
||||
"[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
|
||||
#endif
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
|
||||
"[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'value'", ordered_json::parse_error&);
|
||||
#elif JSON_DIAGNOSTIC_POSITIONS
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
|
||||
"[json.exception.parse_error.105] parse error: (bytes 1-30) operation 'add' must have member 'value'", ordered_json::parse_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
|
||||
"[json.exception.parse_error.105] parse error: operation 'add' must have member 'value'", ordered_json::parse_error&);
|
||||
#endif
|
||||
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "move", "from": "/obj", "path": "/obj/a/b"}])")),
|
||||
"[json.exception.out_of_range.414] cannot move value: 'from' path '/obj' is a proper prefix of 'path' '/obj/a/b'", ordered_json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("diff reproduces the target")
|
||||
{
|
||||
const ordered_json source = {{"a", 1}, {"b", 2}, {"c", {{"x", 1}}}, {"l", {1, 2, 3}}};
|
||||
const std::vector<ordered_json> targets =
|
||||
{
|
||||
// a key removed, a key added, a nested change, a shorter array
|
||||
{{"a", 1}, {"c", {{"x", 2}}}, {"l", {1}}, {"d", 4}},
|
||||
// the same keys in another order
|
||||
{{"c", {{"x", 1}}}, {"a", 1}, {"b", 2}, {"l", {1, 2, 3}}},
|
||||
// new keys ahead of the common ones
|
||||
{{"new", true}, {"a", 1}, {"b", 3}, {"c", {{"x", 1}}}, {"l", {1, 2, 3}}},
|
||||
};
|
||||
for (const auto& target : targets)
|
||||
{
|
||||
CAPTURE(target.dump());
|
||||
CHECK(source.patch(ordered_json::diff(source, target)) == target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,3 +872,16 @@ TEST_CASE("JSON pointers")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
|
||||
{
|
||||
// the parser of a JSON pointer rejects such reference tokens before it
|
||||
// unescapes them, so this is only reachable by calling unescape directly
|
||||
std::string s = "a~2b~";
|
||||
nlohmann::detail::unescape(s);
|
||||
CHECK(s == "a~2b~");
|
||||
|
||||
s = "~0~1~";
|
||||
nlohmann::detail::unescape(s);
|
||||
CHECK(s == "~/~");
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ TEST_CASE("tests on very large JSONs")
|
||||
{
|
||||
SECTION("issue #1419 - Segmentation fault (stack overflow) due to unbounded recursion")
|
||||
{
|
||||
const auto depth = 5000000;
|
||||
const auto depth = 500000;
|
||||
|
||||
std::string s(static_cast<std::size_t>(2 * depth), '[');
|
||||
std::fill(s.begin() + depth, s.end(), ']');
|
||||
|
||||
@@ -158,6 +158,17 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)")
|
||||
json::sax_parse("12.34", &sax);
|
||||
CHECK(sax.float_string_copy == "12.34");
|
||||
}
|
||||
|
||||
SECTION("serializing a long double")
|
||||
{
|
||||
// a floating-point type that is not a float or a double is written
|
||||
// with snprintf, whose locale-specific decimal point and thousands
|
||||
// separator are undone afterwards
|
||||
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
|
||||
CHECK(long_double_json(12345.5L).dump() == "12345.5");
|
||||
CHECK(long_double_json(1.0L).dump() == "1.0");
|
||||
CHECK(long_double_json(-0.25L).dump() == "-0.25");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -345,3 +345,32 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
|
||||
CHECK(p->at("x") == 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON Merge Patch and update on ordered_json")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
SECTION("merge_patch")
|
||||
{
|
||||
ordered_json target = ordered_json::parse(R"({"a": {"b": 1, "c": 2}, "d": 3, "e": [1]})");
|
||||
target.merge_patch(ordered_json::parse(R"({"a": {"b": null, "f": 4}, "d": {"x": {"y": null}}, "e": null, "g": {"h": 5}})"));
|
||||
CHECK(target == ordered_json::parse(R"({"a": {"c": 2, "f": 4}, "d": {"x": {}}, "g": {"h": 5}})"));
|
||||
|
||||
// a patch that is not an object replaces the target
|
||||
target.merge_patch(ordered_json({1, 2}));
|
||||
CHECK(target == ordered_json({1, 2}));
|
||||
// an object patch turns a target that is not an object into one
|
||||
target.merge_patch(ordered_json::parse(R"({"k": {"l": null}})"));
|
||||
CHECK(target == ordered_json::parse(R"({"k": {}})"));
|
||||
}
|
||||
|
||||
SECTION("update with merge_objects")
|
||||
{
|
||||
ordered_json target = ordered_json::parse(R"({"a": {"b": 1, "c": {"d": 2}}, "e": 3})");
|
||||
target.update(ordered_json::parse(R"({"a": {"c": {"x": 1}, "f": 4}, "e": {"y": 5}, "g": 6})"), true);
|
||||
CHECK(target == ordered_json::parse(R"({"a": {"b": 1, "c": {"d": 2, "x": 1}, "f": 4}, "e": {"y": 5}, "g": 6})"));
|
||||
|
||||
target.update(ordered_json::parse(R"({"a": 1})"), false);
|
||||
CHECK(target == ordered_json::parse(R"({"a": 1, "e": {"y": 5}, "g": 6})"));
|
||||
}
|
||||
}
|
||||
|
||||
+352
-34
@@ -14,6 +14,7 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cstdint> // SIZE_MAX, UINT32_MAX
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
@@ -255,7 +256,7 @@ TEST_CASE("MessagePack")
|
||||
|
||||
SECTION("256..65535 (int 16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 65535; ++i)
|
||||
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -440,7 +441,7 @@ TEST_CASE("MessagePack")
|
||||
|
||||
SECTION("-32768..-129 (int 16)")
|
||||
{
|
||||
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
|
||||
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); i = utils::next_integer_sample(i, static_cast<int16_t>(-129), static_cast<int16_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -646,7 +647,7 @@ TEST_CASE("MessagePack")
|
||||
|
||||
SECTION("256..65535 (uint 16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 65535; ++i)
|
||||
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -1550,10 +1551,69 @@ TEST_CASE("MessagePack")
|
||||
SECTION("invalid string in map")
|
||||
{
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found an integer; last byte: 0xFF", json::parse_error&);
|
||||
CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("non-string key (see #3381)")
|
||||
{
|
||||
// only strings map to JSON object keys; any other key is rejected
|
||||
// with a message naming its type
|
||||
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
|
||||
{
|
||||
{{0x81, 0xC0, 0x01}, "nil; last byte: 0xC0"},
|
||||
{{0x81, 0xC2, 0x01}, "a boolean; last byte: 0xC2"},
|
||||
{{0x81, 0xC3, 0x01}, "a boolean; last byte: 0xC3"},
|
||||
{{0x81, 0xCA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a float; last byte: 0xCA"},
|
||||
{{0x81, 0xCB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a float; last byte: 0xCB"},
|
||||
{{0x81, 0xC4, 0x00, 0x01}, "a bin; last byte: 0xC4"},
|
||||
{{0x81, 0xC5, 0x00, 0x00, 0x01}, "a bin; last byte: 0xC5"},
|
||||
{{0x81, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x01}, "a bin; last byte: 0xC6"},
|
||||
{{0x81, 0xC7, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC7"},
|
||||
{{0x81, 0xC8, 0x00, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC8"},
|
||||
{{0x81, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC9"},
|
||||
{{0x81, 0xD4, 0x01, 0x00, 0x01}, "an ext; last byte: 0xD4"},
|
||||
{{0x81, 0xD5, 0x01, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD5"},
|
||||
{{0x81, 0xD6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD6"},
|
||||
{{0x81, 0xD7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD7"},
|
||||
{{0x81, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD8"},
|
||||
{{0x81, 0xCC, 0x01, 0x01}, "an integer; last byte: 0xCC"},
|
||||
{{0x81, 0xCD, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCD"},
|
||||
{{0x81, 0xCE, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCE"},
|
||||
{{0x81, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCF"},
|
||||
{{0x81, 0xD0, 0x01, 0x01}, "an integer; last byte: 0xD0"},
|
||||
{{0x81, 0xD1, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD1"},
|
||||
{{0x81, 0xD2, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD2"},
|
||||
{{0x81, 0xD3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD3"},
|
||||
{{0x81, 0x00, 0x01}, "an integer; last byte: 0x00"},
|
||||
{{0x81, 0x7F, 0x01}, "an integer; last byte: 0x7F"},
|
||||
{{0x81, 0xE0, 0x01}, "an integer; last byte: 0xE0"},
|
||||
{{0x81, 0x80, 0x01}, "a map; last byte: 0x80"},
|
||||
{{0x81, 0x8F, 0x01}, "a map; last byte: 0x8F"},
|
||||
{{0x81, 0xDE, 0x00, 0x00, 0x01}, "a map; last byte: 0xDE"},
|
||||
{{0x81, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x01}, "a map; last byte: 0xDF"},
|
||||
{{0x81, 0x90, 0x01}, "an array; last byte: 0x90"},
|
||||
{{0x81, 0x9F, 0x01}, "an array; last byte: 0x9F"},
|
||||
{{0x81, 0xDC, 0x00, 0x00, 0x01}, "an array; last byte: 0xDC"},
|
||||
{{0x81, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x01}, "an array; last byte: 0xDD"},
|
||||
};
|
||||
|
||||
for (const auto& c : cases)
|
||||
{
|
||||
CAPTURE(c.first)
|
||||
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found " + c.second;
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(c.first), expected.c_str(), json::parse_error&);
|
||||
CHECK(json::from_msgpack(c.first, true, false).is_discarded());
|
||||
}
|
||||
|
||||
json _;
|
||||
// the unused byte 0xC1 is still reported as a malformed string
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xC1, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xC1", json::parse_error&);
|
||||
// a missing key is still reported as the end of input
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 in string (see #5529)")
|
||||
{
|
||||
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
|
||||
@@ -1780,6 +1840,44 @@ TEST_CASE("MessagePack nesting does not consume the call stack")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("MessagePack input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_msgpack(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_msgpack(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_msgpack(input, true, false).is_discarded());
|
||||
CHECK(json::from_msgpack(input.begin(), input.end(), true, false).is_discarded());
|
||||
CHECK(json::from_msgpack(input.data(), input.size(), true, false).is_discarded());
|
||||
CHECK(json::from_msgpack({input.data(), input.size()}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
TEST_CASE("MessagePack SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::msgpack))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_msgpack(j)) == 20);
|
||||
}
|
||||
|
||||
TEST_CASE("single MessagePack roundtrip")
|
||||
{
|
||||
SECTION("sample.json")
|
||||
@@ -2004,60 +2102,34 @@ TEST_CASE("MessagePack roundtrips" * doctest::skip())
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
auto packed = utils::read_binary_file(filename + ".msgpack");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse MessagePack file
|
||||
auto packed = utils::read_binary_file(filename + ".msgpack");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_msgpack(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse MessagePack file
|
||||
std::ifstream f_msgpack(filename + ".msgpack", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_msgpack(f_msgpack));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": uint8_t* and size");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse MessagePack file
|
||||
auto packed = utils::read_binary_file(filename + ".msgpack");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_msgpack({packed.data(), packed.size()}));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output to output adapters");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse MessagePack file
|
||||
auto packed = utils::read_binary_file(filename + ".msgpack");
|
||||
|
||||
if (exclude_packed.count(filename) == 0u)
|
||||
{
|
||||
{
|
||||
@@ -2150,3 +2222,249 @@ TEST_CASE("MessagePack with std::byte")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// the fake sizes below do not fit into a 32-bit std::size_t
|
||||
#if SIZE_MAX > UINT32_MAX
|
||||
template<typename T, typename A = std::allocator<T>>
|
||||
struct huge_array : std::vector<T, A>
|
||||
{
|
||||
using base = std::vector<T, A>;
|
||||
using base::base;
|
||||
|
||||
bool fake_size = false;
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
if (fake_size)
|
||||
{
|
||||
return (std::numeric_limits<std::uint32_t>::max)() + 1ULL;
|
||||
}
|
||||
|
||||
return base::size();
|
||||
}
|
||||
};
|
||||
|
||||
using huge_array_json = nlohmann::basic_json <
|
||||
std::map, huge_array, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>, void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for array")
|
||||
{
|
||||
huge_array_json j = huge_array_json::array();
|
||||
|
||||
j.push_back(1);
|
||||
j.push_back(2);
|
||||
j.push_back(3);
|
||||
|
||||
auto& array = j.get_ref<huge_array_json::array_t&>();
|
||||
array.fake_size = true;
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_array_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
|
||||
array.fake_size = false;
|
||||
}
|
||||
|
||||
template<typename K, typename V,
|
||||
typename C = std::less<K>,
|
||||
typename A = std::allocator<std::pair<const K, V>>>
|
||||
struct huge_map : std::map<K, V, C, A>
|
||||
{
|
||||
using base = std::map<K, V, C, A>;
|
||||
using base::base;
|
||||
|
||||
bool fake_size = false;
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
if (fake_size)
|
||||
{
|
||||
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
|
||||
}
|
||||
|
||||
return base::size();
|
||||
}
|
||||
};
|
||||
|
||||
using huge_object_json = nlohmann::basic_json <
|
||||
huge_map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for object")
|
||||
{
|
||||
|
||||
huge_object_json j = huge_object_json::object();
|
||||
|
||||
j["one"] = 1;
|
||||
j["two"] = 2;
|
||||
|
||||
auto& object = j.get_ref<huge_object_json::object_t&>();
|
||||
object.fake_size = true;
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_object_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
|
||||
object.fake_size = false;
|
||||
}
|
||||
|
||||
struct huge_string : std::string
|
||||
{
|
||||
using std::string::string;
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
|
||||
}
|
||||
};
|
||||
|
||||
using huge_string_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
huge_string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for string")
|
||||
{
|
||||
|
||||
huge_string_json j = "hello";
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_string_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
}
|
||||
|
||||
struct huge_binary : std::vector<std::uint8_t>
|
||||
{
|
||||
using std::vector<std::uint8_t>::vector;
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
|
||||
}
|
||||
};
|
||||
|
||||
using huge_binary_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
huge_binary,
|
||||
void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for binary")
|
||||
{
|
||||
|
||||
huge_binary_json j = huge_binary_json::binary(huge_binary{});
|
||||
|
||||
j.get_binary().push_back(0x01);
|
||||
j.get_binary().push_back(0x02);
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_binary_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// types that report a size beyond UINT32_MAX without allocating that much
|
||||
// memory, so the MessagePack length limit can be tested cheaply; see the
|
||||
// similar types in unit-bson.cpp
|
||||
std::size_t beyond_uint32_size()
|
||||
{
|
||||
return static_cast<std::size_t>((std::numeric_limits<std::uint32_t>::max)()) + 1;
|
||||
}
|
||||
|
||||
class beyond_uint32_binary_t : public std::vector<std::uint8_t>
|
||||
{
|
||||
public:
|
||||
using std::vector<std::uint8_t>::vector;
|
||||
|
||||
size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
return beyond_uint32_size();
|
||||
}
|
||||
};
|
||||
|
||||
// with clang and libstdc++ 10, the std::filesystem::path conversion that
|
||||
// C++17 builds consider for every string type is ambiguous for a class
|
||||
// derived from std::string, so the string case is not tested there
|
||||
#if !(defined(__clang__) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11)
|
||||
#define JSON_TEST_BEYOND_UINT32_STRING 1
|
||||
#endif
|
||||
|
||||
#ifdef JSON_TEST_BEYOND_UINT32_STRING
|
||||
class beyond_uint32_string_t : public std::string
|
||||
{
|
||||
public:
|
||||
using std::string::string;
|
||||
|
||||
size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
return beyond_uint32_size();
|
||||
}
|
||||
};
|
||||
|
||||
using beyond_uint32_string_json = nlohmann::basic_json <
|
||||
std::map, std::vector, beyond_uint32_string_t, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::uint8_t>, void >;
|
||||
#endif
|
||||
|
||||
using beyond_uint32_binary_json = nlohmann::basic_json <
|
||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, beyond_uint32_binary_t, void >;
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("MessagePack lengths beyond UINT32_MAX cannot be serialized")
|
||||
{
|
||||
// MessagePack stores the length of a string, binary value, array, or
|
||||
// object in at most 32 bits; a larger one used to be written without any
|
||||
// length at all
|
||||
#if SIZE_MAX > UINT32_MAX
|
||||
{
|
||||
const char* const expected = "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295";
|
||||
|
||||
const beyond_uint32_binary_json binary = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{});
|
||||
CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(binary), expected, beyond_uint32_binary_json::out_of_range&);
|
||||
|
||||
const beyond_uint32_binary_json ext = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{}, 42);
|
||||
CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(ext), expected, beyond_uint32_binary_json::out_of_range&);
|
||||
|
||||
#ifdef JSON_TEST_BEYOND_UINT32_STRING
|
||||
// created from its type rather than from a beyond_uint32_string_t:
|
||||
// that would consider the std::filesystem::path conversion, which
|
||||
// libstdc++ 10 cannot decide for a class derived from std::string
|
||||
const beyond_uint32_string_json string(beyond_uint32_string_json::value_t::string);
|
||||
CHECK_THROWS_WITH_AS(beyond_uint32_string_json::to_msgpack(string), expected, beyond_uint32_string_json::out_of_range&);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1018,7 +1018,7 @@ TEST_CASE("regression tests 1")
|
||||
};
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x98", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an array; last byte: 0x98", json::parse_error&);
|
||||
|
||||
// related test case: nonempty UTF-8 string (indefinite length)
|
||||
std::vector<uint8_t> const vec1 {0x7f, 0x61, 0x61};
|
||||
@@ -1065,7 +1065,7 @@ TEST_CASE("regression tests 1")
|
||||
};
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
|
||||
|
||||
// related test case: double-precision
|
||||
std::vector<uint8_t> const vec2
|
||||
@@ -1077,7 +1077,7 @@ TEST_CASE("regression tests 1")
|
||||
0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
|
||||
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfb
|
||||
};
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("issue #452 - Heap-buffer-overflow (OSS-Fuzz issue 585)")
|
||||
|
||||
@@ -765,15 +765,6 @@ TEST_CASE("regression tests 2")
|
||||
CHECK(j == k);
|
||||
}
|
||||
|
||||
SECTION("issue #4552 - UTF-8 invalid characters are not always ignored when dumping with error_handler_t::ignore")
|
||||
{
|
||||
json node;
|
||||
node["test"] = "test\334\005";
|
||||
CHECK(node.dump(-1, ' ', false, json::error_handler_t::ignore) == "{\"test\":\"test\\u0005\"}");
|
||||
CHECK(node.dump(-1, ' ', false, json::error_handler_t::keep) == "{\"test\":\"test\334\\u0005\"}");
|
||||
CHECK(node.dump(-1, ' ', true, json::error_handler_t::keep) == "{\"test\":\"test\334\\u0005\"}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("regression test - parser callback must not lose a duplicate key's prior value")
|
||||
|
||||
@@ -92,8 +92,6 @@ TEST_CASE("serialization")
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"äü\"");
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"ä\xEF\xBF\xBDü\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\"");
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::keep) == "\"ä\xA9ü\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::keep) == "\"\\u00e4\xA9\\u00fc\"");
|
||||
}
|
||||
|
||||
SECTION("invalid character (regression guard for shared UTF-8 decoder, see #5529)")
|
||||
@@ -116,8 +114,6 @@ TEST_CASE("serialization")
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123\"");
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd\"");
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::keep) == "\"123\xC2\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::keep) == "\"123\xC2\"");
|
||||
}
|
||||
|
||||
SECTION("unexpected character")
|
||||
@@ -130,39 +126,6 @@ TEST_CASE("serialization")
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123456\"");
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\x34\x35\x36\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd456\"");
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::keep) == "\"123\xF1\xB0\x34\x35\x36\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::keep) == "\"123\xF1\xB0\x34\x35\x36\"");
|
||||
}
|
||||
|
||||
SECTION("keep: valid characters are still escaped")
|
||||
{
|
||||
// an invalid byte followed by characters that must be escaped
|
||||
const json j = "\xC2\"\\\n\xFF\x05";
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::keep) == "\"\xC2\\\"\\\\\\n\xFF\\u0005\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::keep) == "\"\xC2\\\"\\\\\\n\xFF\\u0005\"");
|
||||
}
|
||||
|
||||
SECTION("keep: truncated multibyte sequences")
|
||||
{
|
||||
CHECK(json("\xF0\x9F\x98").dump(-1, ' ', false, json::error_handler_t::keep) == "\"\xF0\x9F\x98\"");
|
||||
CHECK(json("\xF0\x9F\x98").dump(-1, ' ', true, json::error_handler_t::keep) == "\"\xF0\x9F\x98\"");
|
||||
CHECK(json("\xF0\x9F\x98" "a").dump(-1, ' ', false, json::error_handler_t::keep) == "\"\xF0\x9F\x98" "a\"");
|
||||
CHECK(json("\xF0\x9F\x98" "a").dump(-1, ' ', true, json::error_handler_t::keep) == "\"\xF0\x9F\x98" "a\"");
|
||||
}
|
||||
|
||||
SECTION("keep: long string with many invalid bytes")
|
||||
{
|
||||
// exceeds the internal string buffer several times
|
||||
std::string input;
|
||||
std::string expected = "\"";
|
||||
for (int i = 0; i < 2000; ++i)
|
||||
{
|
||||
input += "\xFF\xE2\x82\n\xC3\xA4";
|
||||
expected += "\xFF\xE2\x82\\n\xC3\xA4";
|
||||
}
|
||||
expected += "\"";
|
||||
const json j = input;
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::keep) == expected);
|
||||
}
|
||||
|
||||
SECTION("U+FFFD Substitution of Maximal Subparts")
|
||||
@@ -676,3 +639,163 @@ TEST_CASE("serialization of deeply nested values")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// wraps @a inner into @a depth single-element arrays
|
||||
json wrap_in_arrays(const json& inner, const std::size_t depth)
|
||||
{
|
||||
json j = inner;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
j = json::array({std::move(j)});
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
// what wrap_in_arrays(inner, depth).dump(2) is expected to be: the arrays
|
||||
// around inner.dump(2), with inner's own lines indented by the depth
|
||||
std::string expected_pretty_in_arrays(const json& inner, const std::size_t depth)
|
||||
{
|
||||
std::string expected;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
expected += std::string(2 * i, ' ') + "[\n";
|
||||
}
|
||||
|
||||
const std::string indent(2 * depth, ' ');
|
||||
expected += indent;
|
||||
for (const char c : inner.dump(2))
|
||||
{
|
||||
expected += c;
|
||||
if (c == '\n')
|
||||
{
|
||||
expected += indent;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = depth; i > 0; --i)
|
||||
{
|
||||
expected += '\n' + std::string(2 * (i - 1), ' ') + ']';
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("serialization of every kind of value below the bound of the descent")
|
||||
{
|
||||
// Values nested deeper than the bound are written without the call stack,
|
||||
// by code of their own; each kind of value must come out the same there as
|
||||
// it does at the top level, compact and pretty-printed.
|
||||
std::vector<json> values =
|
||||
{
|
||||
json::parse(R"({"a": 1, "b": [1, 2, {"c": "x"}], "d": {}, "e": []})"),
|
||||
json::parse(R"([1, [2, 3], {"k": null}, "s"])"),
|
||||
json::object(),
|
||||
json::array(),
|
||||
json::binary({1, 2, 3}, 42),
|
||||
json::binary({1, 2, 3}),
|
||||
json::binary({}, 7),
|
||||
json::binary({}),
|
||||
"a string with \"escapes\"\n",
|
||||
true,
|
||||
false,
|
||||
-42,
|
||||
42u,
|
||||
1.5,
|
||||
nullptr,
|
||||
json(json::value_t::discarded),
|
||||
};
|
||||
// a pretty-printed object whose members are themselves deep
|
||||
values.push_back({{"x", wrap_in_arrays(1, 5)}, {"y", {{"z", 2}}}});
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {1, 200})
|
||||
{
|
||||
CAPTURE(depth);
|
||||
for (const auto& inner : values)
|
||||
{
|
||||
CAPTURE(inner.dump());
|
||||
const json j = wrap_in_arrays(inner, depth);
|
||||
CHECK(j.dump() == std::string(depth, '[') + inner.dump() + std::string(depth, ']'));
|
||||
CHECK(j.dump(2) == expected_pretty_in_arrays(inner, depth));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("pretty-printed objects across the bound")
|
||||
{
|
||||
for (std::size_t d = 120; d <= 140; ++d)
|
||||
{
|
||||
CAPTURE(d);
|
||||
|
||||
// built from the inside out: {"k": <level below>, "n": <level>}
|
||||
json j = 7;
|
||||
std::string expected = "7";
|
||||
for (std::size_t i = d; i > 0; --i)
|
||||
{
|
||||
j = json({{"k", std::move(j)}, {"n", i}});
|
||||
|
||||
const std::string indent(2 * i, ' ');
|
||||
const std::string outer_indent(2 * (i - 1), ' ');
|
||||
std::string next = "{\n";
|
||||
next += indent;
|
||||
next += "\"k\": ";
|
||||
next += expected;
|
||||
next += ",\n";
|
||||
next += indent;
|
||||
next += "\"n\": ";
|
||||
next += std::to_string(i);
|
||||
next += '\n';
|
||||
next += outer_indent;
|
||||
next += '}';
|
||||
expected = std::move(next);
|
||||
}
|
||||
|
||||
CHECK(j.dump(2) == expected);
|
||||
CHECK(json::parse(j.dump(2)) == j);
|
||||
CHECK(json::parse(j.dump()) == j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("serializer buffers are flushed mid-string and mid-binary")
|
||||
{
|
||||
SECTION("a long run of escaped characters")
|
||||
{
|
||||
// each character is escaped on its own, so the escape buffer fills up
|
||||
const json newlines = std::string(600, '\n');
|
||||
std::string expected = "\"";
|
||||
for (int i = 0; i < 600; ++i)
|
||||
{
|
||||
expected += "\\n";
|
||||
}
|
||||
expected += '"';
|
||||
CHECK(newlines.dump() == expected);
|
||||
|
||||
// every character is \u-escaped under ensure_ascii
|
||||
std::string umlauts;
|
||||
std::string escaped_umlauts = "\"";
|
||||
for (int i = 0; i < 300; ++i)
|
||||
{
|
||||
umlauts += "\xC3\xA4";
|
||||
escaped_umlauts += "\\u00e4";
|
||||
}
|
||||
escaped_umlauts += '"';
|
||||
CHECK(json(umlauts).dump(-1, ' ', true) == escaped_umlauts);
|
||||
}
|
||||
|
||||
SECTION("a large binary value")
|
||||
{
|
||||
std::vector<std::uint8_t> bytes(3000);
|
||||
std::string expected_bytes;
|
||||
std::string expected_pretty_bytes;
|
||||
for (std::size_t i = 0; i < bytes.size(); ++i)
|
||||
{
|
||||
bytes[i] = static_cast<std::uint8_t>(i % 256);
|
||||
expected_bytes += (i == 0 ? "" : ",") + std::to_string(i % 256);
|
||||
expected_pretty_bytes += (i == 0 ? "" : ", ") + std::to_string(i % 256);
|
||||
}
|
||||
const json j = json::binary(bytes);
|
||||
CHECK(j.dump() == "{\"bytes\":[" + expected_bytes + "],\"subtype\":null}");
|
||||
CHECK(j.dump(2) == "{\n \"bytes\": [" + expected_pretty_bytes + "],\n \"subtype\": null\n}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,29 @@ TEST_CASE("std::formatter<nlohmann::json>")
|
||||
CHECK_THROWS_AS(std::vformat("{:{}}", std::make_format_args(j, dynamic_width)), std::format_error); // dynamic width
|
||||
}
|
||||
|
||||
SECTION("a format spec may run to the end of the parse context")
|
||||
{
|
||||
// std::format always hands parse() a range that still holds the closing
|
||||
// '}', but a parse context may also end right after the spec
|
||||
const auto parse = [](const char* spec)
|
||||
{
|
||||
std::format_parse_context ctx(spec);
|
||||
std::formatter<json> f;
|
||||
CHECK(f.parse(ctx) == ctx.end());
|
||||
return f;
|
||||
};
|
||||
|
||||
CHECK(parse("").indent == -1);
|
||||
CHECK(parse(">").indent == -1);
|
||||
CHECK(parse("#").indent == 4);
|
||||
CHECK(parse("3").indent == 3);
|
||||
CHECK(parse("#12").indent == 12);
|
||||
|
||||
const auto f = parse(".>");
|
||||
CHECK(f.indent == -1);
|
||||
CHECK(f.indent_char == '.');
|
||||
}
|
||||
|
||||
SECTION("std::format_to writes through an arbitrary output iterator")
|
||||
{
|
||||
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
|
||||
|
||||
+85
-33
@@ -265,7 +265,7 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("-32768..-129 (int16)")
|
||||
{
|
||||
for (int32_t i = -32768; i <= -129; ++i)
|
||||
for (int32_t i = -32768; i <= -129; i = utils::next_integer_sample(i, -129, 7))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -425,7 +425,7 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("256..32767 (int16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 32767; ++i)
|
||||
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -631,7 +631,7 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("256..32767 (int16)")
|
||||
{
|
||||
for (size_t i = 256; i <= 32767; ++i)
|
||||
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
|
||||
{
|
||||
CAPTURE(i)
|
||||
|
||||
@@ -1640,6 +1640,29 @@ TEST_CASE("UBJSON")
|
||||
});
|
||||
CHECK_THROWS_AS(_ = json::sax_parse(v_ubjson, &scp, json::input_format_t::ubjson), json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("array with a known size, read with a callback")
|
||||
{
|
||||
// a sized array announces its length to start_array()
|
||||
std::vector<uint8_t> const v_ubjson = {'[', '#', 'i', 2, 'i', 1, 'i', 2};
|
||||
json j;
|
||||
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
CHECK(json::sax_parse(v_ubjson, &scp, json::input_format_t::ubjson));
|
||||
CHECK(j == json({1, 2}));
|
||||
|
||||
// the readers reject a size this large before they announce
|
||||
// it, so it can only reach start_array() directly (the largest
|
||||
// value stands for an unknown size and is never checked)
|
||||
json k;
|
||||
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp2(k, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
CHECK_THROWS_AS(scp2.start_array((std::numeric_limits<std::size_t>::max)() - 1), json::out_of_range&);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2255,6 +2278,46 @@ TEST_CASE("UBJSON nesting does not consume the call stack")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON input that cannot be read is discarded by every overload")
|
||||
{
|
||||
std::vector<std::uint8_t> input = json::to_ubjson(json({{"a", {1, 2}}}));
|
||||
input.pop_back();
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_ubjson(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_ubjson(input, true, false).is_discarded());
|
||||
CHECK(json::from_ubjson(input.begin(), input.end(), true, false).is_discarded());
|
||||
CHECK(json::from_ubjson(input.data(), input.size(), true, false).is_discarded());
|
||||
CHECK(json::from_ubjson({input.data(), input.size()}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON SAX parsing stops at every event")
|
||||
{
|
||||
// Containers are opened and closed by the loop that reads them; a SAX
|
||||
// handler that rejects any event - including the end of a nested
|
||||
// container - must stop the parse right there.
|
||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||
{
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
SaxCountdown scp(events);
|
||||
if (json::sax_parse(input, &scp, json::input_format_t::ubjson))
|
||||
{
|
||||
return events;
|
||||
}
|
||||
++events;
|
||||
REQUIRE(events < 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 20 events: every container kind closes inside another one
|
||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||
CHECK(count_events(json::to_ubjson(j)) == 20);
|
||||
CHECK(count_events(json::to_ubjson(j, true)) == 20);
|
||||
CHECK(count_events(json::to_ubjson(j, true, true)) == 20);
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON optimized arrays of a valueless type are bounded")
|
||||
{
|
||||
// An element of type 'Z', 'T' or 'F' is encoded by its marker alone, so an
|
||||
@@ -2917,60 +2980,34 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip())
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
auto const packed = utils::read_binary_file(filename + ".ubjson");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse UBJSON file
|
||||
auto const packed = utils::read_binary_file(filename + ".ubjson");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_ubjson(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse UBJSON file
|
||||
std::ifstream f_ubjson(filename + ".ubjson", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_ubjson(f_ubjson));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": uint8_t* and size");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse UBJSON file
|
||||
auto const packed = utils::read_binary_file(filename + ".ubjson");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_ubjson({packed.data(), packed.size()}));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output to output adapters");
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
json const j1 = json::parse(f_json);
|
||||
|
||||
// parse UBJSON file
|
||||
auto const packed = utils::read_binary_file(filename + ".ubjson");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
|
||||
std::vector<uint8_t> vec;
|
||||
@@ -2981,3 +3018,18 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("UBJSON optimized array of unsigned integers beyond int64")
|
||||
{
|
||||
// UBJSON has no unsigned 64-bit type, so such values are written as
|
||||
// high-precision numbers - also as the type of an optimized container
|
||||
const json j = {18446744073709551615ULL, 9223372036854775808ULL};
|
||||
const std::vector<std::uint8_t> expected =
|
||||
{
|
||||
'[', '$', 'H', '#', 'i', 2,
|
||||
'i', 20, '1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '1', '6', '1', '5',
|
||||
'i', 19, '9', '2', '2', '3', '3', '7', '2', '0', '3', '6', '8', '5', '4', '7', '7', '5', '8', '0', '8'
|
||||
};
|
||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||
CHECK(json::from_ubjson(expected) == j);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
@@ -76,11 +75,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
static std::string s_replaced2;
|
||||
static std::string s_replaced_ascii;
|
||||
static std::string s_replaced2_ascii;
|
||||
static std::string s_kept;
|
||||
static std::string s_kept2;
|
||||
static std::string s_kept_ascii;
|
||||
|
||||
// dumping with ignore/replace/keep must not throw in any case
|
||||
// dumping with ignore/replace must not throw in any case
|
||||
s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||
@@ -89,9 +85,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||
s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||
s_kept = j.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
s_kept2 = j2.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
s_kept_ascii = j.dump(-1, ' ', true, json::error_handler_t::keep);
|
||||
|
||||
if (success_expected)
|
||||
{
|
||||
@@ -101,7 +94,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
// all dumps should agree on the string
|
||||
CHECK(s_strict == s_ignored);
|
||||
CHECK(s_strict == s_replaced);
|
||||
CHECK(s_strict == s_kept);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,20 +105,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
|
||||
// check that replace string contains a replacement character
|
||||
CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
|
||||
|
||||
// ignore drops the invalid bytes, keep copies them
|
||||
CHECK(s_ignored != s_kept);
|
||||
CHECK(s_ignored_ascii != s_kept_ascii);
|
||||
|
||||
// unless a byte needs escaping, keep copies the input unchanged
|
||||
const bool needs_escaping = std::any_of(json_string.begin(), json_string.end(), [](char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c) < 0x20 || c == '"' || c == '\\';
|
||||
});
|
||||
if (!needs_escaping)
|
||||
{
|
||||
CHECK(s_kept == "\"" + json_string + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
// check that prefix and suffix are preserved
|
||||
@@ -138,8 +116,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
|
||||
CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
|
||||
CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
|
||||
CHECK(s_kept2.substr(1, 3) == "abc");
|
||||
CHECK(s_kept2.substr(s_kept2.size() - 4, 3) == "xyz");
|
||||
}
|
||||
|
||||
void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
@@ -76,11 +75,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
static std::string s_replaced2;
|
||||
static std::string s_replaced_ascii;
|
||||
static std::string s_replaced2_ascii;
|
||||
static std::string s_kept;
|
||||
static std::string s_kept2;
|
||||
static std::string s_kept_ascii;
|
||||
|
||||
// dumping with ignore/replace/keep must not throw in any case
|
||||
// dumping with ignore/replace must not throw in any case
|
||||
s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||
@@ -89,9 +85,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||
s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||
s_kept = j.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
s_kept2 = j2.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
s_kept_ascii = j.dump(-1, ' ', true, json::error_handler_t::keep);
|
||||
|
||||
if (success_expected)
|
||||
{
|
||||
@@ -101,7 +94,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
// all dumps should agree on the string
|
||||
CHECK(s_strict == s_ignored);
|
||||
CHECK(s_strict == s_replaced);
|
||||
CHECK(s_strict == s_kept);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,20 +105,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
|
||||
// check that replace string contains a replacement character
|
||||
CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
|
||||
|
||||
// ignore drops the invalid bytes, keep copies them
|
||||
CHECK(s_ignored != s_kept);
|
||||
CHECK(s_ignored_ascii != s_kept_ascii);
|
||||
|
||||
// unless a byte needs escaping, keep copies the input unchanged
|
||||
const bool needs_escaping = std::any_of(json_string.begin(), json_string.end(), [](char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c) < 0x20 || c == '"' || c == '\\';
|
||||
});
|
||||
if (!needs_escaping)
|
||||
{
|
||||
CHECK(s_kept == "\"" + json_string + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
// check that prefix and suffix are preserved
|
||||
@@ -138,8 +116,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
|
||||
CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
|
||||
CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
|
||||
CHECK(s_kept2.substr(1, 3) == "abc");
|
||||
CHECK(s_kept2.substr(s_kept2.size() - 4, 3) == "xyz");
|
||||
}
|
||||
|
||||
void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
@@ -76,11 +75,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
static std::string s_replaced2;
|
||||
static std::string s_replaced_ascii;
|
||||
static std::string s_replaced2_ascii;
|
||||
static std::string s_kept;
|
||||
static std::string s_kept2;
|
||||
static std::string s_kept_ascii;
|
||||
|
||||
// dumping with ignore/replace/keep must not throw in any case
|
||||
// dumping with ignore/replace must not throw in any case
|
||||
s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||
@@ -89,9 +85,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||
s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||
s_kept = j.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
s_kept2 = j2.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
s_kept_ascii = j.dump(-1, ' ', true, json::error_handler_t::keep);
|
||||
|
||||
if (success_expected)
|
||||
{
|
||||
@@ -101,7 +94,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
// all dumps should agree on the string
|
||||
CHECK(s_strict == s_ignored);
|
||||
CHECK(s_strict == s_replaced);
|
||||
CHECK(s_strict == s_kept);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,20 +105,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
|
||||
// check that replace string contains a replacement character
|
||||
CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
|
||||
|
||||
// ignore drops the invalid bytes, keep copies them
|
||||
CHECK(s_ignored != s_kept);
|
||||
CHECK(s_ignored_ascii != s_kept_ascii);
|
||||
|
||||
// unless a byte needs escaping, keep copies the input unchanged
|
||||
const bool needs_escaping = std::any_of(json_string.begin(), json_string.end(), [](char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c) < 0x20 || c == '"' || c == '\\';
|
||||
});
|
||||
if (!needs_escaping)
|
||||
{
|
||||
CHECK(s_kept == "\"" + json_string + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
// check that prefix and suffix are preserved
|
||||
@@ -138,8 +116,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
|
||||
CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
|
||||
CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
|
||||
CHECK(s_kept2.substr(1, 3) == "abc");
|
||||
CHECK(s_kept2.substr(s_kept2.size() - 4, 3) == "xyz");
|
||||
}
|
||||
|
||||
void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
@@ -76,11 +75,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
static std::string s_replaced2;
|
||||
static std::string s_replaced_ascii;
|
||||
static std::string s_replaced2_ascii;
|
||||
static std::string s_kept;
|
||||
static std::string s_kept2;
|
||||
static std::string s_kept_ascii;
|
||||
|
||||
// dumping with ignore/replace/keep must not throw in any case
|
||||
// dumping with ignore/replace must not throw in any case
|
||||
s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||
@@ -89,9 +85,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||
s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||
s_kept = j.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
s_kept2 = j2.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
s_kept_ascii = j.dump(-1, ' ', true, json::error_handler_t::keep);
|
||||
|
||||
if (success_expected)
|
||||
{
|
||||
@@ -101,7 +94,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
// all dumps should agree on the string
|
||||
CHECK(s_strict == s_ignored);
|
||||
CHECK(s_strict == s_replaced);
|
||||
CHECK(s_strict == s_kept);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,20 +105,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
|
||||
// check that replace string contains a replacement character
|
||||
CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
|
||||
|
||||
// ignore drops the invalid bytes, keep copies them
|
||||
CHECK(s_ignored != s_kept);
|
||||
CHECK(s_ignored_ascii != s_kept_ascii);
|
||||
|
||||
// unless a byte needs escaping, keep copies the input unchanged
|
||||
const bool needs_escaping = std::any_of(json_string.begin(), json_string.end(), [](char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c) < 0x20 || c == '"' || c == '\\';
|
||||
});
|
||||
if (!needs_escaping)
|
||||
{
|
||||
CHECK(s_kept == "\"" + json_string + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
// check that prefix and suffix are preserved
|
||||
@@ -138,8 +116,6 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
||||
CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
|
||||
CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
|
||||
CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
|
||||
CHECK(s_kept2.substr(1, 3) == "abc");
|
||||
CHECK(s_kept2.substr(s_kept2.size() - 4, 3) == "xyz");
|
||||
}
|
||||
|
||||
void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||
|
||||
@@ -70,6 +70,8 @@ TEST_CASE("wide strings")
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'"'}), error_low_surrogate, json::parse_error&);
|
||||
// a high surrogate followed by a non-low-surrogate unit is invalid
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), L'a', L'"'}), error_high_surrogate, json::parse_error&);
|
||||
// ... also when the unit is above the low surrogates
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), static_cast<wchar_t>(0xE000), L'"'}), error_high_surrogate, json::parse_error&);
|
||||
// a lone low surrogate must not swallow the following unit: pairing
|
||||
// it with any second unit would produce valid UTF-8, so the error
|
||||
// has to report an ill-formed byte at the surrogate's own position
|
||||
@@ -99,6 +101,8 @@ TEST_CASE("wide strings")
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
|
||||
// a high surrogate followed by a non-low-surrogate unit is invalid
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
|
||||
// ... also when the unit is above the low surrogates
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
|
||||
// a lone low surrogate must not swallow the following unit: pairing
|
||||
// it with any second unit would produce valid UTF-8, so the error
|
||||
// has to report an ill-formed byte at the surrogate's own position
|
||||
|
||||
Reference in New Issue
Block a user