mirror of
https://github.com/nlohmann/json.git
synced 2026-10-26 15:58:55 +00:00
Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df3f1900d9 | ||
|
|
67b4077734 | ||
|
|
4450aed07e | ||
|
|
ea868aa841 | ||
|
|
3f9c68f63f | ||
|
|
b64ea9946d | ||
|
|
840cdd5b88 | ||
|
|
c0ad2ba72f | ||
|
|
c4e1032f36 | ||
|
|
da65c02eea | ||
|
|
bf8dca5c16 | ||
|
|
d19e030bf1 | ||
|
|
e359852b76 | ||
|
|
734912bed6 | ||
|
|
91cc42b302 | ||
|
|
95412a36d0 | ||
|
|
d2309300e9 | ||
|
|
d3d39a9c10 | ||
|
|
24ff6eaa43 | ||
|
|
9d9bf927ff | ||
|
|
cc39e1f8da | ||
|
|
5ac95db5a9 | ||
|
|
d1d142633a | ||
|
|
49e9eaac4f | ||
|
|
4b35877ae5 | ||
|
|
34f9be2aa2 | ||
|
|
8c4818d5f7 | ||
|
|
df1bca6dae | ||
|
|
b22cad367c | ||
|
|
95169a63fa |
No files matched your search
@@ -90,8 +90,9 @@ type `#!cpp binary_t*` must be dereferenced.
|
|||||||
|
|
||||||
- MessagePack
|
- MessagePack
|
||||||
- If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1,
|
- If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1,
|
||||||
fixext2, fixext4, fixext8) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The subtype is
|
fixext2, fixext4, fixext8, fixext16) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The
|
||||||
then added as a signed 8-bit integer.
|
subtype is then written as the signed 8-bit ext type: subtypes 128 to 255 map to the negative (reserved) ext
|
||||||
|
types, and on reading the ext type is exposed as an unsigned subtype from 0 to 255.
|
||||||
- If no subtype is given, the bin family (bin8, bin16, bin32) is used.
|
- If no subtype is given, the bin family (bin8, bin16, bin32) is used.
|
||||||
|
|
||||||
- BSON
|
- BSON
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# <small>nlohmann::basic_json::</small>bjdata_version_t
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class bjdata_version_t
|
||||||
|
{
|
||||||
|
draft2,
|
||||||
|
draft3
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This enumeration is used in [`to_bjdata`](to_bjdata.md) to choose the version of the BJData specification to write:
|
||||||
|
|
||||||
|
draft2
|
||||||
|
: BJData Draft 2 (the default). Binary values are written as arrays of `U` integers, and ND-arrays with
|
||||||
|
`"_ArrayType_": "byte"` are written as regular objects, since the `B` marker is not part of Draft 2.
|
||||||
|
|
||||||
|
draft3
|
||||||
|
: BJData Draft 3. Binary values are written as `B` elements: as an optimized binary array (`[$B#`) if both `use_size`
|
||||||
|
and `use_type` are set, and as an array of `B`-prefixed elements otherwise. ND-arrays with
|
||||||
|
`"_ArrayType_": "byte"` are written as packed arrays (`[$B#[...]`).
|
||||||
|
|
||||||
|
Only the writer is affected by the version. [`from_bjdata`](from_bjdata.md) reads the markers of both drafts.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The example below shows how the different values of the `bjdata_version_t` influence the serialization of a binary
|
||||||
|
value with [`to_bjdata`](to_bjdata.md).
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/bjdata_version_t.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/bjdata_version_t.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [to_bjdata](to_bjdata.md) serializes a JSON value to BJData
|
||||||
|
- [BJData](../../features/binary_formats/bjdata.md) - the article on the BJData format
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.12.0.
|
||||||
@@ -15,11 +15,13 @@ error
|
|||||||
: report a parse error in case of a tag (the `from_cbor` overloads throw a `parse_error` exception by default)
|
: report a parse error in case of a tag (the `from_cbor` overloads throw a `parse_error` exception by default)
|
||||||
|
|
||||||
ignore
|
ignore
|
||||||
: ignore tags
|
: ignore tags (also in front of a text string map key)
|
||||||
|
|
||||||
store
|
store
|
||||||
: store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are
|
: 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.
|
read as if the tag were ignored. If several tags precede a byte string, only the tag directly in front of it is
|
||||||
|
stored, and only when it has the form 0xd8..0xdb (a tag 0..23 written as 0xc0..0xd7 is skipped). Tags in front of a
|
||||||
|
text string map key are skipped.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -82,8 +82,11 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs
|
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs
|
||||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed
|
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed
|
||||||
successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict`
|
successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict`
|
||||||
|
- Throws [parse_error.115](../../home/exceptions.md#jsonexceptionparse_error115) if the text of a high-precision
|
||||||
|
number (`H`) is not a valid number
|
||||||
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container
|
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container
|
||||||
or n-dimensional array cannot be represented by `std::size_t`
|
cannot be represented by `std::size_t` or exceeds the container's `max_size()`, or if the product of the dimensions of
|
||||||
|
an n-dimensional array overflows
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs, for instance
|
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs, for instance
|
||||||
an invalid byte, a string that is not valid UTF-8, or an object key that is not a string
|
an invalid byte, a string that is not valid UTF-8, or an object key that is not a string
|
||||||
|
|
||||||
|
The UTF-8 validity of strings is always checked, independent of
|
||||||
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md), because the end of a BON8 string is determined from
|
||||||
|
its encoding. Duplicate object keys are accepted; the last value wins (see
|
||||||
|
[BON8](../../features/binary_formats/bon8.md)).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
Linear in the size of the input.
|
Linear in the size of the input.
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [`parse_error.110`](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or
|
- Throws [`parse_error.110`](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or
|
||||||
the end of the input was not reached when `strict` was set to true
|
the end of the input was not reached when `strict` was set to true
|
||||||
- Throws [`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs (e.g., an
|
- Throws [`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs (e.g., an
|
||||||
invalid string or byte array length)
|
invalid string or byte array length, an invalid inner length of a binary value with subtype `0x02`, or a document size
|
||||||
|
that does not match the content; `strict` does not affect the document size checks)
|
||||||
- Throws [`parse_error.114`](../../home/exceptions.md#jsonexceptionparse_error114) if an unsupported BSON record type is
|
- Throws [`parse_error.114`](../../home/exceptions.md#jsonexceptionparse_error114) if an unsupported BSON record type is
|
||||||
encountered
|
encountered
|
||||||
- Throws [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) if a string value or object key is
|
- Throws [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) if a string value or object key is
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
|
|
||||||
- an `std::istream` object
|
- an `std::istream` object
|
||||||
- a `FILE` pointer
|
- a `FILE` pointer
|
||||||
- a C-style array of characters
|
- a C-style array of characters (its terminating NUL is read as trailing data: `parse_error.110` unless `strict` is
|
||||||
- a pointer to a null-terminated string of single byte characters
|
`false`)
|
||||||
|
- a pointer to a null-terminated string of single byte characters (read up to the first 0x00 byte, which is a valid
|
||||||
|
CBOR byte)
|
||||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
||||||
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
|
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
|
||||||
|
|
||||||
@@ -89,8 +91,14 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
|
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
|
||||||
used in the given input or if the input is not valid CBOR
|
used in the given input or if the input is not valid CBOR
|
||||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of
|
- 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 if a string value or object key is not
|
other types are not supported, as JSON object keys are always strings), if the head of a string or byte string is
|
||||||
valid UTF-8 and `error_handler` is `strict`
|
malformed (including a chunk of the wrong type or an indefinite-length string nested inside an indefinite-length
|
||||||
|
string), or if a string value or object key (or one chunk of an indefinite-length string) is not valid UTF-8 and
|
||||||
|
`error_handler` is `strict`
|
||||||
|
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if an array or map announces
|
||||||
|
a length that `std::size_t` cannot hold, or that equals `std::size_t(-1)` (for example `9B FF FF FF FF FF FF FF FF`)
|
||||||
|
- Throws [out_of_range.406](../../home/exceptions.md#jsonexceptionout_of_range406) if a floating-point number does not
|
||||||
|
fit into `number_float_t`
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -79,11 +79,18 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
|
|
||||||
- Throws [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or
|
- Throws [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or
|
||||||
the end of the file was not reached when `strict` was set to true
|
the end of the file was not reached when `strict` was set to true
|
||||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs
|
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if an invalid type marker is read
|
||||||
|
(including a type marker after `$` of an optimized container, even if its count is 0), or if `$` is not followed by `#`
|
||||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed
|
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed
|
||||||
successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict`
|
successfully (negative or non-integer length, `C` followed by a byte above 0x7F), if the count of an optimized
|
||||||
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container
|
container is negative, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict`
|
||||||
or n-dimensional array cannot be represented by `std::size_t`
|
- Throws [parse_error.115](../../home/exceptions.md#jsonexceptionparse_error115) if the text of a high-precision number
|
||||||
|
(`H`) is not a valid JSON number
|
||||||
|
- Throws [out_of_range.406](../../home/exceptions.md#jsonexceptionout_of_range406) if a high-precision number, or a
|
||||||
|
float64 when `number_float_t` is `float`, does not fit into `number_float_t`
|
||||||
|
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the count of an optimized
|
||||||
|
container exceeds the capacity of the container (or of `std::size_t`), or if an optimized array of `Z`, `T`, or `F` has
|
||||||
|
more than 1,048,576 elements
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ The class satisfies the following concept requirements:
|
|||||||
- [**json_pointer**](../json_pointer/index.md) - JSON Pointer implementation
|
- [**json_pointer**](../json_pointer/index.md) - JSON Pointer implementation
|
||||||
- [**json_serializer**](json_serializer.md) - type of the serializer to for conversions from/to JSON
|
- [**json_serializer**](json_serializer.md) - type of the serializer to for conversions from/to JSON
|
||||||
- [**error_handler_t**](error_handler_t.md) - type to choose behavior on decoding errors
|
- [**error_handler_t**](error_handler_t.md) - type to choose behavior on decoding errors
|
||||||
|
- [**bjdata_version_t**](bjdata_version_t.md) - type to choose the BJData version to write
|
||||||
- [**cbor_tag_handler_t**](cbor_tag_handler_t.md) - type to choose how to handle CBOR tags
|
- [**cbor_tag_handler_t**](cbor_tag_handler_t.md) - type to choose how to handle CBOR tags
|
||||||
- **initializer_list_t** - type for initializer lists of `basic_json` values
|
- **initializer_list_t** - type for initializer lists of `basic_json` values
|
||||||
- [**input_format_t**](input_format_t.md) - type to choose the format to parse
|
- [**input_format_t**](input_format_t.md) - type to choose the format to parse
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
`#!cpp false` by default.
|
`#!cpp false` by default.
|
||||||
|
|
||||||
`version` (in)
|
`version` (in)
|
||||||
: which version of BJData to use (see note on "Binary values" on [BJData](../../features/binary_formats/bjdata.md));
|
: which version of BJData to use, see [`bjdata_version_t`](bjdata_version_t.md) and the note on "Binary values" on
|
||||||
|
[BJData](../../features/binary_formats/bjdata.md);
|
||||||
optional, `#!cpp bjdata_version_t::draft2` by default.
|
optional, `#!cpp bjdata_version_t::draft2` by default.
|
||||||
|
|
||||||
`error_handler` (in)
|
`error_handler` (in)
|
||||||
@@ -64,7 +65,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
|
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
|
||||||
is false, and `j` contains a non-empty array, object, or binary value.
|
is false, and `j` contains a non-empty array or object, or a non-empty binary value (with
|
||||||
|
`version = bjdata_version_t::draft3`, any binary value, including an empty one).
|
||||||
|
Scalars, empty arrays, and empty objects do not throw.
|
||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ static void to_bson(const basic_json& j, detail::output_adapter<char> o,
|
|||||||
```
|
```
|
||||||
|
|
||||||
BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are stored as a single entity (a
|
BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are stored as a single entity (a
|
||||||
so-called document).
|
so-called document). The members of an object are written in the order in which iteration visits them.
|
||||||
|
|
||||||
1. Returns a byte vector containing the BSON serialization.
|
1. Returns a byte vector containing the BSON serialization.
|
||||||
2. Writes the BSON serialization to an output adapter.
|
2. Writes the BSON serialization to an output adapter.
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
|
|
||||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
Strong guarantee: if an exception is thrown, there are no changes in the JSON value. Bytes that were already written to
|
||||||
|
the output adapter are not taken back.
|
||||||
|
|
||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
|
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
|
||||||
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
|
`"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
|
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
|
||||||
exceeds 255, the maximum of the MessagePack ext type; example:
|
exceeds 255, the largest value the library can store in the one-byte MessagePack ext type; example:
|
||||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict`
|
not valid UTF-8 and `error_handler` is `strict`
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class byte_container_with_subtype : public BinaryType;
|
|||||||
```
|
```
|
||||||
|
|
||||||
This type extends the template parameter `BinaryType` provided to [`basic_json`](../basic_json/index.md) with a subtype
|
This type extends the template parameter `BinaryType` provided to [`basic_json`](../basic_json/index.md) with a subtype
|
||||||
used by BSON and MessagePack. This type exists so that the user does not have to specify a type themselves with a
|
used by BSON, CBOR, and MessagePack. This type exists so that the user does not have to specify a type themselves with a
|
||||||
specific naming scheme in order to override the binary type.
|
specific naming scheme in order to override the binary type.
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
void print_hex(const char* label, const std::vector<std::uint8_t>& v)
|
||||||
|
{
|
||||||
|
std::cout << label << ':';
|
||||||
|
for (auto b : v)
|
||||||
|
{
|
||||||
|
std::cout << ' ' << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(b);
|
||||||
|
}
|
||||||
|
std::cout << std::dec << std::setfill(' ') << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// a binary value
|
||||||
|
json j = json::binary({0xCA, 0xFE, 0xBA, 0xBE});
|
||||||
|
|
||||||
|
// Draft 2 (the default) writes an array of integers
|
||||||
|
auto v2 = json::to_bjdata(j, true, true, json::bjdata_version_t::draft2);
|
||||||
|
print_hex("draft2", v2);
|
||||||
|
|
||||||
|
// Draft 3 writes an optimized binary array
|
||||||
|
auto v3 = json::to_bjdata(j, true, true, json::bjdata_version_t::draft3);
|
||||||
|
print_hex("draft3", v3);
|
||||||
|
|
||||||
|
// reading yields an array for Draft 2 and a binary value for Draft 3
|
||||||
|
std::cout << json::from_bjdata(v2) << '\n';
|
||||||
|
std::cout << json::from_bjdata(v3) << '\n';
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
draft2: 5b 24 55 23 69 04 ca fe ba be
|
||||||
|
draft3: 5b 24 42 23 69 04 ca fe ba be
|
||||||
|
[202,254,186,190]
|
||||||
|
{"bytes":[202,254,186,190],"subtype":null}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
The [BJData format](https://neurojson.org) was derived from and improved upon
|
The [BJData format](https://neurojson.org) was derived from and improved upon
|
||||||
[Universal Binary JSON(UBJSON)](https://ubjson.org) specification (Draft 12). Specifically, it introduces an optimized
|
[Universal Binary JSON(UBJSON)](https://ubjson.org) specification (Draft 12). Specifically, it introduces an optimized
|
||||||
array container for efficient storage of N-dimensional packed arrays (**ND-arrays**); it also adds 5 new type markers -
|
array container for efficient storage of N-dimensional packed arrays (**ND-arrays**); it also adds four new type markers -
|
||||||
`[u] - uint16`, `[m] - uint32`, `[M] - uint64`, `[h] - float16` and `[B] - byte` - to unambiguously map common binary
|
`[u] - uint16`, `[m] - uint32`, `[M] - uint64`, and `[h] - float16` - to unambiguously map common binary numeric types
|
||||||
numeric types; furthermore, it uses little-endian (LE) to store all numerics instead of big-endian (BE) as in UBJSON to
|
(Draft 3 adds `[B] - byte`); furthermore, it uses little-endian (LE) to store all numerics instead of big-endian (BE) as in UBJSON to
|
||||||
avoid unnecessary conversions on commonly available platforms.
|
avoid unnecessary conversions on commonly available platforms.
|
||||||
|
|
||||||
Compared to other binary JSON-like formats such as MessagePack and CBOR, both BJData and UBJSON demonstrate a rare
|
Compared to other binary JSON-like formats such as MessagePack and CBOR, both BJData and UBJSON demonstrate a rare
|
||||||
@@ -15,11 +15,30 @@ simple processing.
|
|||||||
|
|
||||||
!!! abstract "References"
|
!!! abstract "References"
|
||||||
|
|
||||||
- [BJData Specification](https://neurojson.org/bjdata/draft2)
|
- [BJData Specification (Draft 3)](https://neurojson.org/bjdata/draft3)
|
||||||
|
- [BJData Specification (Draft 2)](https://neurojson.org/bjdata/draft2)
|
||||||
|
|
||||||
|
!!! info "Supported drafts"
|
||||||
|
|
||||||
|
The library supports BJData Draft 2 (the default) and Draft 3, except for column-major N-dimensional arrays (see
|
||||||
|
below). The draft used for writing is selected with the
|
||||||
|
[`version` parameter](../../api/basic_json/bjdata_version_t.md) of
|
||||||
|
[`to_bjdata`](../../api/basic_json/to_bjdata.md); [`from_bjdata`](../../api/basic_json/from_bjdata.md) has no such
|
||||||
|
parameter and reads the markers of both drafts.
|
||||||
|
|
||||||
|
!!! warning "Draft 4 and column-major ND-arrays are not supported"
|
||||||
|
|
||||||
|
BJData Draft 4 (April 2026) adds structure-of-arrays (SoA) containers (`[$` or `{$` followed by a schema object)
|
||||||
|
and the extension type `E`. `from_bjdata` does not implement them and throws
|
||||||
|
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) for such input; `to_bjdata` never writes
|
||||||
|
them.
|
||||||
|
|
||||||
|
Likewise, column-major ND-arrays of Draft 3 (the `#` marker followed by an array that wraps the dimension vector)
|
||||||
|
are rejected with [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
|
||||||
|
|
||||||
## Byte layout
|
## Byte layout
|
||||||
|
|
||||||
BJData follows the structure of UBJSON: a one-byte, human-readable ASCII *type marker* followed by the payload of that type, with strings prefixed by a length that is itself an integer value. In contrast to UBJSON, numbers are stored in little-endian byte order, and additional markers cover unsigned 16/32/64-bit integers, half-precision floats, and bytes. Containers can be written unoptimized (opened with `[` or `{`, closed with `]` or `}`) or optimized with a declared element type (`$`) and/or count (`#`). BJData extends the optimized form: the count can be a list of dimensions, which stores an N-dimensional packed array as a flat sequence of values without any per-element markers.
|
BJData follows the structure of UBJSON: a one-byte, human-readable ASCII *type marker* followed by the payload of that type, with strings prefixed by a length that is itself an integer value. In contrast to UBJSON, numbers are stored in little-endian byte order, and additional markers cover unsigned 16/32/64-bit integers, half-precision floats, and bytes. Containers can be written unoptimized (opened with `[` or `{`, closed with `]` or `}`) or optimized with a count (`#`), optionally preceded by a declared element type (`$`). BJData extends the optimized form: the count can be a list of dimensions, which stores an N-dimensional packed array as a flat sequence of values without any per-element markers.
|
||||||
|
|
||||||
Consider the following JSON value (shown formatted for readability):
|
Consider the following JSON value (shown formatted for readability):
|
||||||
|
|
||||||
@@ -54,35 +73,35 @@ Consider the following JSON value (shown formatted for readability):
|
|||||||
|
|
||||||
The library uses the following mapping from JSON values types to BJData types according to the BJData specification:
|
The library uses the following mapping from JSON values types to BJData types according to the BJData specification:
|
||||||
|
|
||||||
| JSON value type | value/range | BJData type | marker |
|
| JSON value type | value/range | BJData type | marker |
|
||||||
|-----------------|-------------------------------------------|----------------|--------|
|
|-----------------|-------------------------------------------|-------------|-------------------------------------------------------------------------|
|
||||||
| null | `null` | null | `Z` |
|
| null | `null` | null | `Z` |
|
||||||
| boolean | `true` | true | `T` |
|
| boolean | `true` | true | `T` |
|
||||||
| boolean | `false` | false | `F` |
|
| boolean | `false` | false | `F` |
|
||||||
| number_integer | -9223372036854775808..-2147483649 | int64 | `L` |
|
| number_integer | -9223372036854775808..-2147483649 | int64 | `L` |
|
||||||
| number_integer | -2147483648..-32769 | int32 | `l` |
|
| number_integer | -2147483648..-32769 | int32 | `l` |
|
||||||
| number_integer | -32768..-129 | int16 | `I` |
|
| number_integer | -32768..-129 | int16 | `I` |
|
||||||
| number_integer | -128..127 | int8 | `i` |
|
| number_integer | -128..127 | int8 | `i` |
|
||||||
| number_integer | 128..255 | uint8 | `U` |
|
| number_integer | 128..255 | uint8 | `U` |
|
||||||
| number_integer | 256..32767 | int16 | `I` |
|
| number_integer | 256..32767 | int16 | `I` |
|
||||||
| number_integer | 32768..65535 | uint16 | `u` |
|
| number_integer | 32768..65535 | uint16 | `u` |
|
||||||
| number_integer | 65536..2147483647 | int32 | `l` |
|
| number_integer | 65536..2147483647 | int32 | `l` |
|
||||||
| number_integer | 2147483648..4294967295 | uint32 | `m` |
|
| number_integer | 2147483648..4294967295 | uint32 | `m` |
|
||||||
| number_integer | 4294967296..9223372036854775807 | int64 | `L` |
|
| number_integer | 4294967296..9223372036854775807 | int64 | `L` |
|
||||||
| number_integer | 9223372036854775808..18446744073709551615 | uint64 | `M` |
|
| number_integer | 9223372036854775808..18446744073709551615 | uint64 | `M` |
|
||||||
| number_unsigned | 0..127 | int8 | `i` |
|
| number_unsigned | 0..127 | int8 | `i` |
|
||||||
| number_unsigned | 128..255 | uint8 | `U` |
|
| number_unsigned | 128..255 | uint8 | `U` |
|
||||||
| number_unsigned | 256..32767 | int16 | `I` |
|
| number_unsigned | 256..32767 | int16 | `I` |
|
||||||
| number_unsigned | 32768..65535 | uint16 | `u` |
|
| number_unsigned | 32768..65535 | uint16 | `u` |
|
||||||
| number_unsigned | 65536..2147483647 | int32 | `l` |
|
| number_unsigned | 65536..2147483647 | int32 | `l` |
|
||||||
| number_unsigned | 2147483648..4294967295 | uint32 | `m` |
|
| number_unsigned | 2147483648..4294967295 | uint32 | `m` |
|
||||||
| number_unsigned | 4294967296..9223372036854775807 | int64 | `L` |
|
| number_unsigned | 4294967296..9223372036854775807 | int64 | `L` |
|
||||||
| number_unsigned | 9223372036854775808..18446744073709551615 | uint64 | `M` |
|
| number_unsigned | 9223372036854775808..18446744073709551615 | uint64 | `M` |
|
||||||
| number_float | *any value* | float64 | `D` |
|
| number_float | *any value* | float64 | `D` |
|
||||||
| string | *with shortest length indicator* | string | `S` |
|
| string | *with shortest length indicator* | string | `S` |
|
||||||
| array | *see notes on optimized format/ND-array* | array | `[` |
|
| array | *see notes on optimized format/ND-array* | array | `[` |
|
||||||
| object | *see notes on optimized format* | map | `{` |
|
| object | *see notes on optimized format* | map | `{` |
|
||||||
| binary | *see notes on binary values* | array | `[$B` |
|
| binary | *see notes on binary values* | array | `[$B` (Draft 3 with `use_size` and `use_type`), `[U`, or `[B` otherwise |
|
||||||
|
|
||||||
!!! success "Complete mapping"
|
!!! success "Complete mapping"
|
||||||
|
|
||||||
@@ -107,10 +126,16 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
|
|
||||||
!!! info "Unused BJData markers"
|
!!! info "Unused BJData markers"
|
||||||
|
|
||||||
The following markers are not used in the conversion:
|
The following markers are not written:
|
||||||
|
|
||||||
- `Z`: no-op values are not created.
|
- `N`: no-op values are not created (they are skipped when reading).
|
||||||
- `C`: single-byte strings are serialized with `S` markers.
|
- `C`: single-character strings are serialized with `S` markers (`C` is read, and written for the elements of
|
||||||
|
ND-arrays with `"_ArrayType_": "char"`).
|
||||||
|
- `h`: floating-point numbers are written as `D`, not as float16 (`h` is read).
|
||||||
|
- `B`: only written in Draft 3 mode.
|
||||||
|
- `d` is only written for `single` ND-arrays or if `number_float_t` is `float`; `H` is only written for integers
|
||||||
|
wider than 64 bits (custom `number_integer_t`/`number_unsigned_t`). An unsigned integer above 9223372036854775807
|
||||||
|
is written as `M`.
|
||||||
|
|
||||||
!!! info "NaN/infinity handling"
|
!!! info "NaN/infinity handling"
|
||||||
|
|
||||||
@@ -142,7 +167,7 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
BJData extends UBJSON's optimized array **size** marker to support ND-arrays of uniform numerical data types
|
BJData extends UBJSON's optimized array **size** marker to support ND-arrays of uniform numerical data types
|
||||||
(referred to as *packed arrays*). For example, the 2-D `uint8` integer array `[[1,2],[3,4],[5,6]]`, stored as nested
|
(referred to as *packed arrays*). For example, the 2-D `uint8` integer array `[[1,2],[3,4],[5,6]]`, stored as nested
|
||||||
optimized array in UBJSON `[ [$U#i2 1 2 [$U#i2 3 4 [$U#i2 5 6 ]`, can be further compressed in BJData to
|
optimized array in UBJSON `[ [$U#i2 1 2 [$U#i2 3 4 [$U#i2 5 6 ]`, can be further compressed in BJData to
|
||||||
`[$U#[$i#i2 2 3 1 2 3 4 5 6` or `[$U#[i2 i3] 1 2 3 4 5 6`.
|
`[$U#[$i#i2 3 2 1 2 3 4 5 6` or `[$U#[i3 i2] 1 2 3 4 5 6`.
|
||||||
|
|
||||||
To maintain type and size information, ND-arrays are converted to JSON objects following the **annotated array
|
To maintain type and size information, ND-arrays are converted to JSON objects following the **annotated array
|
||||||
format** (defined in the [JData specification (Draft 3)][JDataAAFmt]), when parsed using
|
format** (defined in the [JData specification (Draft 3)][JDataAAFmt]), when parsed using
|
||||||
@@ -152,11 +177,14 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"_ArrayType_": "uint8",
|
"_ArrayType_": "uint8",
|
||||||
"_ArraySize_": [2,3],
|
"_ArraySize_": [3,2],
|
||||||
"_ArrayData_": [1,2,3,4,5,6]
|
"_ArrayData_": [1,2,3,4,5,6]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
ND-arrays with the element type `h` (float16) are returned with `"_ArrayType_": "half"`. `to_bjdata` does not pack
|
||||||
|
`"half"` objects; they are written as regular objects, which read back as an equal value.
|
||||||
|
|
||||||
Likewise, when a JSON object in the above form is serialized using
|
Likewise, when a JSON object in the above form is serialized using
|
||||||
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array.
|
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array.
|
||||||
|
|
||||||
@@ -167,34 +195,40 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
object; otherwise it is serialized as a regular JSON object, so the annotation is never lost in a round trip. This requires
|
object; otherwise it is serialized as a regular JSON object, so the annotation is never lost in a round trip. This requires
|
||||||
all of the following:
|
all of the following:
|
||||||
|
|
||||||
|
- the object has exactly the three keys `"_ArrayType_"`, `"_ArraySize_"`, and `"_ArrayData_"` (any other key, e.g.,
|
||||||
|
`"_ArrayOrder_"`, makes it a regular object),
|
||||||
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
|
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
|
||||||
`double`, `char`, or `byte`,
|
`double`, `char`, or (only with `version = bjdata_version_t::draft3`, since the `B` marker is not part of Draft 2)
|
||||||
|
`byte`; the names are case-sensitive,
|
||||||
- `"_ArraySize_"` is an array, since the dimensions are written as the ND-array header's length,
|
- `"_ArraySize_"` is an array, since the dimensions are written as the ND-array header's length,
|
||||||
- `"_ArraySize_"` has at least two entries and is not a 1×N row vector (first entry 1), since other shapes are
|
- `"_ArraySize_"` has at least two entries and is not a two-entry row vector (`[1,N]`), since other shapes are
|
||||||
parsed back as a regular array,
|
parsed back as a regular array (`[1,2,2]` is packed, `[1,4]` is not),
|
||||||
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
|
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
|
||||||
- `"_ArrayData_"` is an array holding exactly that many elements, and
|
- `"_ArrayData_"` is an array holding exactly that many elements, and
|
||||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"`: for the integer types, a
|
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"`: for the integer types, an
|
||||||
value that fits the named width; for `double`, any value; for `single`, a value that survives narrowing to
|
integer (a floating-point number such as `1.0` does not qualify) that fits the named width (for `char`, 0..127);
|
||||||
`float` and back without change (for instance, `0.1` does not, since it is not exactly representable as
|
for `double`, a floating-point number (an integer such as `1` does not qualify); for `single`, a floating-point
|
||||||
`float`).
|
number that survives narrowing to `float` and back without change (for instance, `0.1` does not, since it is not
|
||||||
|
exactly representable as `float`).
|
||||||
|
|
||||||
An annotated object is always read back with its keys in the order shown above, `"_ArrayType_"`, `"_ArraySize_"`,
|
With `ordered_json`, an annotated object read from BJData has its keys in the order `"_ArrayType_"`,
|
||||||
`"_ArrayData_"`, regardless of the order the ND-array's header stores them in on the wire. This matters for
|
`"_ArraySize_"`, `"_ArrayData_"`; with `json`, they are sorted like those of any other object.
|
||||||
`ordered_json`, whose comparison takes key order into account.
|
|
||||||
|
|
||||||
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
||||||
array input to a BJData ND-array.
|
array input to a BJData ND-array.
|
||||||
|
|
||||||
[JDataAAFmt]: https://github.com/NeuroJSON/jdata/blob/master/JData_specification.md#annotated-storage-of-n-d-arrays
|
[JDataAAFmt]: https://github.com/NeuroJSON/jdata/blob/Draft-3/JData_specification.md#annotated-storage-of-n-d-arrays
|
||||||
|
|
||||||
!!! info "Restrictions in optimized data types for arrays and objects"
|
!!! info "Restrictions in optimized data types for arrays and objects"
|
||||||
|
|
||||||
Due to diminished space saving, hampered readability, and increased security risks, in BJData, the allowed data
|
Due to diminished space saving, hampered readability, and increased security risks, in BJData, the
|
||||||
types following the `$` marker in an optimized array and object container are restricted to
|
allowed data types following the `$` marker in an optimized array and object container are restricted to
|
||||||
**non-zero-fixed-length** data types. Therefore, the valid optimized type markers can only be one of
|
**non-zero-fixed-length** data types. Therefore, the valid optimized type markers can only be one of
|
||||||
`UiuImlMLhdDCB`. This also means other variable (`[{SH`) or zero-length types (`TFN`) can not be used in an
|
`UiuImlMLhdDC`, plus `B` since Draft 3 (the reader accepts `B` in both drafts). This also means other variable (`[{SH`) or zero-length types (`TFZN`) can not be used in an
|
||||||
optimized array or object in BJData.
|
optimized array or object in BJData. The library enforces this restriction for both drafts. A `C` (char) element above 127 is rejected on read with
|
||||||
|
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113), in 1-D and in N-dimensional arrays. With `use_type`, the
|
||||||
|
elements must have the same marker (`[1,200,3]` is not optimized, since it mixes `i` and `U`); booleans, null,
|
||||||
|
strings, arrays, and objects are never optimized with `$`.
|
||||||
|
|
||||||
!!! info "Binary values"
|
!!! info "Binary values"
|
||||||
|
|
||||||
@@ -202,14 +236,16 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
in optimized arrays to designate binary data. This means that, unlike UBJSON, binary data can be both serialized and
|
in optimized arrays to designate binary data. This means that, unlike UBJSON, binary data can be both serialized and
|
||||||
deserialized.
|
deserialized.
|
||||||
|
|
||||||
To preserve compatibility with BJData Draft 2, the Draft 3 optimized binary array must be explicitly enabled using
|
To preserve compatibility with BJData Draft 2, the Draft 3 optimized binary array must be explicitly enabled: pass
|
||||||
the `version` parameter of [`to_bjdata`](../../api/basic_json/to_bjdata.md).
|
`version = bjdata_version_t::draft3` together with `use_size = true` and `use_type = true` to
|
||||||
|
[`to_bjdata`](../../api/basic_json/to_bjdata.md). With Draft 3 and only `use_size` (or neither), a binary value is
|
||||||
|
written as an array of `B` elements.
|
||||||
|
|
||||||
In Draft2 mode (default), if the JSON data contains the binary type, the value stored as a list of integers, as
|
In Draft 2 mode (default), if the JSON data contains the binary type, the value is stored as a list of integers, as
|
||||||
suggested by the BJData documentation. In particular, this means that the serialization and the deserialization of
|
suggested by the BJData documentation. In particular, this means that the serialization and the deserialization of
|
||||||
JSON containing binary values into BJData and back will result in a different JSON object.
|
JSON containing binary values into BJData and back will result in a different JSON object.
|
||||||
|
|
||||||
[BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md#optimized-binary-array
|
[BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/Draft-3/Binary_JData_Specification.md#optimized-binary-array
|
||||||
|
|
||||||
??? example "Example: serialize JSON values to BJData, with and without size/type optimization"
|
??? example "Example: serialize JSON values to BJData, with and without size/type optimization"
|
||||||
|
|
||||||
@@ -227,34 +263,46 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
|
|
||||||
The library maps BJData types to JSON value types as follows:
|
The library maps BJData types to JSON value types as follows:
|
||||||
|
|
||||||
| BJData type | JSON value type | marker |
|
| BJData type | JSON value type | marker |
|
||||||
|-------------|------------------------------------------|----------|
|
|-----------------------|--------------------------------------------------|----------|
|
||||||
| no-op | *no value, next value is read* | `N` |
|
| no-op | *no value, next value is read* | `N` |
|
||||||
| null | `null` | `Z` |
|
| null | `null` | `Z` |
|
||||||
| false | `false` | `F` |
|
| false | `false` | `F` |
|
||||||
| true | `true` | `T` |
|
| true | `true` | `T` |
|
||||||
| float16 | number_float | `h` |
|
| float16 | number_float | `h` |
|
||||||
| float32 | number_float | `d` |
|
| float32 | number_float | `d` |
|
||||||
| float64 | number_float | `D` |
|
| float64 | number_float | `D` |
|
||||||
| uint8 | number_unsigned | `U` |
|
| high-precision number | number_integer, number_unsigned, or number_float | `H` |
|
||||||
| int8 | number_integer | `i` |
|
| uint8 | number_unsigned | `U` |
|
||||||
| uint16 | number_unsigned | `u` |
|
| int8 | number_integer | `i` |
|
||||||
| int16 | number_integer | `I` |
|
| uint16 | number_unsigned | `u` |
|
||||||
| uint32 | number_unsigned | `m` |
|
| int16 | number_integer | `I` |
|
||||||
| int32 | number_integer | `l` |
|
| uint32 | number_unsigned | `m` |
|
||||||
| uint64 | number_unsigned | `M` |
|
| int32 | number_integer | `l` |
|
||||||
| int64 | number_integer | `L` |
|
| uint64 | number_unsigned | `M` |
|
||||||
| byte | number_unsigned | `B` |
|
| int64 | number_integer | `L` |
|
||||||
| string | string | `S` |
|
| byte | number_unsigned | `B` |
|
||||||
| char | string | `C` |
|
| string | string | `S` |
|
||||||
| array | array (optimized values are supported) | `[` |
|
| char | string | `C` |
|
||||||
| ND-array | object (in JData annotated array format) | `[$.#[.` |
|
| array | array (optimized values are supported) | `[` |
|
||||||
| object | object (optimized values are supported) | `{` |
|
| ND-array | object (in JData annotated array format) | `[$.#[.` |
|
||||||
| binary | binary (strongly-typed byte array) | `[$B` |
|
| object | object (optimized values are supported) | `{` |
|
||||||
|
| binary | binary (strongly-typed byte array) | `[$B` |
|
||||||
|
|
||||||
!!! success "Complete mapping"
|
!!! success "Complete mapping"
|
||||||
|
|
||||||
The mapping is **complete** in the sense that any BJData value can be converted to a JSON value.
|
The mapping is **complete** for the BJData types of Draft 2 and Draft 3 with one exception: column-major
|
||||||
|
N-dimensional arrays are rejected with [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
|
||||||
|
The Draft 4 constructs (structure-of-arrays containers and the extension type `E`) are not supported either.
|
||||||
|
|
||||||
|
The digits of an `H` value are parsed like a JSON number: integers that fit 64 bits become `number_integer` or
|
||||||
|
`number_unsigned`, anything else a `number_float`; invalid text throws
|
||||||
|
[`parse_error.115`](../../home/exceptions.md#jsonexceptionparse_error115).
|
||||||
|
|
||||||
|
!!! info "Duplicate object keys"
|
||||||
|
|
||||||
|
If an object contains the same key more than once, `from_bjdata` accepts it and keeps the last value; with
|
||||||
|
`ordered_json`, the key keeps the position of its first occurrence.
|
||||||
|
|
||||||
!!! warning "Ill-formed UTF-8 in string values and object keys"
|
!!! warning "Ill-formed UTF-8 in string values and object keys"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# BON8
|
# BON8
|
||||||
|
|
||||||
BON8 (Binary Object Notation 8) is a compact binary serialization format for JSON values. It uses the byte values that
|
BON8 (Binary Object Notation 8) is a compact binary serialization format for JSON values. It uses byte values that
|
||||||
cannot begin a UTF-8 character as type markers, so strings are stored as plain UTF-8 without a length prefix: a string
|
cannot begin a UTF-8 character as type markers, so strings are stored as plain UTF-8 without a length prefix: a string
|
||||||
ends at the first byte that cannot continue it. Integers from -10 to 39, `true`, `false`, `null`, and the floating-point
|
ends at the first byte that cannot continue it. Integers from -10 to 39, `true`, `false`, `null`, and the floating-point
|
||||||
values -1.0, 0.0, and 1.0 take a single byte, and arrays and objects with up to four elements need no terminator.
|
values -1.0, 0.0, and 1.0 take a single byte, and arrays and objects with up to four elements need no terminator.
|
||||||
@@ -12,7 +12,7 @@ values -1.0, 0.0, and 1.0 take a single byte, and arrays and objects with up to
|
|||||||
|
|
||||||
## Byte layout
|
## Byte layout
|
||||||
|
|
||||||
BON8 exploits the fact that some byte values can never start a UTF-8 character. These bytes serve as *type markers*, so strings can be stored as plain UTF-8 without a length prefix: a string ends at the first byte that cannot continue it (often the marker of the next value). Small integers, `true`, `false`, `null`, and a few common floats are a single byte. Arrays and objects are introduced by a marker; those with up to four elements encode their size in the marker and need no terminator, while larger ones are closed by the end marker `0xFE`.
|
BON8 exploits the fact that some byte values can never start a UTF-8 character. These bytes serve as *type markers*, so strings can be stored as plain UTF-8 without a length prefix: a string ends at the first byte that cannot continue it: a marker byte, or a lead byte 0xC2..0xF7 followed by a byte outside 0x80..0xBF. The ending byte is often the marker of the next value. Small integers, `true`, `false`, `null`, and a few common floats are a single byte. Arrays and objects are introduced by a marker; those with up to four elements encode their size in the marker and need no terminator, while larger ones are closed by the end marker `0xFE`.
|
||||||
|
|
||||||
Consider the following JSON value (shown formatted for readability):
|
Consider the following JSON value (shown formatted for readability):
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ Consider the following JSON value (shown formatted for readability):
|
|||||||
<div class="byte-layout" markdown="0"><table>
|
<div class="byte-layout" markdown="0"><table>
|
||||||
<thead><tr><th>JSON</th><th>Bytes</th><th>Meaning</th></tr></thead><tbody>
|
<thead><tr><th>JSON</th><th>Bytes</th><th>Meaning</th></tr></thead><tbody>
|
||||||
<tr><td><code>{</code></td><td><span class="bl bl-t">8A</span></td><td>object with 4 members</td></tr>
|
<tr><td><code>{</code></td><td><span class="bl bl-t">8A</span></td><td>object with 4 members</td></tr>
|
||||||
<tr><td><code>"id": 300,</code></td><td><span class="bl bl-d">69 64</span> <span class="bl bl-t">C4</span> <span class="bl bl-d">04</span></td><td>key: UTF-8 bytes; the string ends at the next byte, which cannot continue a UTF-8 character; value: 2-byte positive integer (lead byte, then a byte below 0x80)</td></tr>
|
<tr><td><code>"id": 300,</code></td><td><span class="bl bl-d">69 64</span> <span class="bl bl-t">C4</span> <span class="bl bl-d">04</span></td><td>key: UTF-8 bytes; the string ends at 0xC4, because the byte after it (0x04) is not a UTF-8 continuation byte, so 0xC4 starts an integer; value: 2-byte positive integer (lead byte, then a byte below 0x80)</td></tr>
|
||||||
<tr><td><code>"name": "json",</code></td><td><span class="bl bl-d">6E 61 6D 65</span> <span class="bl bl-l">FF</span> <span class="bl bl-d">6A 73 6F 6E</span> <span class="bl bl-l">FF</span></td><td>key: UTF-8 bytes; 0xFF terminates the string as another string follows; value: UTF-8 bytes; terminated as a string key follows</td></tr>
|
<tr><td><code>"name": "json",</code></td><td><span class="bl bl-d">6E 61 6D 65</span> <span class="bl bl-l">FF</span> <span class="bl bl-d">6A 73 6F 6E</span> <span class="bl bl-l">FF</span></td><td>key: UTF-8 bytes; 0xFF terminates the string as another string follows; value: UTF-8 bytes; terminated as a string key follows</td></tr>
|
||||||
<tr><td><code>"pi": 3.14,</code></td><td><span class="bl bl-d">70 69</span> <span class="bl bl-t">8F</span> <span class="bl bl-d">40 09 1E B8 51 EB 85 1F</span></td><td>key: UTF-8 bytes; value: binary64 (IEEE 754, big-endian); its marker also ends <code>"pi"</code></td></tr>
|
<tr><td><code>"pi": 3.14,</code></td><td><span class="bl bl-d">70 69</span> <span class="bl bl-t">8F</span> <span class="bl bl-d">40 09 1E B8 51 EB 85 1F</span></td><td>key: UTF-8 bytes; value: binary64 (IEEE 754, big-endian); its marker also ends <code>"pi"</code></td></tr>
|
||||||
<tr><td><code>"tags": [</code></td><td><span class="bl bl-d">74 61 67 73</span> <span class="bl bl-t">82</span></td><td>key: UTF-8 bytes; value: array with 2 elements; its marker also ends <code>"tags"</code></td></tr>
|
<tr><td><code>"tags": [</code></td><td><span class="bl bl-d">74 61 67 73</span> <span class="bl bl-t">82</span></td><td>key: UTF-8 bytes; value: array with 2 elements; its marker also ends <code>"tags"</code></td></tr>
|
||||||
@@ -100,7 +100,8 @@ serialized as 0x81 0x65 0xFF, but `[1,2,3,4,"e"]` as 0x85 0x91 0x92 0x93 0x94 0x
|
|||||||
|
|
||||||
The following values can **not** be converted to a BON8 value:
|
The following values can **not** be converted to a BON8 value:
|
||||||
|
|
||||||
- unsigned integers above 9223372036854775807, because BON8 has no unsigned 64-bit integer type
|
- integers outside the int64 range (unsigned integers above 9223372036854775807 and, for number types wider than
|
||||||
|
64 bits, any larger or smaller integer), because BON8 integers are limited to int64
|
||||||
([out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407))
|
([out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407))
|
||||||
- strings that are not valid UTF-8, because the end of a string is determined from its encoding
|
- strings that are not valid UTF-8, because the end of a string is determined from its encoding
|
||||||
([type_error.316](../../home/exceptions.md#jsonexceptiontype_error316))
|
([type_error.316](../../home/exceptions.md#jsonexceptiontype_error316))
|
||||||
@@ -168,7 +169,13 @@ The library maps BON8 types to JSON value types as follows:
|
|||||||
|
|
||||||
Non-negative integers are read as number_unsigned, negative integers as number_integer.
|
Non-negative integers are read as number_unsigned, negative integers as number_integer.
|
||||||
|
|
||||||
!!! info
|
A byte 0xF5..0xF7 followed by 0x80..0xBF is neither a string nor an integer and is rejected with
|
||||||
|
[parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) (invalid UTF-8 byte). The same error is
|
||||||
|
reported for any other invalid byte (such as a stray 0xFE), for ill-formed UTF-8, and for an object key that is not a
|
||||||
|
string. Input that ends prematurely, trailing data after the value, and a missing final 0xFF after a string at the end
|
||||||
|
of the message are reported as [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110).
|
||||||
|
|
||||||
|
!!! info "Non-canonical input"
|
||||||
|
|
||||||
Values that do not use the canonical representation, such as integers with a longer encoding than necessary,
|
Values that do not use the canonical representation, such as integers with a longer encoding than necessary,
|
||||||
arrays and objects with up to four elements that are terminated by 0xFE, unsorted object keys, or a 0xFF after a
|
arrays and objects with up to four elements that are terminated by 0xFE, unsorted object keys, or a 0xFF after a
|
||||||
@@ -176,7 +183,10 @@ Non-negative integers are read as number_unsigned, negative integers as number_i
|
|||||||
|
|
||||||
Strings must be valid UTF-8, and a string at the very end of a message must be terminated by 0xFF.
|
Strings must be valid UTF-8, and a string at the very end of a message must be terminated by 0xFF.
|
||||||
|
|
||||||
!!! info
|
Duplicate object keys are accepted; the last value wins (`ordered_json` keeps the position of the first occurrence).
|
||||||
|
The specification does not say how to treat them; the HikoGUI reference decoder keeps the first value.
|
||||||
|
|
||||||
|
!!! info "Round trips"
|
||||||
|
|
||||||
Any BON8 output created by `to_bon8` can be successfully parsed by `from_bon8`.
|
Any BON8 output created by `to_bon8` can be successfully parsed by `from_bon8`.
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,12 @@ The library uses the following mapping from JSON values types to BSON types:
|
|||||||
!!! warning "Incomplete mapping"
|
!!! warning "Incomplete mapping"
|
||||||
|
|
||||||
The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON.
|
The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON.
|
||||||
Also, keys may not contain U+0000, since they are serialized a zero-terminated c-strings.
|
Also, keys may not contain U+0000, since they are serialized as zero-terminated C strings.
|
||||||
|
|
||||||
|
!!! info "Order of object members"
|
||||||
|
|
||||||
|
The members of an object are written in the order in which iteration visits them: sorted by key for
|
||||||
|
[`json`](../../api/json.md), in insertion order for [`ordered_json`](../../api/ordered_json.md).
|
||||||
|
|
||||||
!!! warning "BSON type 0x11 interoperability"
|
!!! warning "BSON type 0x11 interoperability"
|
||||||
|
|
||||||
@@ -81,6 +86,19 @@ The library uses the following mapping from JSON values types to BSON types:
|
|||||||
As a result, serializing and deserializing a JSON object containing such a value produces a different JSON object,
|
As a result, serializing and deserializing a JSON object containing such a value produces a different JSON object,
|
||||||
even though the binary data is unchanged.
|
even though the binary data is unchanged.
|
||||||
|
|
||||||
|
!!! info "Binary subtype 0x02 (old binary)"
|
||||||
|
|
||||||
|
The BSON specification defines the payload of the "binary (old)" subtype `0x02` as an int32 followed by that many
|
||||||
|
bytes, so the value is preceded by two lengths: the length of the binary element and, as the first four bytes of
|
||||||
|
the payload, the number of bytes that follow. A binary value with subtype `2` is therefore written with the outer
|
||||||
|
length 4 bytes larger than its size and the size as inner length: `binary({1, 2, 3}, 2)` becomes
|
||||||
|
`07 00 00 00 02 03 00 00 00 01 02 03`. When reading, the inner length is checked and removed, so the value
|
||||||
|
contains only the bytes that follow it. `from_bson()` throws
|
||||||
|
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) if the outer length is less than 4 or the
|
||||||
|
inner length is not the outer length minus 4. Versions before 3.13.0 did not write the inner length and returned
|
||||||
|
it as part of the payload; BSON data written by those versions with subtype 2 is not readable any more and must be
|
||||||
|
written again.
|
||||||
|
|
||||||
??? example "Example: serialize a JSON value to BSON"
|
??? example "Example: serialize a JSON value to BSON"
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@@ -135,12 +153,29 @@ The library maps BSON record types to JSON value types as follows:
|
|||||||
The BSON reader is lenient in a few areas where the BSON specification is more restrictive:
|
The BSON reader is lenient in a few areas where the BSON specification is more restrictive:
|
||||||
|
|
||||||
- array element keys are not checked against the required decimal sequence (`0`, `1`, `2`, ...),
|
- array element keys are not checked against the required decimal sequence (`0`, `1`, `2`, ...),
|
||||||
- any non-zero byte is accepted as `true` for the boolean type, and
|
- any non-zero byte is accepted as `true` for the boolean type,
|
||||||
- the payload for binary subtype `0x02` is returned as-is, including its inner length prefix.
|
- the payloads of the binary subtypes `0x03` (UUID, old), `0x04` (UUID), and `0x05` (MD5) are not checked to have
|
||||||
|
16 bytes,
|
||||||
|
- duplicate keys within a document are accepted; the last value wins (`{"a": 1, "a": 2}` is read as `{"a": 2}`),
|
||||||
|
while a SAX handler receives every occurrence,
|
||||||
|
- a `string` value may contain embedded `0x00` bytes, and
|
||||||
|
- with `strict` set to `false`, bytes after the end of the document are ignored.
|
||||||
|
|
||||||
If BSON input must be validated for strict specification compliance, validate it separately before passing it to
|
If BSON input must be validated for strict specification compliance, validate it separately before passing it to
|
||||||
`from_bson()`.
|
`from_bson()`.
|
||||||
|
|
||||||
|
!!! note "Document size"
|
||||||
|
|
||||||
|
Every document starts with its size. `from_bson()` rejects a document with
|
||||||
|
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) if the size is less than 5 (the size prefix and the terminator), if an embedded document does not end before the
|
||||||
|
end of the document that contains it, if the elements do not fit into the declared size, or if the size does not
|
||||||
|
match the number of bytes read when the terminating `0x00` byte is reached. A size that is too small is detected
|
||||||
|
before an element is read whose type byte and name no longer fit; an element that starts inside the declared size
|
||||||
|
but extends past it is read, and reported to a SAX handler, before the mismatch is found at the terminator. A size
|
||||||
|
that is too large is likewise found only at the terminator. Passing `strict` as `false` skips only the check for bytes after
|
||||||
|
the end of the document ([`parse_error.110`](../../home/exceptions.md#jsonexceptionparse_error110)), not the
|
||||||
|
document size checks.
|
||||||
|
|
||||||
!!! warning "Ill-formed UTF-8 in string values"
|
!!! warning "Ill-formed UTF-8 in string values"
|
||||||
|
|
||||||
The BSON specification requires `string` values (type `0x02`) to be valid UTF-8, but this is not required of a
|
The BSON specification requires `string` values (type `0x02`) to be valid UTF-8, but this is not required of a
|
||||||
@@ -154,8 +189,9 @@ The library maps BSON record types to JSON value types as follows:
|
|||||||
ill-formed bytes. `to_bson()`'s own `error_handler` parameter defaults to `keep`, so such a string value or element
|
ill-formed bytes. `to_bson()`'s own `error_handler` parameter defaults to `keep`, so such a string value or element
|
||||||
(key) name is written unchanged; with `strict` (the default if
|
(key) name is written unchanged; with `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception
|
[`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception
|
||||||
instead. Element (key) names are never validated on read, since they are read byte-by-byte as a C string. `binary`
|
instead. Element (key) names are treated like string values: with `keep` they are read unchanged, with `strict`,
|
||||||
values (type `0x05`) are unaffected, since they are not required to hold text.
|
`replace`, or `ignore` they are checked and handled in the same way. `binary` values (type `0x05`) are unaffected,
|
||||||
|
since they are not required to hold text.
|
||||||
|
|
||||||
??? example "Example: deserialize a JSON value from BSON"
|
??? example "Example: deserialize a JSON value from BSON"
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ extremely small code sizes, fairly small message size, and extensibility without
|
|||||||
|
|
||||||
## Byte layout
|
## Byte layout
|
||||||
|
|
||||||
CBOR encodes every data item as a *head* followed by optional content. The first byte of the head holds the *major type* in its upper three bits (unsigned integer, negative integer, byte string, text string, array, map, tag, or simple/float) and an *additional information* value in its lower five bits. Additional information below 24 is the argument itself (for example, a small integer or a short length); the values 24 to 27 announce that the argument follows in 1, 2, 4, or 8 big-endian bytes. Strings, arrays, and maps are prefixed with their length (or count of elements/pairs), and content follows directly, so a reader always knows how many items to expect without a terminator.
|
CBOR encodes every data item as a *head* followed by optional content. The first byte of the head holds the *major type* in its upper three bits (unsigned integer, negative integer, byte string, text string, array, map, tag, or simple/float) and an *additional information* value in its lower five bits. Additional information below 24 is the argument itself (for example, a small integer or a short length); the values 24 to 27 announce that the argument follows in 1, 2, 4, or 8 big-endian bytes. Strings, arrays, and maps are prefixed with their length (or count of elements/pairs), and content follows directly. A reader of definite-length items knows how many items to expect without a terminator; indefinite-length items (additional information 31) are instead closed by the "break" byte 0xFF, which `from_cbor` reads but `to_cbor` never writes.
|
||||||
|
|
||||||
Consider the following JSON value (shown formatted for readability):
|
Consider the following JSON value (shown formatted for readability):
|
||||||
|
|
||||||
@@ -52,16 +52,16 @@ The library uses the following mapping from JSON values types to CBOR types acco
|
|||||||
| null | `null` | Null | 0xF6 |
|
| null | `null` | Null | 0xF6 |
|
||||||
| boolean | `true` | True | 0xF5 |
|
| boolean | `true` | True | 0xF5 |
|
||||||
| boolean | `false` | False | 0xF4 |
|
| boolean | `false` | False | 0xF4 |
|
||||||
| number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B |
|
| number_integer | -9223372036854775808..-4294967297 | Negative integer (8 bytes follow) | 0x3B |
|
||||||
| number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A |
|
| number_integer | -4294967296..-65537 | Negative integer (4 bytes follow) | 0x3A |
|
||||||
| number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 |
|
| number_integer | -65536..-257 | Negative integer (2 bytes follow) | 0x39 |
|
||||||
| number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 |
|
| number_integer | -256..-25 | Negative integer (1 byte follow) | 0x38 |
|
||||||
| number_integer | -24..-1 | Negative integer | 0x20..0x37 |
|
| number_integer | -24..-1 | Negative integer | 0x20..0x37 |
|
||||||
| number_integer | 0..23 | Integer | 0x00..0x17 |
|
| number_integer | 0..23 | Integer | 0x00..0x17 |
|
||||||
| number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 |
|
| number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 |
|
||||||
| number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 |
|
| number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 |
|
||||||
| number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A |
|
| number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A |
|
||||||
| number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B |
|
| number_integer | 4294967296..9223372036854775807 | Unsigned integer (8 bytes follow) | 0x1B |
|
||||||
| number_unsigned | 0..23 | Integer | 0x00..0x17 |
|
| number_unsigned | 0..23 | Integer | 0x00..0x17 |
|
||||||
| number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 |
|
| number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 |
|
||||||
| number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 |
|
| number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 |
|
||||||
@@ -95,7 +95,24 @@ see "binary" cells in the table above.
|
|||||||
|
|
||||||
!!! success "Complete mapping"
|
!!! success "Complete mapping"
|
||||||
|
|
||||||
The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value.
|
The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value, with one
|
||||||
|
exception: an integer outside the range of CBOR integers (only possible with a number type wider than 64 bits)
|
||||||
|
throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407).
|
||||||
|
|
||||||
|
!!! info "Not a deterministic encoding"
|
||||||
|
|
||||||
|
`to_cbor` does not produce the deterministic encoding of
|
||||||
|
[RFC 8949, Section 4.2.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-4.2.1), and not always the preferred
|
||||||
|
serialization of Section 4.1:
|
||||||
|
|
||||||
|
- floating-point numbers are written as single precision (0xFA) when that is exact and as double precision (0xFB)
|
||||||
|
otherwise; half precision (0xF9) is used only for `NaN` and `Infinity`/`-Infinity` (`1.0` is `fa 3f 80 00 00`);
|
||||||
|
- a binary subtype 0..23 is written as `d8 xx` instead of the one-byte tag 0xC0..0xD7 (`d8 01 41 01`);
|
||||||
|
- object members are written in the order of `object_t` (for the default `std::map`: sorted by the key string, not
|
||||||
|
by the encoded key bytes).
|
||||||
|
|
||||||
|
Integers and lengths use the shortest form, and indefinite-length items are never written. Decoders must accept all
|
||||||
|
of this; the RFC does not require the preferred serialization.
|
||||||
|
|
||||||
!!! info "NaN/infinity handling"
|
!!! info "NaN/infinity handling"
|
||||||
|
|
||||||
@@ -105,9 +122,8 @@ see "binary" cells in the table above.
|
|||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
Prior to version 3.13.0, NaN and Infinity were instead serialized as a CBOR double-precision float (type 0xFB,
|
Prior to version 3.8.0, NaN and Infinity were instead serialized as a CBOR double-precision float (type 0xFB,
|
||||||
9 bytes total), because the check used to select a smaller encoding compared magnitudes with NaN, which is
|
9 bytes total).
|
||||||
always `false` and caused the intended half-precision path to be skipped.
|
|
||||||
|
|
||||||
!!! info "Unused CBOR types"
|
!!! info "Unused CBOR types"
|
||||||
|
|
||||||
@@ -124,7 +140,6 @@ see "binary" cells in the table above.
|
|||||||
- expected conversions (0xD5..0xD7)
|
- expected conversions (0xD5..0xD7)
|
||||||
- simple values (0xE0..0xF3, 0xF8)
|
- simple values (0xE0..0xF3, 0xF8)
|
||||||
- undefined (0xF7)
|
- undefined (0xF7)
|
||||||
- half-precision floats (0xF9)
|
|
||||||
- break (0xFF)
|
- break (0xFF)
|
||||||
|
|
||||||
!!! info "Tagged items"
|
!!! info "Tagged items"
|
||||||
@@ -226,10 +241,20 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https://
|
|||||||
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
|
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
|
||||||
general-purpose CBOR library instead.
|
general-purpose CBOR library instead.
|
||||||
|
|
||||||
|
A tag in front of a text string key is handled like a tag in front of a value: it is skipped with
|
||||||
|
`cbor_tag_handler_t::ignore` or `cbor_tag_handler_t::store` and rejected with the default `error`. A tag does not
|
||||||
|
turn a key of another type into a text string.
|
||||||
|
|
||||||
|
A map with duplicate keys is well-formed, but invalid under
|
||||||
|
[RFC 8949, Section 5.3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1). `from_cbor` accepts it and the
|
||||||
|
last value wins; the [SAX interface](../parsing/sax_interface.md) reports every occurrence.
|
||||||
|
|
||||||
!!! warning "Ill-formed UTF-8 in text strings"
|
!!! warning "Ill-formed UTF-8 in text strings"
|
||||||
|
|
||||||
[RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings (major
|
[RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings (major
|
||||||
type 3) to be valid UTF-8, but leaves it up to the decoder whether to enforce this, so checking is opt-in: with the
|
type 3) to be valid UTF-8 (a string with ill-formed UTF-8 is "well-formed but invalid"), and
|
||||||
|
[Section 5.3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1) leaves it up to the decoder whether to
|
||||||
|
verify this, so checking is opt-in: with the
|
||||||
[`error_handler`](../../api/basic_json/from_cbor.md) parameter left at `keep` (the default), `from_cbor()` accepts a
|
[`error_handler`](../../api/basic_json/from_cbor.md) parameter left at `keep` (the default), `from_cbor()` accepts a
|
||||||
text string (object keys included) whose bytes are not valid UTF-8 and hands them back unchanged. Passing
|
text string (object keys included) whose bytes are not valid UTF-8 and hands them back unchanged. Passing
|
||||||
`error_handler_t::strict` makes `from_cbor()` check and throw
|
`error_handler_t::strict` makes `from_cbor()` check and throw
|
||||||
@@ -242,9 +267,14 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https://
|
|||||||
[`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception
|
[`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception
|
||||||
instead. Byte strings (major type 2) are unaffected, since they are not required to hold text.
|
instead. Byte strings (major type 2) are unaffected, since they are not required to hold text.
|
||||||
|
|
||||||
|
The chunks of an indefinite-length text string are checked one by one, as required by
|
||||||
|
[RFC 8949, Section 3.2.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3): a code point cannot be spread
|
||||||
|
over two chunks. With `error_handler_t::strict` such a string throws `parse_error.113`; `replace` and `ignore`
|
||||||
|
sanitize each chunk on its own.
|
||||||
|
|
||||||
!!! warning "Tagged items"
|
!!! 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. 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.
|
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 tag directly in front of it is stored, and only when it has the form 0xD8..0xDB (a tag 0..23 written as 0xC0..0xD7 is skipped); for example, `D8 05 C1 41 AA` yields subtype `null`, whereas `C1 D8 05 41 AA` yields 5. Tags in front of a text string map key are skipped as well. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
|
||||||
|
|
||||||
??? example "Example: deserialize a JSON value from CBOR"
|
??? example "Example: deserialize a JSON value from CBOR"
|
||||||
|
|
||||||
|
|||||||
@@ -16,25 +16,25 @@ to efficiently encode JSON values to byte vectors and to decode such vectors.
|
|||||||
|
|
||||||
### Completeness
|
### Completeness
|
||||||
|
|
||||||
| Format | Serialization | Deserialization |
|
| Format | Serialization | Deserialization |
|
||||||
|-------------|-----------------------------------------------|----------------------------------------------|
|
|-------------|-----------------------------------------------|-----------------------------------------------------------------------|
|
||||||
| BJData | complete | complete |
|
| BJData | complete | incomplete: no column-major ND-arrays, no Draft 4 SoA/extension types |
|
||||||
| BON8 | incomplete: no unsigned integers above int64 | complete |
|
| BON8 | incomplete: no unsigned integers above int64 | complete |
|
||||||
| BSON | incomplete: top-level value must be an object | incomplete, but all JSON types are supported |
|
| BSON | incomplete: top-level value must be an object | incomplete, but all JSON types are supported |
|
||||||
| CBOR | complete | incomplete, but all JSON types are supported |
|
| CBOR | complete | incomplete, but all JSON types are supported |
|
||||||
| MessagePack | complete | complete |
|
| MessagePack | complete | incomplete, but all JSON types are supported |
|
||||||
| UBJSON | complete | complete |
|
| UBJSON | complete | complete |
|
||||||
|
|
||||||
### Binary values
|
### Binary values
|
||||||
|
|
||||||
| Format | Binary values | Binary subtypes |
|
| Format | Binary values | Binary subtypes |
|
||||||
|-------------|---------------|-----------------|
|
|-------------|--------------------------|-----------------|
|
||||||
| BJData | not supported | not supported |
|
| BJData | supported (Draft 3 only) | not supported |
|
||||||
| BON8 | not supported | not supported |
|
| BON8 | not supported | not supported |
|
||||||
| BSON | supported | supported |
|
| BSON | supported | supported |
|
||||||
| CBOR | supported | supported |
|
| CBOR | supported | supported |
|
||||||
| MessagePack | supported | supported |
|
| MessagePack | supported | supported |
|
||||||
| UBJSON | not supported | not supported |
|
| UBJSON | not supported | not supported |
|
||||||
|
|
||||||
See [binary values](../binary_values.md) for more information.
|
See [binary values](../binary_values.md) for more information.
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ extra byte in addition to the strings themselves.
|
|||||||
|
|
||||||
## Byte layout
|
## Byte layout
|
||||||
|
|
||||||
MessagePack encodes each value as a one-byte *format marker*, optionally followed by a length or the payload. The marker range is partitioned to keep common values small: small non-negative integers (0 to 127) and small negative integers (-32 to -1) are the marker byte itself; short strings, arrays, and maps (up to 31 or 15 elements) hide their length in the marker's lower bits. Larger values use dedicated markers that announce the width (8, 16, 32, or 64 bits) of the length or number that follows, in big-endian byte order. Like CBOR, containers are length-prefixed, so no terminator is needed.
|
MessagePack encodes each value as a one-byte *format marker*, optionally followed by a length or the payload. The marker range is partitioned to keep common values small: small non-negative integers (0 to 127) and small negative integers (-32 to -1) are the marker byte itself; short strings, arrays, and maps (up to 31 bytes for strings and up to 15 elements for arrays and maps) hide their length in the marker's lower bits. Larger values use dedicated markers that announce the width (8, 16, 32, or 64 bits) of the length or number that follows, in big-endian byte order. Like CBOR, containers are length-prefixed, so no terminator is needed.
|
||||||
|
|
||||||
Consider the following JSON value (shown formatted for readability):
|
Consider the following JSON value (shown formatted for readability):
|
||||||
|
|
||||||
@@ -109,8 +109,9 @@ specification:
|
|||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
Prior to version 3.13.0, NaN and Infinity were instead serialized as a MessagePack float 64 (type 0xCB, 9 bytes
|
Prior to version 3.13.0, NaN and Infinity were instead serialized as a MessagePack float 64 (type 0xCB, 9 bytes
|
||||||
total), because the check used to select the smaller float 32 encoding compared magnitudes with NaN, which is
|
total), because the check used to select the smaller float 32 encoding failed for NaN (all comparisons are `false`)
|
||||||
always `false` and caused the float 32 path to be skipped.
|
and for Infinity and -Infinity (outside the float range checked against `std::numeric_limits<float>::lowest()` and
|
||||||
|
`max()`), so the float 32 path was skipped.
|
||||||
|
|
||||||
??? example "Example: serialize a JSON value to MessagePack"
|
??? example "Example: serialize a JSON value to MessagePack"
|
||||||
|
|
||||||
@@ -135,6 +136,7 @@ The library maps MessagePack types to JSON value types as follows:
|
|||||||
| fixarray | array | 0x90..0x9F |
|
| fixarray | array | 0x90..0x9F |
|
||||||
| fixstr | string | 0xA0..0xBF |
|
| fixstr | string | 0xA0..0xBF |
|
||||||
| nil | `null` | 0xC0 |
|
| nil | `null` | 0xC0 |
|
||||||
|
| (never used) | – | 0xC1 |
|
||||||
| false | `false` | 0xC2 |
|
| false | `false` | 0xC2 |
|
||||||
| true | `true` | 0xC3 |
|
| true | `true` | 0xC3 |
|
||||||
| float 32 | number_float | 0xCA |
|
| float 32 | number_float | 0xCA |
|
||||||
@@ -167,6 +169,30 @@ The library maps MessagePack types to JSON value types as follows:
|
|||||||
| fixext 16 | binary | 0xD8 |
|
| fixext 16 | binary | 0xD8 |
|
||||||
| negative fixint | number_integer | 0xE0-0xFF |
|
| negative fixint | number_integer | 0xE0-0xFF |
|
||||||
|
|
||||||
|
The byte 0xC1 is reserved as "never used" by the specification. As a value it is rejected with
|
||||||
|
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) (`invalid byte: 0xC1`); as a map key it is
|
||||||
|
rejected with [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) and the generic "expected length
|
||||||
|
specification" message, because the key is read as a string.
|
||||||
|
|
||||||
|
Duplicate keys in a map are accepted; the last value wins.
|
||||||
|
|
||||||
|
### Extension types (ext, fixext)
|
||||||
|
|
||||||
|
MessagePack ext and fixext values are read as binary values, and the ext type byte is stored as the binary subtype.
|
||||||
|
Although the specification defines the type as a signed 8-bit integer (0 to 127 for applications, -128 to -1 reserved,
|
||||||
|
with -1 being the Timestamp extension), the library reads it as an unsigned byte: a negative ext type `n` becomes
|
||||||
|
subtype `256+n`, so ext type -1 is read as subtype 255 and -128 as subtype 128. The Timestamp extension is not
|
||||||
|
interpreted; it is returned as binary with subtype 255. Use
|
||||||
|
[`has_subtype()`](../../api/byte_container_with_subtype/has_subtype.md) to tell a `bin` value (no subtype) from an ext
|
||||||
|
value (subtype, even if it is 0).
|
||||||
|
|
||||||
|
When serializing, a binary value with a subtype is always written as fixext (1, 2, 4, 8, or 16 bytes) or ext, even
|
||||||
|
for subtype 0 (`d6 00 01 02 03 04`); a binary value without a subtype is written as bin. Subtypes 128 to 255 are
|
||||||
|
written as the reserved negative ext types (255 as -1), so `json::binary({1,2,3,4}, 255)` becomes
|
||||||
|
`d6 ff 01 02 03 04`, which other MessagePack libraries decode as a 32-bit timestamp. A subtype above 255 throws
|
||||||
|
[`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415). The payload length is limited to
|
||||||
|
4294967295 bytes like all other lengths.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
||||||
@@ -177,7 +203,8 @@ The library maps MessagePack types to JSON value types as follows:
|
|||||||
JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in 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
|
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
|
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:
|
`allow_exceptions` set to `false`, a discarded value). For most key types the message names the type 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
|
[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
|
||||||
@@ -189,7 +216,7 @@ The library maps MessagePack types to JSON value types as follows:
|
|||||||
!!! warning "Ill-formed UTF-8 in string values"
|
!!! warning "Ill-formed UTF-8 in string values"
|
||||||
|
|
||||||
The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain
|
The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain
|
||||||
a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged.
|
a byte sequence that is not valid UTF-8, and recommends that a deserializer provide a way to obtain the original bytes.
|
||||||
This library follows that by default: with its
|
This library follows that by default: with its
|
||||||
[`error_handler`](../../api/basic_json/from_msgpack.md) parameter left at `keep` (the default),
|
[`error_handler`](../../api/basic_json/from_msgpack.md) parameter left at `keep` (the default),
|
||||||
`from_msgpack()` reads `str` bytes (object keys included) as-is, without validating them, so such a value
|
`from_msgpack()` reads `str` bytes (object keys included) as-is, without validating them, so such a value
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ achieve the generality of JSON, combined with being much easier to process than
|
|||||||
|
|
||||||
!!! abstract "References"
|
!!! abstract "References"
|
||||||
|
|
||||||
- [UBJSON Website](http://ubjson.org)
|
- [UBJSON Website](https://ubjson.org)
|
||||||
|
|
||||||
## Byte layout
|
## Byte layout
|
||||||
|
|
||||||
UBJSON is built from just a one-byte, human-readable ASCII *type marker* (for example, `i` for int8, `S` for string, `[` for array) followed by the payload of that type. Strings and the like are prefixed with their length, which is itself a UBJSON integer value including its marker. Numbers are stored in big-endian byte order. Containers can be written in two ways: as an *unoptimized* container that is opened with `[` or `{` and closed with `]` or `}`, or as an *optimized* container that declares an element type (`$`) and/or a count (`#`) after the opening marker, which saves per-element markers and (for a count) the closing marker.
|
UBJSON is built from just a one-byte, human-readable ASCII *type marker* (for example, `i` for int8, `S` for string, `[` for array) followed by the payload of that type. Strings and the like are prefixed with their length, which is itself a UBJSON integer value including its marker. Numbers are stored in big-endian byte order. Containers can be written in two ways: as an *unoptimized* container that is opened with `[` or `{` and closed with `]` or `}`, or as an *optimized* container that declares a count (`#`) after the opening marker, optionally preceded by an element type (`$`); an element type is only allowed together with a count. An optimized container saves the closing marker and, with a type, the per-element markers.
|
||||||
|
|
||||||
Consider the following JSON value (shown formatted for readability):
|
Consider the following JSON value (shown formatted for readability):
|
||||||
|
|
||||||
@@ -44,29 +44,29 @@ Consider the following JSON value (shown formatted for readability):
|
|||||||
|
|
||||||
The library uses the following mapping from JSON values types to UBJSON types according to the UBJSON specification:
|
The library uses the following mapping from JSON values types to UBJSON types according to the UBJSON specification:
|
||||||
|
|
||||||
| JSON value type | value/range | UBJSON type | marker |
|
| JSON value type | value/range | UBJSON type | marker |
|
||||||
|-----------------|-------------------------------------------|----------------|--------|
|
|-----------------|-------------------------------------------|--------------------------------------------------|-----------|
|
||||||
| null | `null` | null | `Z` |
|
| null | `null` | null | `Z` |
|
||||||
| boolean | `true` | true | `T` |
|
| boolean | `true` | true | `T` |
|
||||||
| boolean | `false` | false | `F` |
|
| boolean | `false` | false | `F` |
|
||||||
| number_integer | -9223372036854775808..-2147483649 | int64 | `L` |
|
| number_integer | -9223372036854775808..-2147483649 | int64 | `L` |
|
||||||
| number_integer | -2147483648..-32769 | int32 | `l` |
|
| number_integer | -2147483648..-32769 | int32 | `l` |
|
||||||
| number_integer | -32768..-129 | int16 | `I` |
|
| number_integer | -32768..-129 | int16 | `I` |
|
||||||
| number_integer | -128..127 | int8 | `i` |
|
| number_integer | -128..127 | int8 | `i` |
|
||||||
| number_integer | 128..255 | uint8 | `U` |
|
| number_integer | 128..255 | uint8 | `U` |
|
||||||
| number_integer | 256..32767 | int16 | `I` |
|
| number_integer | 256..32767 | int16 | `I` |
|
||||||
| number_integer | 32768..2147483647 | int32 | `l` |
|
| number_integer | 32768..2147483647 | int32 | `l` |
|
||||||
| number_integer | 2147483648..9223372036854775807 | int64 | `L` |
|
| number_integer | 2147483648..9223372036854775807 | int64 | `L` |
|
||||||
| number_unsigned | 0..127 | int8 | `i` |
|
| number_unsigned | 0..127 | int8 | `i` |
|
||||||
| number_unsigned | 128..255 | uint8 | `U` |
|
| number_unsigned | 128..255 | uint8 | `U` |
|
||||||
| number_unsigned | 256..32767 | int16 | `I` |
|
| number_unsigned | 256..32767 | int16 | `I` |
|
||||||
| number_unsigned | 32768..2147483647 | int32 | `l` |
|
| number_unsigned | 32768..2147483647 | int32 | `l` |
|
||||||
| number_unsigned | 2147483648..9223372036854775807 | int64 | `L` |
|
| number_unsigned | 2147483648..9223372036854775807 | int64 | `L` |
|
||||||
| number_unsigned | 9223372036854775808..18446744073709551615 | high-precision | `H` |
|
| number_unsigned | 9223372036854775808..18446744073709551615 | high-precision | `H` |
|
||||||
| number_float | *any value* | float64 | `D` |
|
| number_float | *any value* | float64 (float32 if `number_float_t` is `float`) | `D` (`d`) |
|
||||||
| string | *with shortest length indicator* | string | `S` |
|
| string | *with shortest length indicator* | string | `S` |
|
||||||
| array | *see notes on optimized format* | array | `[` |
|
| array | *see notes on optimized format* | array | `[` |
|
||||||
| object | *see notes on optimized format* | map | `{` |
|
| object | *see notes on optimized format* | object | `{` |
|
||||||
|
|
||||||
!!! success "Complete mapping"
|
!!! success "Complete mapping"
|
||||||
|
|
||||||
@@ -93,20 +93,27 @@ The library uses the following mapping from JSON values types to UBJSON types ac
|
|||||||
|
|
||||||
The following markers are not used in the conversion:
|
The following markers are not used in the conversion:
|
||||||
|
|
||||||
- `Z`: no-op values are not created.
|
- `N`: no-op values are never written (they are skipped when reading).
|
||||||
- `C`: single-byte strings are serialized with `S` markers.
|
- `C`: single-byte strings are serialized with `S` markers.
|
||||||
|
|
||||||
!!! info "NaN/infinity handling"
|
!!! info "NaN/infinity handling"
|
||||||
|
|
||||||
If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the
|
NaN and infinity are written as IEEE 754 float64 values (`D`) and read back unchanged. This differs from
|
||||||
[`dump()`](../../api/basic_json/dump.md) function which serializes NaN or Infinity to `null`.
|
[`dump()`](../../api/basic_json/dump.md), which serializes them to `null`, and from the UBJSON specification, which
|
||||||
|
encodes infinity as `null` (it does not mention NaN); other UBJSON readers may therefore not accept these values.
|
||||||
|
|
||||||
!!! info "Optimized formats"
|
!!! info "Optimized formats"
|
||||||
|
|
||||||
The optimized formats for containers are supported: Parameter `use_size` adds size information to the beginning of a
|
The optimized formats for containers are supported: Parameter `use_size` adds size information to the beginning of a
|
||||||
container and removes the closing marker. Parameter `use_type` further checks whether all elements of a container
|
container and removes the closing marker. Parameter `use_type` further checks whether all elements of a container
|
||||||
have the same type and adds the type marker to the beginning of the container. The `use_type` parameter must only be
|
have the same type and adds the type marker to the beginning of the container. The `use_type` parameter must only be
|
||||||
used together with `use_size = true`.
|
used together with `use_size = true`: combining `use_type` with `use_size = false` throws
|
||||||
|
[`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if the value contains a non-empty array,
|
||||||
|
object, or binary value.
|
||||||
|
|
||||||
|
Two elements have the same type if they are written with the same type marker. For example, `[1, 2]` is written with
|
||||||
|
the type `i`, but `[1, 200]` (`i`, `U`), `[true, false]` (`T`, `F`), and `[1, 2.5]` (`i`, `D`) are not; an empty
|
||||||
|
container is never given a `$` annotation of its own (an array of empty arrays is still typed `[`).
|
||||||
|
|
||||||
Note that `use_size = true` alone may result in larger representations - the benefit of this parameter is that the
|
Note that `use_size = true` alone may result in larger representations - the benefit of this parameter is that the
|
||||||
receiving side is immediately informed on the number of elements of the container.
|
receiving side is immediately informed on the number of elements of the container.
|
||||||
@@ -115,8 +122,9 @@ The library uses the following mapping from JSON values types to UBJSON types ac
|
|||||||
already is the value. Its declared count is therefore the only thing that decides how much memory the receiving side
|
already is the value. Its declared count is therefore the only thing that decides how much memory the receiving side
|
||||||
allocates, and a handful of bytes can describe billions of elements. `from_ubjson` rejects such an array with
|
allocates, and a handful of bytes can describe billions of elements. `from_ubjson` rejects such an array with
|
||||||
[`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408) when the count exceeds 1,048,576
|
[`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408) when the count exceeds 1,048,576
|
||||||
(`1 << 20`), and `to_ubjson` writes longer arrays of these types without the annotation, so any value it produces
|
(`1 << 20`), and `to_ubjson` writes longer arrays of these types in the counted but untyped form: the `$` type marker is
|
||||||
can be read back.
|
dropped, the `#` count is kept, and every element is written with its own marker (`[#l 1048577 Z Z ...`), so any value
|
||||||
|
it produces can be read back. Objects and untyped counted arrays are not subject to this limit.
|
||||||
|
|
||||||
!!! info "Binary values"
|
!!! info "Binary values"
|
||||||
|
|
||||||
@@ -140,27 +148,54 @@ The library uses the following mapping from JSON values types to UBJSON types ac
|
|||||||
|
|
||||||
The library maps UBJSON types to JSON value types as follows:
|
The library maps UBJSON types to JSON value types as follows:
|
||||||
|
|
||||||
| UBJSON type | JSON value type | marker |
|
| UBJSON type | JSON value type | marker |
|
||||||
|-------------|-----------------------------------------|--------|
|
|----------------|--------------------------------------------------|--------|
|
||||||
| no-op | *no value, next value is read* | `N` |
|
| no-op | *no value, next value is read* | `N` |
|
||||||
| null | `null` | `Z` |
|
| null | `null` | `Z` |
|
||||||
| false | `false` | `F` |
|
| false | `false` | `F` |
|
||||||
| true | `true` | `T` |
|
| true | `true` | `T` |
|
||||||
| float32 | number_float | `d` |
|
| float32 | number_float | `d` |
|
||||||
| float64 | number_float | `D` |
|
| float64 | number_float | `D` |
|
||||||
| uint8 | number_unsigned | `U` |
|
| uint8 | number_unsigned | `U` |
|
||||||
| int8 | number_integer | `i` |
|
| int8 | number_integer | `i` |
|
||||||
| int16 | number_integer | `I` |
|
| int16 | number_integer | `I` |
|
||||||
| int32 | number_integer | `l` |
|
| int32 | number_integer | `l` |
|
||||||
| int64 | number_integer | `L` |
|
| int64 | number_integer | `L` |
|
||||||
| string | string | `S` |
|
| string | string | `S` |
|
||||||
| char | string | `C` |
|
| char | string | `C` |
|
||||||
| array | array (optimized values are supported) | `[` |
|
| high-precision | number_unsigned, number_integer, or number_float | `H` |
|
||||||
| object | object (optimized values are supported) | `{` |
|
| array | array (optimized values are supported) | `[` |
|
||||||
|
| object | object (optimized values are supported) | `{` |
|
||||||
|
|
||||||
|
!!! info "High-precision numbers"
|
||||||
|
|
||||||
|
The payload of `H` is a length-prefixed string that is parsed as a JSON number (surrounding whitespace is ignored).
|
||||||
|
Integer text in the range of `number_unsigned_t` (non-negative) or `number_integer_t` (negative) is stored as such;
|
||||||
|
any other valid number text (larger or smaller integers, fractions, and exponents such as `1e2`) is stored as
|
||||||
|
`number_float`, so precision may be lost. Text that is not a valid JSON number raises
|
||||||
|
[`parse_error.115`](../../home/exceptions.md#jsonexceptionparse_error115), a number outside the range of
|
||||||
|
`number_float_t` (`1e999`) raises [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406).
|
||||||
|
|
||||||
!!! success "Complete mapping"
|
!!! success "Complete mapping"
|
||||||
|
|
||||||
The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value.
|
Every well-formed UBJSON value can be converted to a JSON value, with these exceptions: a high-precision number
|
||||||
|
outside the range of `number_float_t` ([`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406)),
|
||||||
|
an optimized array of `Z`, `T`, or `F` with more than 1,048,576 elements, and a count that exceeds the capacity of the
|
||||||
|
container ([`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408)). High-precision numbers that
|
||||||
|
do not fit an integer type are stored as floating-point numbers and may lose precision. Ill-formed input is rejected
|
||||||
|
with a parse error.
|
||||||
|
|
||||||
|
!!! info "Duplicate object keys"
|
||||||
|
|
||||||
|
If an object contains the same key more than once, the last value is kept (the UBJSON specification does not
|
||||||
|
address duplicate keys).
|
||||||
|
|
||||||
|
!!! info "Type marker of optimized containers"
|
||||||
|
|
||||||
|
The type marker after `$` must be one of `Z`, `N`, `T`, `F`, `i`, `U`, `I`, `l`, `L`, `d`, `D`, `H`, `C`, `S`, `[`, or
|
||||||
|
`{`. It is checked even if the count is 0; any other marker raises
|
||||||
|
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112). A counted object typed `N` (`{$N#i1 ...`)
|
||||||
|
is not supported.
|
||||||
|
|
||||||
!!! warning "Ill-formed UTF-8 in string values and object keys"
|
!!! warning "Ill-formed UTF-8 in string values and object keys"
|
||||||
|
|
||||||
|
|||||||
@@ -132,8 +132,11 @@ is an integer or `null`.
|
|||||||
|
|
||||||
### BJData
|
### BJData
|
||||||
|
|
||||||
[BJData](binary_formats/bjdata.md) neither supports binary values nor subtypes and proposes to serialize binary values
|
[BJData](binary_formats/bjdata.md) Draft 3 supports binary values as optimized arrays of `B` (byte) elements, but no
|
||||||
as an array of uint8 values. The library implements this translation.
|
subtypes. With `version = bjdata_version_t::draft3` and both `use_size` and `use_type` set, `to_bjdata` writes a binary
|
||||||
|
value as `[$B#...`, and `from_bjdata` reads it back as a binary value (without subtype). In all other cases, including
|
||||||
|
the default Draft 2 mode, the library follows the Draft 2 proposal and writes an array of integers (`U` in Draft 2, `B`
|
||||||
|
in Draft 3), which is read back as an array.
|
||||||
|
|
||||||
??? example "Example: serialize a binary value to BJData"
|
??? example "Example: serialize a binary value to BJData"
|
||||||
|
|
||||||
@@ -171,7 +174,7 @@ as an array of uint8 values. The library implements this translation.
|
|||||||
|
|
||||||
```c
|
```c
|
||||||
0x7B // '{'
|
0x7B // '{'
|
||||||
0x23 0x69 0x01 // '#' 'i' type of the array elements: unsigned integers
|
0x23 0x69 0x01 // '#' 'i' 1 number of object members
|
||||||
0x69 0x06 // i 6 (length of the key)
|
0x69 0x06 // i 6 (length of the key)
|
||||||
0x62 0x69 0x6E 0x61 0x72 0x79 // "binary"
|
0x62 0x69 0x6E 0x61 0x72 0x79 // "binary"
|
||||||
0x5B // '[' array
|
0x5B // '[' array
|
||||||
@@ -180,7 +183,7 @@ as an array of uint8 values. The library implements this translation.
|
|||||||
0xCA 0xFE 0xBA 0xBE // content
|
0xCA 0xFE 0xBA 0xBE // content
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that subtype (42) is **not** serialized and that BJData has **no binary type**, and deserializing `v` would
|
Note that subtype (42) is **not** serialized and that, in Draft 2 mode, BJData has **no binary type**, and deserializing `v` would
|
||||||
yield the following value:
|
yield the following value:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -213,7 +216,7 @@ array of integers.
|
|||||||
0x87 // object with 1 member
|
0x87 // object with 1 member
|
||||||
0x62 0x69 0x6E 0x61 0x72 0x79 // "binary"
|
0x62 0x69 0x6E 0x61 0x72 0x79 // "binary"
|
||||||
0x84 // array with 4 elements
|
0x84 // array with 4 elements
|
||||||
0xC3 0x22 0xC3 0x56 0xC3 0x12 0xC3 0x16 // content (each byte as a 2-byte integer)
|
0xC3 0x22 0xC3 0x56 0xC3 0x12 0xC3 0x16 // content (each byte, here all above 39, as a 2-byte integer)
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the subtype is lost, and deserializing `v` would yield the following value:
|
Note that the subtype is lost, and deserializing `v` would yield the following value:
|
||||||
@@ -227,7 +230,10 @@ array of integers.
|
|||||||
### BSON
|
### BSON
|
||||||
|
|
||||||
[BSON](binary_formats/bson.md) supports binary values and subtypes. If a subtype is given, it is used and added as an
|
[BSON](binary_formats/bson.md) supports binary values and subtypes. If a subtype is given, it is used and added as an
|
||||||
unsigned 8-bit integer. If no subtype is given, the generic binary subtype 0x00 is used.
|
unsigned 8-bit integer; a subtype above 255 throws
|
||||||
|
[`out_of_range.415`](../home/exceptions.md#jsonexceptionout_of_range415). If no subtype is given, the generic binary
|
||||||
|
subtype 0x00 is used. The payload of the "old binary" subtype 0x02 is preceded by a second length, which the library
|
||||||
|
writes and removes again when reading; it is not part of the payload.
|
||||||
|
|
||||||
??? example "Example: serialize a binary value to BSON"
|
??? example "Example: serialize a binary value to BSON"
|
||||||
|
|
||||||
@@ -301,6 +307,8 @@ representation using the length of the byte array.
|
|||||||
[`json::from_cbor`](../api/basic_json/from_cbor.md) (see
|
[`json::from_cbor`](../api/basic_json/from_cbor.md) (see
|
||||||
[`cbor_tag_handler_t`](../api/basic_json/cbor_tag_handler_t.md)).
|
[`cbor_tag_handler_t`](../api/basic_json/cbor_tag_handler_t.md)).
|
||||||
|
|
||||||
|
With `ignore`, the subtype is dropped (`"subtype": null`); with `store`, the tag is kept (`"subtype": 42`):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"binary": {
|
"binary": {
|
||||||
@@ -310,11 +318,21 @@ representation using the length of the byte array.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"binary": {
|
||||||
|
"bytes": [202, 254, 186, 190],
|
||||||
|
"subtype": 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### MessagePack
|
### MessagePack
|
||||||
|
|
||||||
[MessagePack](binary_formats/messagepack.md) supports binary values and subtypes. If a subtype is given, the ext family
|
[MessagePack](binary_formats/messagepack.md) supports binary values and subtypes. If a subtype is given, the ext family
|
||||||
is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, ext8, ext16, and
|
is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, fixext16, ext8,
|
||||||
ext32. The subtype is then added as a signed 8-bit integer.
|
ext16, and ext32. The subtype is then written as the signed 8-bit ext type: subtypes 128 to 255 map to the negative
|
||||||
|
(reserved) ext types, and on reading the ext type is exposed as an unsigned subtype from 0 to 255.
|
||||||
|
|
||||||
If no subtype is given, the bin family (bin8, bin16, bin32) is used.
|
If no subtype is given, the bin family (bin8, bin16, bin32) is used.
|
||||||
|
|
||||||
|
|||||||
@@ -368,8 +368,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
|||||||
`#!cpp std::from_chars` is not available or declines the token, and `std::strtold` reads up to the null character. A type whose `data()`
|
`#!cpp std::from_chars` is not available or declines the token, and `std::strtold` reads up to the null character. A type whose `data()`
|
||||||
is not null-terminated does not fail to compile; with such a `NumberFloatType` it can silently misparse
|
is not null-terminated does not fail to compile; with such a `NumberFloatType` it can silently misparse
|
||||||
floating-point numbers.
|
floating-point numbers.
|
||||||
- `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md), and `append(const StringType&)`,
|
- `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md) and by the binary readers. The library's internal string concatenation additionally has
|
||||||
used by the CBOR reader for indefinite-length strings. The library's internal string concatenation additionally has
|
|
||||||
to append a `#!cpp char` and a `#!cpp const char*`; for each it selects between `append(arg)`, `#!cpp operator+=`,
|
to append a `#!cpp char` and a `#!cpp const char*`; for each it selects between `append(arg)`, `#!cpp operator+=`,
|
||||||
`append(first, last)`, and `append(data, size)`.
|
`append(first, last)`, and `append(data, size)`.
|
||||||
- The comparison operator `==` against another `StringType`, and `<` for use as a key of the chosen
|
- The comparison operator `==` against another `StringType`, and `<` for use as a key of the chosen
|
||||||
@@ -678,8 +677,7 @@ using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;
|
|||||||
[`StringType`](#stringtype), whose non-`#!cpp const` `data()` is C++17 and later only.
|
[`StringType`](#stringtype), whose non-`#!cpp const` `data()` is C++17 and later only.
|
||||||
- Default-constructible, copy-constructible, and move-constructible.
|
- Default-constructible, copy-constructible, and move-constructible.
|
||||||
- Member functions `size()`, `empty()`, `data()`, `resize()`, `operator[]`, `back()`, `begin()`, `end()`, `cbegin()`,
|
- Member functions `size()`, `empty()`, `data()`, `resize()`, `operator[]`, `back()`, `begin()`, `end()`, `cbegin()`,
|
||||||
and `cend()` with random-access iterators, and `insert(pos, first, last)`, which the CBOR reader uses to join the
|
and `cend()` with random-access iterators. `push_back()` is **not** required.
|
||||||
chunks of an indefinite-length byte string. `push_back()` is **not** required.
|
|
||||||
- Comparison operators: `==` is used by
|
- Comparison operators: `==` is used by
|
||||||
[`byte_container_with_subtype`](../../api/byte_container_with_subtype/index.md), the relational operators by
|
[`byte_container_with_subtype`](../../api/byte_container_with_subtype/index.md), the relational operators by
|
||||||
[`basic_json`'s comparison operators](../../api/basic_json/operator_le.md).
|
[`basic_json`'s comparison operators](../../api/basic_json/operator_le.md).
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ where they cannot be resolved.
|
|||||||
### json.exception.parse_error.110
|
### json.exception.parse_error.110
|
||||||
|
|
||||||
When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has
|
When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has
|
||||||
been read.
|
been read, or (with `strict = true`) further bytes follow the value.
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
@@ -346,7 +346,7 @@ been read.
|
|||||||
|
|
||||||
### json.exception.parse_error.112
|
### json.exception.parse_error.112
|
||||||
|
|
||||||
An unexpected byte was read in a [binary format](../features/binary_formats/index.md) or length information is invalid ([BSON](../features/binary_formats/bson.md)).
|
An unexpected byte was read in a [binary format](../features/binary_formats/index.md) or length information is invalid ([BSON](../features/binary_formats/bson.md)). In [BON8](../features/binary_formats/bon8.md), this exception is also thrown for a key that is not a string and for ill-formed UTF-8.
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
@@ -371,15 +371,21 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
|
|||||||
```
|
```
|
||||||
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
|
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
|
||||||
```
|
```
|
||||||
|
```
|
||||||
|
[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80
|
||||||
|
```
|
||||||
|
|
||||||
### json.exception.parse_error.113
|
### json.exception.parse_error.113
|
||||||
|
|
||||||
A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a
|
A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a
|
||||||
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
||||||
the string's bytes are not valid UTF-8 and the `error_handler` parameter of the corresponding `from_*` function is
|
the string's bytes are not valid UTF-8 and the `error_handler` parameter of the corresponding `from_*` function is
|
||||||
set to `strict`. By default (`error_handler_t::keep`), the bytes of a string are not checked for valid UTF-8 on read;
|
set to `strict`. By default (`error_handler_t::keep`), the bytes of a string are not checked for valid UTF-8 on read.
|
||||||
see the ill-formed UTF-8 notes on the individual [binary format](../features/binary_formats/index.md) pages for how
|
This applies to the `from_*` functions that have an `error_handler` parameter; `from_bon8` does not have one.
|
||||||
|
See the ill-formed UTF-8 notes on the individual [binary format](../features/binary_formats/index.md) pages for how
|
||||||
such a string is handled depending on `error_handler`.
|
such a string is handled depending on `error_handler`.
|
||||||
|
[`from_bon8()`](../api/basic_json/from_bon8.md) never throws this exception: BON8 non-string keys and ill-formed UTF-8
|
||||||
|
are reported as [parse_error.112](#jsonexceptionparse_error112).
|
||||||
|
|
||||||
CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other
|
CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other
|
||||||
type (for instance integers or `null`) are therefore not supported; see the notes on
|
type (for instance integers or `null`) are therefore not supported; see the notes on
|
||||||
@@ -418,13 +424,16 @@ The parsing of the corresponding BSON record type is not implemented (yet).
|
|||||||
|
|
||||||
### json.exception.parse_error.115
|
### json.exception.parse_error.115
|
||||||
|
|
||||||
A UBJSON high-precision number could not be parsed.
|
A UBJSON or BJData high-precision number could not be parsed.
|
||||||
|
|
||||||
!!! failure "Example message"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
```
|
```
|
||||||
[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A
|
[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A
|
||||||
```
|
```
|
||||||
|
```
|
||||||
|
[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A
|
||||||
|
```
|
||||||
|
|
||||||
### json.exception.parse_error.116
|
### json.exception.parse_error.116
|
||||||
|
|
||||||
@@ -812,6 +821,9 @@ The binary writers [`to_cbor()`](../api/basic_json/to_cbor.md), [`to_ubjson()`](
|
|||||||
as well for a string value or object key that is not valid UTF-8 if their `error_handler` is `strict` (the default if
|
as well for a string value or object key that is not valid UTF-8 if their `error_handler` is `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) is enabled). So does
|
[`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) is enabled). So does
|
||||||
[`to_msgpack()`](../api/basic_json/to_msgpack.md) if `error_handler_t::strict` is passed.
|
[`to_msgpack()`](../api/basic_json/to_msgpack.md) if `error_handler_t::strict` is passed.
|
||||||
|
[`to_bon8()`](../api/basic_json/to_bon8.md) always throws this exception for such a string or key, regardless of
|
||||||
|
[`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md), because the end of a BON8 string is determined
|
||||||
|
from its UTF-8 encoding.
|
||||||
|
|
||||||
!!! failure "Example message"
|
!!! failure "Example message"
|
||||||
|
|
||||||
@@ -1006,7 +1018,8 @@ double-precision number when `number_float_t` is `#!cpp float`.
|
|||||||
An integer number cannot be represented by the binary format it is serialized to:
|
An integer number cannot be represented by the binary format it is serialized to:
|
||||||
|
|
||||||
- [BON8](../features/binary_formats/bon8.md) only stores integers that fit into int64, so
|
- [BON8](../features/binary_formats/bon8.md) only stores integers that fit into int64, so
|
||||||
[`to_bon8`](../api/basic_json/to_bon8.md) throws for unsigned integers greater than 9223372036854775807.
|
[`to_bon8`](../api/basic_json/to_bon8.md) throws for unsigned integers greater than 9223372036854775807 and, with a
|
||||||
|
number type wider than 64 bits, for any integer outside the int64 range.
|
||||||
- [CBOR](../features/binary_formats/cbor.md), [MessagePack](../features/binary_formats/messagepack.md), and
|
- [CBOR](../features/binary_formats/cbor.md), [MessagePack](../features/binary_formats/messagepack.md), and
|
||||||
[BSON](../features/binary_formats/bson.md) store integers in at most 64 bits. With the default number types, every
|
[BSON](../features/binary_formats/bson.md) store integers in at most 64 bits. With the default number types, every
|
||||||
integer fits, but a [`number_integer_t`](../api/basic_json/number_integer_t.md) or
|
integer fits, but a [`number_integer_t`](../api/basic_json/number_integer_t.md) or
|
||||||
@@ -1025,7 +1038,7 @@ exception, because they serialize integers beyond 64 bits as high-precision numb
|
|||||||
- Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers.
|
- Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers.
|
||||||
|
|
||||||
Before version 3.13.0, CBOR, MessagePack, and BSON silently truncated integers wider than 64 bits, and BJData
|
Before version 3.13.0, CBOR, MessagePack, and BSON silently truncated integers wider than 64 bits, and BJData
|
||||||
truncated unsigned integers wider than 64 bits.
|
truncated unsigned integers wider than 64 bits that were elements of ND-arrays with `"_ArrayType_": "uint64"`.
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
@@ -1045,8 +1058,8 @@ or the encoded length for [CBOR](../features/binary_formats/cbor.md).
|
|||||||
The exception is also thrown for a [UBJSON](../features/binary_formats/ubjson.md) array of a type that is encoded by its
|
The exception is also thrown for a [UBJSON](../features/binary_formats/ubjson.md) array of a type that is encoded by its
|
||||||
marker alone (`Z`, `T` or `F`) whose declared count exceeds 1,048,576 (`1 << 20`). Such an array has no payload, so its
|
marker alone (`Z`, `T` or `F`) whose declared count exceeds 1,048,576 (`1 << 20`). Such an array has no payload, so its
|
||||||
count alone decides how much memory is allocated, and a handful of bytes would otherwise describe billions of values.
|
count alone decides how much memory is allocated, and a handful of bytes would otherwise describe billions of values.
|
||||||
[`to_ubjson`](../api/basic_json/to_ubjson.md) writes longer arrays of these types without the size and type annotation,
|
[`to_ubjson`](../api/basic_json/to_ubjson.md) writes longer arrays of these types without the `$` type annotation (the `#`
|
||||||
so any value it produces can still be read back.
|
count and one marker per element are kept), so any value it produces can still be read back.
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
@@ -1153,14 +1166,18 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa
|
|||||||
### json.exception.out_of_range.415
|
### json.exception.out_of_range.415
|
||||||
|
|
||||||
MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a
|
MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a
|
||||||
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255.
|
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255, the largest value the library can store in that byte.
|
||||||
|
|
||||||
!!! failure "Example message"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
```
|
```
|
||||||
[json.exception.out_of_range.415] subtype 70000 is too large for the MessagePack ext type (max 255)
|
[json.exception.out_of_range.415] subtype 70000 is too large for the MessagePack ext type (max 255)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
[json.exception.out_of_range.415] subtype 70000 is too large for the BSON binary subtype (max 255)
|
||||||
|
```
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
This exception was added in version 3.13.0. Before that, subtypes above 255 were silently truncated modulo 256 instead of raising an error.
|
This exception was added in version 3.13.0. Before that, subtypes above 255 were silently truncated modulo 256 instead of raising an error.
|
||||||
@@ -1229,7 +1246,9 @@ A JSON Patch operation 'test' failed. The unsuccessful operation is also printed
|
|||||||
### json.exception.other_error.502
|
### json.exception.other_error.502
|
||||||
|
|
||||||
[`to_ubjson`](../api/basic_json/to_ubjson.md) and [`to_bjdata`](../api/basic_json/to_bjdata.md) were called with
|
[`to_ubjson`](../api/basic_json/to_ubjson.md) and [`to_bjdata`](../api/basic_json/to_bjdata.md) were called with
|
||||||
`use_type = true` but `use_size = false`. UBJSON requires a size marker (`#`) after a type marker (`$`).
|
`use_type = true` but `use_size = false`, and the value contains a non-empty array or object (or, with BJData Draft 3,
|
||||||
|
any binary value). UBJSON requires a size marker (`#`) after a type marker (`$`); scalars and empty arrays or objects
|
||||||
|
do not throw.
|
||||||
|
|
||||||
!!! failure "Example message"
|
!!! failure "Example message"
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ nav:
|
|||||||
- 'begin': api/basic_json/begin.md
|
- 'begin': api/basic_json/begin.md
|
||||||
- 'binary': api/basic_json/binary.md
|
- 'binary': api/basic_json/binary.md
|
||||||
- 'binary_t': api/basic_json/binary_t.md
|
- 'binary_t': api/basic_json/binary_t.md
|
||||||
|
- 'bjdata_version_t': api/basic_json/bjdata_version_t.md
|
||||||
- 'boolean_t': api/basic_json/boolean_t.md
|
- 'boolean_t': api/basic_json/boolean_t.md
|
||||||
- 'cbegin': api/basic_json/cbegin.md
|
- 'cbegin': api/basic_json/cbegin.md
|
||||||
- 'cbor_tag_handler_t': api/basic_json/cbor_tag_handler_t.md
|
- 'cbor_tag_handler_t': api/basic_json/cbor_tag_handler_t.md
|
||||||
|
|||||||
@@ -235,6 +235,9 @@ class binary_reader
|
|||||||
/// UBJSON/BJData: the type marker of an optimized container, so that
|
/// UBJSON/BJData: the type marker of an optimized container, so that
|
||||||
/// its elements are read without one of their own; 0 otherwise
|
/// its elements are read without one of their own; 0 otherwise
|
||||||
char_int_type type_marker;
|
char_int_type type_marker;
|
||||||
|
/// BSON: the position right after this document, according to the
|
||||||
|
/// size it declares
|
||||||
|
std::size_t end_position = 0;
|
||||||
/// BSON: the size this document declares, in bytes
|
/// BSON: the size this document declares, in bytes
|
||||||
std::int32_t declared_size = 0;
|
std::int32_t declared_size = 0;
|
||||||
/// whether to close this container with end_object() or end_array()
|
/// whether to close this container with end_object() or end_array()
|
||||||
@@ -371,6 +374,23 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the smallest document is the size prefix and the terminator
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(document_size < 5))
|
||||||
|
{
|
||||||
|
return bson_document_size_error("document size ", document_size, " is less than 5");
|
||||||
|
}
|
||||||
|
|
||||||
|
// the declared size bounds where the document ends: an embedded
|
||||||
|
// document must end before the terminator of the document that contains it
|
||||||
|
const auto unsigned_size = static_cast<std::size_t>(document_size);
|
||||||
|
const std::size_t document_end = unsigned_size > (std::numeric_limits<std::size_t>::max)() - document_start
|
||||||
|
? (std::numeric_limits<std::size_t>::max)()
|
||||||
|
: document_start + unsigned_size;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!container_stack.empty() && document_end >= container_stack.back().end_position))
|
||||||
|
{
|
||||||
|
return bson_document_size_error("document size ", document_size, " exceeds the size of the enclosing document");
|
||||||
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!enter_container(is_object, detail::unknown_size())))
|
if (JSON_HEDLEY_UNLIKELY(!enter_container(is_object, detail::unknown_size())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -378,6 +398,7 @@ class binary_reader
|
|||||||
|
|
||||||
container_frame& frame = container_stack.back();
|
container_frame& frame = container_stack.back();
|
||||||
frame.start_position = document_start;
|
frame.start_position = document_start;
|
||||||
|
frame.end_position = document_end;
|
||||||
frame.declared_size = document_size;
|
frame.declared_size = document_size;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -436,6 +457,13 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// an element needs at least its name's terminator, and the
|
||||||
|
// document its own terminator, within the declared size
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(chars_read + 2 > container_stack.back().end_position))
|
||||||
|
{
|
||||||
|
return bson_document_size_error("document size ", container_stack.back().declared_size, " is too small for its content");
|
||||||
|
}
|
||||||
|
|
||||||
const std::size_t element_type_parse_position = chars_read;
|
const std::size_t element_type_parse_position = chars_read;
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
||||||
@@ -584,9 +612,46 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
result.set_subtype(subtype);
|
result.set_subtype(subtype);
|
||||||
|
|
||||||
|
// the "old binary" subtype 0x02 starts with an int32 that repeats the
|
||||||
|
// size of the payload following it; the payload does not include it
|
||||||
|
if (subtype == 0x02)
|
||||||
|
{
|
||||||
|
return get_bson_old_binary(len, result);
|
||||||
|
}
|
||||||
|
|
||||||
return get_binary(len, result);
|
return get_binary(len, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Parses the payload of a BSON binary value with subtype 0x02.
|
||||||
|
@param[in] len The length of the binary value, including the inner int32.
|
||||||
|
@param[in,out] result A reference to the binary variable where the payload
|
||||||
|
(without the inner int32) is to be stored.
|
||||||
|
@pre len >= 0
|
||||||
|
@return `true` if the inner length was present and matches @a len
|
||||||
|
*/
|
||||||
|
template<typename NumberType>
|
||||||
|
bool get_bson_old_binary(const NumberType len, binary_t& result)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(len < 4))
|
||||||
|
{
|
||||||
|
return bson_length_error("length of binary subtype 0x02 must be at least 4, is ", len, "binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t inner_length{};
|
||||||
|
if (JSON_HEDLEY_UNLIKELY((!get_number<std::int32_t, true>(inner_length))))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(static_cast<std::int64_t>(inner_length) != static_cast<std::int64_t>(len) - 4))
|
||||||
|
{
|
||||||
|
return bson_length_error("inner length of binary subtype 0x02 does not match the payload size; inner length is ", inner_length, "binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_binary(inner_length, result);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Read a BSON document element of the given @a element_type.
|
@brief Read a BSON document element of the given @a element_type.
|
||||||
@param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
|
@param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
|
||||||
@@ -1218,6 +1283,10 @@ class binary_reader
|
|||||||
// level as required by RFC 8949, Section 3.2.3
|
// level as required by RFC 8949, Section 3.2.3
|
||||||
bool indefinite = false;
|
bool indefinite = false;
|
||||||
|
|
||||||
|
// the chunk currently being validated; only used for the chunks of an
|
||||||
|
// indefinite-length string when the UTF-8 check can do something
|
||||||
|
string_t chunk;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string")))
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string")))
|
||||||
@@ -1237,26 +1306,82 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// a break marker closes the indefinite-length string; outside
|
// a break marker closes the indefinite-length string; outside
|
||||||
// of one it falls through to the error below
|
// of one it falls through to the error below. Every chunk has
|
||||||
|
// been checked on its own, so there is nothing left to check.
|
||||||
if (indefinite && current == 0xFF)
|
if (indefinite && current == 0xFF)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!indefinite)
|
if (!indefinite)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return get_cbor_string_chunk(result, false) && check_string_utf8(result, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_handler == error_handler_t::keep)
|
||||||
|
{
|
||||||
|
// nothing to check: read the chunk directly into the result
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// RFC 8949, Section 3.2.3: each chunk is a text string of its
|
||||||
|
// own and must be valid UTF-8 by itself, i.e., a code point
|
||||||
|
// cannot be spread over two chunks
|
||||||
|
chunk.clear();
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result.append(chunk.data(), chunk.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
get();
|
get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief skips the tags in front of a CBOR object key
|
||||||
|
|
||||||
|
A tag may wrap any data item, so it may also wrap a key. Like in front of a
|
||||||
|
value, the tags are skipped for the handlers ignore and store; with the
|
||||||
|
handler error they are left to @ref get_cbor_object_key, which rejects the
|
||||||
|
key. The tag loop is iterative, so the number of tags is not limited by the
|
||||||
|
recursion depth.
|
||||||
|
|
||||||
|
@param[in] tag_handler how CBOR tags should be treated
|
||||||
|
|
||||||
|
@return whether the tags could be skipped (false on EOF in a tag argument)
|
||||||
|
|
||||||
|
@pre @a current is the first byte of the key
|
||||||
|
*/
|
||||||
|
bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler)
|
||||||
|
{
|
||||||
|
if (tag_handler == cbor_tag_handler_t::error)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in
|
||||||
|
// the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB)
|
||||||
|
while (current >= 0xC0 && current <= 0xDB)
|
||||||
|
{
|
||||||
|
if (current >= 0xD8)
|
||||||
|
{
|
||||||
|
std::uint64_t tag_to_ignore{};
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a CBOR object key
|
@brief reads a CBOR object key
|
||||||
|
|
||||||
@@ -1623,6 +1748,10 @@ class binary_reader
|
|||||||
|
|
||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -2936,6 +3065,14 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate the type marker up front: otherwise it would only be
|
||||||
|
// checked when an element is read, and a container with count 0
|
||||||
|
// would accept any marker
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!is_valid_optimized_type(result.second)))
|
||||||
|
{
|
||||||
|
return optimized_type_error();
|
||||||
|
}
|
||||||
|
|
||||||
get_ignore_noop();
|
get_ignore_noop();
|
||||||
if (JSON_HEDLEY_UNLIKELY(current != '#'))
|
if (JSON_HEDLEY_UNLIKELY(current != '#'))
|
||||||
{
|
{
|
||||||
@@ -3140,6 +3277,8 @@ class binary_reader
|
|||||||
// get_ubjson_size_value() (the type marker is known before the dimension vector
|
// get_ubjson_size_value() (the type marker is known before the dimension vector
|
||||||
// that determines size_and_type.first is read, so it is emitted first there to
|
// that determines size_and_type.first is read, so it is emitted first there to
|
||||||
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
|
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
|
||||||
|
// 'C' elements are read as unsigned bytes, but BJData limits a char to 0..127
|
||||||
|
const bool nd_is_char = size_and_type.second == 'C';
|
||||||
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
||||||
{
|
{
|
||||||
size_and_type.second = 'U';
|
size_and_type.second = 'U';
|
||||||
@@ -3153,6 +3292,24 @@ class binary_reader
|
|||||||
|
|
||||||
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
||||||
{
|
{
|
||||||
|
if (nd_is_char)
|
||||||
|
{
|
||||||
|
get();
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("char")))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(current > 127))
|
||||||
|
{
|
||||||
|
return last_byte_error(exception_id::invalid_string_or_size, "byte after 'C' must be in range 0x00..0x7F", "char", "");
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast<number_unsigned_t>(current))))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))
|
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -4130,9 +4287,9 @@ class binary_reader
|
|||||||
// sequence), UBJSON, BJData, or BSON requires a decoder to reject
|
// sequence), UBJSON, BJData, or BSON requires a decoder to reject
|
||||||
// ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
|
// ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
|
||||||
// rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
|
// rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
|
||||||
// @ref error_handler, applied once the whole string (all chunks of
|
// @ref error_handler, applied by @ref check_string_utf8 at the call
|
||||||
// an indefinite-length CBOR string included) has been assembled, by
|
// site to the whole string or, for an indefinite-length CBOR string,
|
||||||
// @ref check_string_utf8 at the call site.
|
// to each chunk on its own (RFC 8949, Section 3.2.3).
|
||||||
return get_bytes(len, "string", result);
|
return get_bytes(len, "string", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4327,6 +4484,12 @@ class binary_reader
|
|||||||
return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(bytes_read), ")"), "document");
|
return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(bytes_read), ")"), "document");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool bson_document_size_error(const char* prefix, const std::int32_t document_size, const char* suffix) const
|
||||||
|
{
|
||||||
|
return last_byte_error(exception_id::unexpected_byte, concat(prefix, std::to_string(document_size), suffix), "document");
|
||||||
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const
|
bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const
|
||||||
{
|
{
|
||||||
@@ -4432,6 +4595,44 @@ class binary_reader
|
|||||||
std::size_t bon8_pushback_size = 0;
|
std::size_t bon8_pushback_size = 0;
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
|
/*!
|
||||||
|
@brief check whether @a marker is a type marker that may follow '$'
|
||||||
|
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
|
||||||
|
(BJData's excluded markers are rejected separately).
|
||||||
|
*/
|
||||||
|
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
||||||
|
{
|
||||||
|
switch (marker)
|
||||||
|
{
|
||||||
|
case 'i':
|
||||||
|
case 'U':
|
||||||
|
case 'I':
|
||||||
|
case 'l':
|
||||||
|
case 'L':
|
||||||
|
case 'd':
|
||||||
|
case 'D':
|
||||||
|
case 'C':
|
||||||
|
return true;
|
||||||
|
case 'Z':
|
||||||
|
case 'N':
|
||||||
|
case 'T':
|
||||||
|
case 'F':
|
||||||
|
case 'H':
|
||||||
|
case 'S':
|
||||||
|
case '[':
|
||||||
|
case '{':
|
||||||
|
return input_format != input_format_t::bjdata;
|
||||||
|
case 'B':
|
||||||
|
case 'u':
|
||||||
|
case 'm':
|
||||||
|
case 'M':
|
||||||
|
case 'h':
|
||||||
|
return input_format == input_format_t::bjdata;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
||||||
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
||||||
@@ -4474,6 +4675,8 @@ class binary_reader
|
|||||||
return "uint8";
|
return "uint8";
|
||||||
case 'd':
|
case 'd':
|
||||||
return "single";
|
return "single";
|
||||||
|
case 'h':
|
||||||
|
return "half";
|
||||||
case 'i':
|
case 'i':
|
||||||
return "int8";
|
return "int8";
|
||||||
case 'l':
|
case 'l':
|
||||||
|
|||||||
@@ -1684,7 +1684,15 @@ class binary_writer
|
|||||||
throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j);
|
throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sizeof(std::int32_t) + value.size() + 1ul;
|
// the "old binary" subtype 0x02 repeats the payload size in an inner
|
||||||
|
// int32 in front of the payload
|
||||||
|
return sizeof(std::int32_t) + (is_bson_old_binary(value) ? sizeof(std::int32_t) : 0ul) + value.size() + 1ul;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return whether @a value has the BSON subtype 0x02 ("binary (old)")
|
||||||
|
static bool is_bson_old_binary(const binary_t& value) noexcept
|
||||||
|
{
|
||||||
|
return value.has_subtype() && value.subtype() == 0x02;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -1697,10 +1705,18 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x05);
|
write_bson_entry_header(name, 0x05);
|
||||||
|
|
||||||
write_number<std::int32_t>(to_bson_length(value.size()), true);
|
const bool old_binary = is_bson_old_binary(value);
|
||||||
|
|
||||||
|
// the length field counts the inner int32 of the old binary subtype
|
||||||
|
write_number<std::int32_t>(to_bson_length(value.size() + (old_binary ? sizeof(std::int32_t) : 0ul)), true);
|
||||||
|
|
||||||
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
||||||
|
|
||||||
|
if (old_binary)
|
||||||
|
{
|
||||||
|
write_number<std::int32_t>(to_bson_length(value.size()), true);
|
||||||
|
}
|
||||||
|
|
||||||
write_bytes(value.data(), value.size());
|
write_bytes(value.data(), value.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1860,7 +1876,8 @@ class binary_writer
|
|||||||
(or the header of a string or binary value) in one reservation
|
(or the header of a string or binary value) in one reservation
|
||||||
@return whether the entry was written; false for a discarded value, a
|
@return whether the entry was written; false for a discarded value, a
|
||||||
string or binary value whose length does not fit the length
|
string or binary value whose length does not fit the length
|
||||||
field, in which case nothing was written
|
field, or a binary value with subtype 0x02, in which case nothing
|
||||||
|
was written
|
||||||
@pre @a name is at most bson_scalar_max_name_size characters long and is
|
@pre @a name is at most bson_scalar_max_name_size characters long and is
|
||||||
not changed by the error handler
|
not changed by the error handler
|
||||||
*/
|
*/
|
||||||
@@ -1921,7 +1938,9 @@ class binary_writer
|
|||||||
if (j.is_binary())
|
if (j.is_binary())
|
||||||
{
|
{
|
||||||
const binary_t& value = *j.m_data.m_value.binary;
|
const binary_t& value = *j.m_data.m_value.binary;
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size())))
|
// the old binary subtype 0x02 has an inner length that the single
|
||||||
|
// reservation has no room for; write_bson_binary writes it
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size()) || is_bson_old_binary(value)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3829,7 +3848,7 @@ class binary_writer
|
|||||||
A C++11 `constexpr` function cannot contain a `switch`, so this is a plain
|
A C++11 `constexpr` function cannot contain a `switch`, so this is a plain
|
||||||
comparison chain instead; it is only reached once per ND-array candidate
|
comparison chain instead; it is only reached once per ND-array candidate
|
||||||
object. Keep in sync with binary_reader's `bjd_type_name()`, which maps
|
object. Keep in sync with binary_reader's `bjd_type_name()`, which maps
|
||||||
the other way.
|
the other way (it also reads "half", which is never written).
|
||||||
*/
|
*/
|
||||||
static CharType bjdata_ndarray_type_marker(const string_t& name)
|
static CharType bjdata_ndarray_type_marker(const string_t& name)
|
||||||
{
|
{
|
||||||
@@ -4037,10 +4056,13 @@ class binary_writer
|
|||||||
switch (dtype)
|
switch (dtype)
|
||||||
{
|
{
|
||||||
case 'U':
|
case 'U':
|
||||||
case 'C':
|
|
||||||
case 'B':
|
case 'B':
|
||||||
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
|
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
|
||||||
break;
|
break;
|
||||||
|
case 'C':
|
||||||
|
// BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges)
|
||||||
|
ok = bjdata_ndarray_value_in_range<std::int8_t>(el) && bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||||
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
|
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
|
||||||
break;
|
break;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -255,81 +255,6 @@ struct number_token
|
|||||||
bool is_float; ///< a fraction or an exponent
|
bool is_float; ///< a fraction or an exponent
|
||||||
};
|
};
|
||||||
|
|
||||||
/// advance over ASCII digits, eight bytes at a time
|
|
||||||
inline const unsigned char* digit_run(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
||||||
while (e - p >= 8)
|
|
||||||
{
|
|
||||||
// (the digits become 0..9; a byte above 9, or with bit 7 set, is no digit. A carry
|
|
||||||
// out of a byte with bit 7 set only affects the bytes above, after the first one
|
|
||||||
// that count_trailing_zeros finds.)
|
|
||||||
const std::uint64_t x = read_eight_bytes(p) ^ (ones * 0x30u);
|
|
||||||
const std::uint64_t not_digit = ((x + (ones * 0x76u)) | x) & (ones * 0x80u);
|
|
||||||
if (not_digit != 0)
|
|
||||||
{
|
|
||||||
return p + (static_cast<std::size_t>(count_trailing_zeros(not_digit)) / 8);
|
|
||||||
}
|
|
||||||
p += 8;
|
|
||||||
}
|
|
||||||
while (p != e && is_digit(*p))
|
|
||||||
{
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether the n bytes at p are ASCII digits (n > 0)
|
|
||||||
inline bool all_digits(const unsigned char* p, std::size_t n) noexcept
|
|
||||||
{
|
|
||||||
const auto digits = [](const unsigned char* q)
|
|
||||||
{
|
|
||||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
||||||
const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u);
|
|
||||||
return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0;
|
|
||||||
};
|
|
||||||
if (n >= 8)
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i + 8 < n; i += 8)
|
|
||||||
{
|
|
||||||
if (!digits(p + i))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return digits(p + n - 8); // (overlapping the bytes before: they are digits)
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
if (!is_digit(p[i]))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether a float token has the shape that the parser records for most of
|
|
||||||
/// them: [-]digits.digits, with the numbers of digits of the node's layout
|
|
||||||
/// (extra). Then the token is a number of that layout, and the general scan
|
|
||||||
/// that follows is not needed.
|
|
||||||
inline bool plain_float_token(const unsigned char* s, std::size_t len, std::uint16_t extra) noexcept
|
|
||||||
{
|
|
||||||
const std::size_t int_digits = extra & 0xFFu;
|
|
||||||
const std::size_t frac_digits = extra >> 8u;
|
|
||||||
if (int_digits == 0 || int_digits >= 255 || frac_digits == 0 || frac_digits >= 255 || len == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const std::size_t negative = *s == '-' ? 1 : 0;
|
|
||||||
if (len != negative + int_digits + 1 + frac_digits)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const unsigned char* const p = s + negative;
|
|
||||||
return !(int_digits > 1 && *p == '0') && p[int_digits] == '.' && all_digits(p, int_digits) && all_digits(p + int_digits + 1, frac_digits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
|
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
|
||||||
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
|
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
|
||||||
{
|
{
|
||||||
@@ -348,7 +273,11 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
|||||||
}
|
}
|
||||||
else if (*p >= '1' && *p <= '9')
|
else if (*p >= '1' && *p <= '9')
|
||||||
{
|
{
|
||||||
p = digit_run(p + 1, e);
|
do
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
while (p != e && is_digit(*p));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -360,7 +289,10 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
|||||||
if (p != e && *p == '.')
|
if (p != e && *p == '.')
|
||||||
{
|
{
|
||||||
const unsigned char* const f0 = ++p;
|
const unsigned char* const f0 = ++p;
|
||||||
p = digit_run(p, e);
|
while (p != e && is_digit(*p))
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
}
|
||||||
if (p == f0)
|
if (p == f0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -471,15 +403,10 @@ struct float_range
|
|||||||
template<typename Check>
|
template<typename Check>
|
||||||
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||||
{
|
{
|
||||||
// (save() writes the ranges in the order of the nodes, which is sorted already)
|
std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b)
|
||||||
const auto before = [](const byte_range & a, const byte_range & b)
|
|
||||||
{
|
{
|
||||||
return a.off != b.off ? a.off < b.off : a.len < b.len;
|
return a.off != b.off ? a.off < b.off : a.len < b.len;
|
||||||
};
|
});
|
||||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
|
||||||
{
|
|
||||||
std::sort(ranges.begin(), ranges.end(), before);
|
|
||||||
}
|
|
||||||
std::size_t end = 0;
|
std::size_t end = 0;
|
||||||
for (std::size_t i = 0; i < ranges.size(); ++i)
|
for (std::size_t i = 0; i < ranges.size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -501,8 +428,7 @@ bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
|||||||
/// record layouts that match it), tokens must not overlap.
|
/// record layouts that match it), tokens must not overlap.
|
||||||
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
|
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
|
||||||
{
|
{
|
||||||
// (save() writes the tokens in the order of the nodes, which is sorted already)
|
std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b)
|
||||||
const auto before = [](const float_range & a, const float_range & b)
|
|
||||||
{
|
{
|
||||||
if (a.off != b.off)
|
if (a.off != b.off)
|
||||||
{
|
{
|
||||||
@@ -513,11 +439,7 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
|||||||
return a.len < b.len;
|
return a.len < b.len;
|
||||||
}
|
}
|
||||||
return a.extra < b.extra;
|
return a.extra < b.extra;
|
||||||
};
|
});
|
||||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
|
||||||
{
|
|
||||||
std::sort(ranges.begin(), ranges.end(), before);
|
|
||||||
}
|
|
||||||
std::size_t end = 0;
|
std::size_t end = 0;
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
while (i < ranges.size())
|
while (i < ranges.size())
|
||||||
@@ -527,18 +449,14 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
number_token t{};
|
||||||
const unsigned char* const s = text + r.off;
|
const unsigned char* const s = text + r.off;
|
||||||
std::uint16_t layout = r.extra;
|
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||||
if (!plain_float_token(s, r.len, r.extra))
|
|
||||||
{
|
{
|
||||||
number_token t{};
|
return false;
|
||||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// the digit layout the parser records (or "many", as compaction writes it)
|
|
||||||
layout = float_layout(t);
|
|
||||||
}
|
}
|
||||||
|
// the digit layout the parser records (or "many", as compaction writes it)
|
||||||
|
const std::uint16_t layout = float_layout(t);
|
||||||
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
|
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
|
||||||
{
|
{
|
||||||
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
|
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
|
||||||
|
|||||||
@@ -743,12 +743,8 @@ class basic_json_view
|
|||||||
template<typename, bool> friend class basic_json_document;
|
template<typename, bool> friend class basic_json_document;
|
||||||
template<typename, bool> friend class basic_json_view;
|
template<typename, bool> friend class basic_json_view;
|
||||||
template<typename, typename> friend class detail::view::editor;
|
template<typename, typename> friend class detail::view::editor;
|
||||||
template<typename, typename> friend class detail::view::view_side;
|
|
||||||
friend iterator;
|
friend iterator;
|
||||||
|
|
||||||
/// whether the view is one of an editable document
|
|
||||||
static constexpr bool is_editable = Editable;
|
|
||||||
|
|
||||||
basic_json_view(const document_data* d, const node* n) noexcept
|
basic_json_view(const document_data* d, const node* n) noexcept
|
||||||
: m_doc(d), m_node(n)
|
: m_doc(d), m_node(n)
|
||||||
{}
|
{}
|
||||||
|
|||||||
Generated
+242
-17
@@ -17702,6 +17702,9 @@ class binary_reader
|
|||||||
/// UBJSON/BJData: the type marker of an optimized container, so that
|
/// UBJSON/BJData: the type marker of an optimized container, so that
|
||||||
/// its elements are read without one of their own; 0 otherwise
|
/// its elements are read without one of their own; 0 otherwise
|
||||||
char_int_type type_marker;
|
char_int_type type_marker;
|
||||||
|
/// BSON: the position right after this document, according to the
|
||||||
|
/// size it declares
|
||||||
|
std::size_t end_position = 0;
|
||||||
/// BSON: the size this document declares, in bytes
|
/// BSON: the size this document declares, in bytes
|
||||||
std::int32_t declared_size = 0;
|
std::int32_t declared_size = 0;
|
||||||
/// whether to close this container with end_object() or end_array()
|
/// whether to close this container with end_object() or end_array()
|
||||||
@@ -17838,6 +17841,23 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the smallest document is the size prefix and the terminator
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(document_size < 5))
|
||||||
|
{
|
||||||
|
return bson_document_size_error("document size ", document_size, " is less than 5");
|
||||||
|
}
|
||||||
|
|
||||||
|
// the declared size bounds where the document ends: an embedded
|
||||||
|
// document must end before the terminator of the document that contains it
|
||||||
|
const auto unsigned_size = static_cast<std::size_t>(document_size);
|
||||||
|
const std::size_t document_end = unsigned_size > (std::numeric_limits<std::size_t>::max)() - document_start
|
||||||
|
? (std::numeric_limits<std::size_t>::max)()
|
||||||
|
: document_start + unsigned_size;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!container_stack.empty() && document_end >= container_stack.back().end_position))
|
||||||
|
{
|
||||||
|
return bson_document_size_error("document size ", document_size, " exceeds the size of the enclosing document");
|
||||||
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!enter_container(is_object, detail::unknown_size())))
|
if (JSON_HEDLEY_UNLIKELY(!enter_container(is_object, detail::unknown_size())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -17845,6 +17865,7 @@ class binary_reader
|
|||||||
|
|
||||||
container_frame& frame = container_stack.back();
|
container_frame& frame = container_stack.back();
|
||||||
frame.start_position = document_start;
|
frame.start_position = document_start;
|
||||||
|
frame.end_position = document_end;
|
||||||
frame.declared_size = document_size;
|
frame.declared_size = document_size;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -17903,6 +17924,13 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// an element needs at least its name's terminator, and the
|
||||||
|
// document its own terminator, within the declared size
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(chars_read + 2 > container_stack.back().end_position))
|
||||||
|
{
|
||||||
|
return bson_document_size_error("document size ", container_stack.back().declared_size, " is too small for its content");
|
||||||
|
}
|
||||||
|
|
||||||
const std::size_t element_type_parse_position = chars_read;
|
const std::size_t element_type_parse_position = chars_read;
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
||||||
@@ -18051,9 +18079,46 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
result.set_subtype(subtype);
|
result.set_subtype(subtype);
|
||||||
|
|
||||||
|
// the "old binary" subtype 0x02 starts with an int32 that repeats the
|
||||||
|
// size of the payload following it; the payload does not include it
|
||||||
|
if (subtype == 0x02)
|
||||||
|
{
|
||||||
|
return get_bson_old_binary(len, result);
|
||||||
|
}
|
||||||
|
|
||||||
return get_binary(len, result);
|
return get_binary(len, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Parses the payload of a BSON binary value with subtype 0x02.
|
||||||
|
@param[in] len The length of the binary value, including the inner int32.
|
||||||
|
@param[in,out] result A reference to the binary variable where the payload
|
||||||
|
(without the inner int32) is to be stored.
|
||||||
|
@pre len >= 0
|
||||||
|
@return `true` if the inner length was present and matches @a len
|
||||||
|
*/
|
||||||
|
template<typename NumberType>
|
||||||
|
bool get_bson_old_binary(const NumberType len, binary_t& result)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(len < 4))
|
||||||
|
{
|
||||||
|
return bson_length_error("length of binary subtype 0x02 must be at least 4, is ", len, "binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t inner_length{};
|
||||||
|
if (JSON_HEDLEY_UNLIKELY((!get_number<std::int32_t, true>(inner_length))))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(static_cast<std::int64_t>(inner_length) != static_cast<std::int64_t>(len) - 4))
|
||||||
|
{
|
||||||
|
return bson_length_error("inner length of binary subtype 0x02 does not match the payload size; inner length is ", inner_length, "binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_binary(inner_length, result);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Read a BSON document element of the given @a element_type.
|
@brief Read a BSON document element of the given @a element_type.
|
||||||
@param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
|
@param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
|
||||||
@@ -18685,6 +18750,10 @@ class binary_reader
|
|||||||
// level as required by RFC 8949, Section 3.2.3
|
// level as required by RFC 8949, Section 3.2.3
|
||||||
bool indefinite = false;
|
bool indefinite = false;
|
||||||
|
|
||||||
|
// the chunk currently being validated; only used for the chunks of an
|
||||||
|
// indefinite-length string when the UTF-8 check can do something
|
||||||
|
string_t chunk;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string")))
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string")))
|
||||||
@@ -18704,26 +18773,82 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// a break marker closes the indefinite-length string; outside
|
// a break marker closes the indefinite-length string; outside
|
||||||
// of one it falls through to the error below
|
// of one it falls through to the error below. Every chunk has
|
||||||
|
// been checked on its own, so there is nothing left to check.
|
||||||
if (indefinite && current == 0xFF)
|
if (indefinite && current == 0xFF)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!indefinite)
|
if (!indefinite)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return get_cbor_string_chunk(result, false) && check_string_utf8(result, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_handler == error_handler_t::keep)
|
||||||
|
{
|
||||||
|
// nothing to check: read the chunk directly into the result
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// RFC 8949, Section 3.2.3: each chunk is a text string of its
|
||||||
|
// own and must be valid UTF-8 by itself, i.e., a code point
|
||||||
|
// cannot be spread over two chunks
|
||||||
|
chunk.clear();
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result.append(chunk.data(), chunk.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
get();
|
get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief skips the tags in front of a CBOR object key
|
||||||
|
|
||||||
|
A tag may wrap any data item, so it may also wrap a key. Like in front of a
|
||||||
|
value, the tags are skipped for the handlers ignore and store; with the
|
||||||
|
handler error they are left to @ref get_cbor_object_key, which rejects the
|
||||||
|
key. The tag loop is iterative, so the number of tags is not limited by the
|
||||||
|
recursion depth.
|
||||||
|
|
||||||
|
@param[in] tag_handler how CBOR tags should be treated
|
||||||
|
|
||||||
|
@return whether the tags could be skipped (false on EOF in a tag argument)
|
||||||
|
|
||||||
|
@pre @a current is the first byte of the key
|
||||||
|
*/
|
||||||
|
bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler)
|
||||||
|
{
|
||||||
|
if (tag_handler == cbor_tag_handler_t::error)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in
|
||||||
|
// the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB)
|
||||||
|
while (current >= 0xC0 && current <= 0xDB)
|
||||||
|
{
|
||||||
|
if (current >= 0xD8)
|
||||||
|
{
|
||||||
|
std::uint64_t tag_to_ignore{};
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a CBOR object key
|
@brief reads a CBOR object key
|
||||||
|
|
||||||
@@ -19090,6 +19215,10 @@ class binary_reader
|
|||||||
|
|
||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -20403,6 +20532,14 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate the type marker up front: otherwise it would only be
|
||||||
|
// checked when an element is read, and a container with count 0
|
||||||
|
// would accept any marker
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!is_valid_optimized_type(result.second)))
|
||||||
|
{
|
||||||
|
return optimized_type_error();
|
||||||
|
}
|
||||||
|
|
||||||
get_ignore_noop();
|
get_ignore_noop();
|
||||||
if (JSON_HEDLEY_UNLIKELY(current != '#'))
|
if (JSON_HEDLEY_UNLIKELY(current != '#'))
|
||||||
{
|
{
|
||||||
@@ -20607,6 +20744,8 @@ class binary_reader
|
|||||||
// get_ubjson_size_value() (the type marker is known before the dimension vector
|
// get_ubjson_size_value() (the type marker is known before the dimension vector
|
||||||
// that determines size_and_type.first is read, so it is emitted first there to
|
// that determines size_and_type.first is read, so it is emitted first there to
|
||||||
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
|
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
|
||||||
|
// 'C' elements are read as unsigned bytes, but BJData limits a char to 0..127
|
||||||
|
const bool nd_is_char = size_and_type.second == 'C';
|
||||||
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
||||||
{
|
{
|
||||||
size_and_type.second = 'U';
|
size_and_type.second = 'U';
|
||||||
@@ -20620,6 +20759,24 @@ class binary_reader
|
|||||||
|
|
||||||
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
||||||
{
|
{
|
||||||
|
if (nd_is_char)
|
||||||
|
{
|
||||||
|
get();
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("char")))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(current > 127))
|
||||||
|
{
|
||||||
|
return last_byte_error(exception_id::invalid_string_or_size, "byte after 'C' must be in range 0x00..0x7F", "char", "");
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast<number_unsigned_t>(current))))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))
|
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -21597,9 +21754,9 @@ class binary_reader
|
|||||||
// sequence), UBJSON, BJData, or BSON requires a decoder to reject
|
// sequence), UBJSON, BJData, or BSON requires a decoder to reject
|
||||||
// ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
|
// ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
|
||||||
// rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
|
// rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
|
||||||
// @ref error_handler, applied once the whole string (all chunks of
|
// @ref error_handler, applied by @ref check_string_utf8 at the call
|
||||||
// an indefinite-length CBOR string included) has been assembled, by
|
// site to the whole string or, for an indefinite-length CBOR string,
|
||||||
// @ref check_string_utf8 at the call site.
|
// to each chunk on its own (RFC 8949, Section 3.2.3).
|
||||||
return get_bytes(len, "string", result);
|
return get_bytes(len, "string", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21794,6 +21951,12 @@ class binary_reader
|
|||||||
return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(bytes_read), ")"), "document");
|
return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(bytes_read), ")"), "document");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool bson_document_size_error(const char* prefix, const std::int32_t document_size, const char* suffix) const
|
||||||
|
{
|
||||||
|
return last_byte_error(exception_id::unexpected_byte, concat(prefix, std::to_string(document_size), suffix), "document");
|
||||||
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const
|
bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const
|
||||||
{
|
{
|
||||||
@@ -21899,6 +22062,44 @@ class binary_reader
|
|||||||
std::size_t bon8_pushback_size = 0;
|
std::size_t bon8_pushback_size = 0;
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
|
/*!
|
||||||
|
@brief check whether @a marker is a type marker that may follow '$'
|
||||||
|
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
|
||||||
|
(BJData's excluded markers are rejected separately).
|
||||||
|
*/
|
||||||
|
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
||||||
|
{
|
||||||
|
switch (marker)
|
||||||
|
{
|
||||||
|
case 'i':
|
||||||
|
case 'U':
|
||||||
|
case 'I':
|
||||||
|
case 'l':
|
||||||
|
case 'L':
|
||||||
|
case 'd':
|
||||||
|
case 'D':
|
||||||
|
case 'C':
|
||||||
|
return true;
|
||||||
|
case 'Z':
|
||||||
|
case 'N':
|
||||||
|
case 'T':
|
||||||
|
case 'F':
|
||||||
|
case 'H':
|
||||||
|
case 'S':
|
||||||
|
case '[':
|
||||||
|
case '{':
|
||||||
|
return input_format != input_format_t::bjdata;
|
||||||
|
case 'B':
|
||||||
|
case 'u':
|
||||||
|
case 'm':
|
||||||
|
case 'M':
|
||||||
|
case 'h':
|
||||||
|
return input_format == input_format_t::bjdata;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
||||||
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
||||||
@@ -21941,6 +22142,8 @@ class binary_reader
|
|||||||
return "uint8";
|
return "uint8";
|
||||||
case 'd':
|
case 'd':
|
||||||
return "single";
|
return "single";
|
||||||
|
case 'h':
|
||||||
|
return "half";
|
||||||
case 'i':
|
case 'i':
|
||||||
return "int8";
|
return "int8";
|
||||||
case 'l':
|
case 'l':
|
||||||
@@ -27931,7 +28134,15 @@ class binary_writer
|
|||||||
throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j);
|
throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sizeof(std::int32_t) + value.size() + 1ul;
|
// the "old binary" subtype 0x02 repeats the payload size in an inner
|
||||||
|
// int32 in front of the payload
|
||||||
|
return sizeof(std::int32_t) + (is_bson_old_binary(value) ? sizeof(std::int32_t) : 0ul) + value.size() + 1ul;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return whether @a value has the BSON subtype 0x02 ("binary (old)")
|
||||||
|
static bool is_bson_old_binary(const binary_t& value) noexcept
|
||||||
|
{
|
||||||
|
return value.has_subtype() && value.subtype() == 0x02;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -27944,10 +28155,18 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x05);
|
write_bson_entry_header(name, 0x05);
|
||||||
|
|
||||||
write_number<std::int32_t>(to_bson_length(value.size()), true);
|
const bool old_binary = is_bson_old_binary(value);
|
||||||
|
|
||||||
|
// the length field counts the inner int32 of the old binary subtype
|
||||||
|
write_number<std::int32_t>(to_bson_length(value.size() + (old_binary ? sizeof(std::int32_t) : 0ul)), true);
|
||||||
|
|
||||||
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
||||||
|
|
||||||
|
if (old_binary)
|
||||||
|
{
|
||||||
|
write_number<std::int32_t>(to_bson_length(value.size()), true);
|
||||||
|
}
|
||||||
|
|
||||||
write_bytes(value.data(), value.size());
|
write_bytes(value.data(), value.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28107,7 +28326,8 @@ class binary_writer
|
|||||||
(or the header of a string or binary value) in one reservation
|
(or the header of a string or binary value) in one reservation
|
||||||
@return whether the entry was written; false for a discarded value, a
|
@return whether the entry was written; false for a discarded value, a
|
||||||
string or binary value whose length does not fit the length
|
string or binary value whose length does not fit the length
|
||||||
field, in which case nothing was written
|
field, or a binary value with subtype 0x02, in which case nothing
|
||||||
|
was written
|
||||||
@pre @a name is at most bson_scalar_max_name_size characters long and is
|
@pre @a name is at most bson_scalar_max_name_size characters long and is
|
||||||
not changed by the error handler
|
not changed by the error handler
|
||||||
*/
|
*/
|
||||||
@@ -28168,7 +28388,9 @@ class binary_writer
|
|||||||
if (j.is_binary())
|
if (j.is_binary())
|
||||||
{
|
{
|
||||||
const binary_t& value = *j.m_data.m_value.binary;
|
const binary_t& value = *j.m_data.m_value.binary;
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size())))
|
// the old binary subtype 0x02 has an inner length that the single
|
||||||
|
// reservation has no room for; write_bson_binary writes it
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size()) || is_bson_old_binary(value)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -30076,7 +30298,7 @@ class binary_writer
|
|||||||
A C++11 `constexpr` function cannot contain a `switch`, so this is a plain
|
A C++11 `constexpr` function cannot contain a `switch`, so this is a plain
|
||||||
comparison chain instead; it is only reached once per ND-array candidate
|
comparison chain instead; it is only reached once per ND-array candidate
|
||||||
object. Keep in sync with binary_reader's `bjd_type_name()`, which maps
|
object. Keep in sync with binary_reader's `bjd_type_name()`, which maps
|
||||||
the other way.
|
the other way (it also reads "half", which is never written).
|
||||||
*/
|
*/
|
||||||
static CharType bjdata_ndarray_type_marker(const string_t& name)
|
static CharType bjdata_ndarray_type_marker(const string_t& name)
|
||||||
{
|
{
|
||||||
@@ -30284,10 +30506,13 @@ class binary_writer
|
|||||||
switch (dtype)
|
switch (dtype)
|
||||||
{
|
{
|
||||||
case 'U':
|
case 'U':
|
||||||
case 'C':
|
|
||||||
case 'B':
|
case 'B':
|
||||||
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
|
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
|
||||||
break;
|
break;
|
||||||
|
case 'C':
|
||||||
|
// BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges)
|
||||||
|
ok = bjdata_ndarray_value_in_range<std::int8_t>(el) && bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||||
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
|
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Generated
+438
-1390
File diff suppressed because it is too large.
Load diff
+126
-2
@@ -2916,6 +2916,35 @@ TEST_CASE("BJData")
|
|||||||
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
|
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray with _ArrayType_ \"char\" is limited to 0..127")
|
||||||
|
{
|
||||||
|
// both BJData drafts limit a char to 0..127; larger values must not
|
||||||
|
// be written as 'C' elements (the reader rejects them), so such an
|
||||||
|
// object stays a plain object that round-trips
|
||||||
|
for (const auto version :
|
||||||
|
{
|
||||||
|
json::bjdata_version_t::draft2, json::bjdata_version_t::draft3
|
||||||
|
})
|
||||||
|
{
|
||||||
|
json const j_ok = json({{"_ArrayType_", "char"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {0, 97, 98, 127}}});
|
||||||
|
const auto out_ok = json::to_bjdata(j_ok, true, true, version);
|
||||||
|
CHECK(out_ok == std::vector<uint8_t>({'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 2, 2, 0, 97, 98, 127}));
|
||||||
|
CHECK(json::from_bjdata(out_ok) == j_ok);
|
||||||
|
|
||||||
|
for (const int bad :
|
||||||
|
{
|
||||||
|
128, 200, 255, 256, -1
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(bad)
|
||||||
|
json const j_bad = json({{"_ArrayType_", "char"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {97, 98, 99, bad}}});
|
||||||
|
const auto out_bad = json::to_bjdata(j_bad, true, true, version);
|
||||||
|
CHECK(out_bad.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_bad) == j_bad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("ndarray annotation keys are read back in the documented order")
|
SECTION("ndarray annotation keys are read back in the documented order")
|
||||||
{
|
{
|
||||||
// from_bjdata() must emit the annotation object's keys in the order
|
// from_bjdata() must emit the annotation object's keys in the order
|
||||||
@@ -3134,7 +3163,7 @@ TEST_CASE("BJData")
|
|||||||
std::vector<uint8_t> const v1 = {'[', '#', 'i', 0xF1};
|
std::vector<uint8_t> const v1 = {'[', '#', 'i', 0xF1};
|
||||||
std::vector<uint8_t> const v2 = {'[', '$', 'I', '#', 'i', 0xF2};
|
std::vector<uint8_t> const v2 = {'[', '$', 'I', '#', 'i', 0xF2};
|
||||||
std::vector<uint8_t> const v3 = {'[', '$', 'I', '#', '[', 'i', 0xF4, 'i', 0x02, ']'};
|
std::vector<uint8_t> const v3 = {'[', '$', 'I', '#', '[', 'i', 0xF4, 'i', 0x02, ']'};
|
||||||
std::vector<uint8_t> const v4 = {'[', '$', 0xF6, '#', 'i', 0xF7};
|
std::vector<uint8_t> const v4 = {'[', '$', 'l', '#', 'i', 0xF7};
|
||||||
std::vector<uint8_t> const v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'};
|
std::vector<uint8_t> const v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'};
|
||||||
std::vector<uint8_t> const v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'};
|
std::vector<uint8_t> const v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'};
|
||||||
|
|
||||||
@@ -3324,6 +3353,44 @@ TEST_CASE("BJData")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray of half-precision floats can be read")
|
||||||
|
{
|
||||||
|
// the 'h' element type is read as _ArrayType_ "half"
|
||||||
|
json _;
|
||||||
|
const std::vector<uint8_t> half = {'[', '$', 'h', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 0x00, 0x3C, 0x00, 0x40, 0x00, 0x42, 0x00, 0x44};
|
||||||
|
const json j_half = json::parse(R"({"_ArrayType_":"half","_ArraySize_":[2,2],"_ArrayData_":[1.0,2.0,3.0,4.0]})");
|
||||||
|
CHECK(json::from_bjdata(half) == j_half);
|
||||||
|
|
||||||
|
// the writer does not pack "half": it writes a plain object that reads back equal
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(j_half)) == j_half);
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(j_half, true, true, json::bjdata_version_t::draft3)) == j_half);
|
||||||
|
|
||||||
|
// truncated half data
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bjdata(std::vector<uint8_t>(half.begin(), half.end() - 1)), json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray of char rejects values above 127")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
const std::vector<uint8_t> ok = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c', 0x7F};
|
||||||
|
CHECK(json::from_bjdata(ok) == json::parse(R"({"_ArrayType_":"char","_ArraySize_":[2,2],"_ArrayData_":[97,98,99,127]})"));
|
||||||
|
|
||||||
|
std::vector<uint8_t> bad = ok;
|
||||||
|
bad.back() = 0xC8;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bjdata(bad), json::parse_error&);
|
||||||
|
CHECK(json::from_bjdata(bad, true, false).is_discarded());
|
||||||
|
|
||||||
|
// the same restriction holds for 1-D arrays
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bjdata(std::vector<uint8_t>({'[', '$', 'C', '#', 'i', 1, 0xC8})), json::parse_error&);
|
||||||
|
|
||||||
|
// truncated input
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bjdata(std::vector<uint8_t>(ok.begin(), ok.end() - 1)), json::parse_error&);
|
||||||
|
|
||||||
|
// 'B' (byte) elements may use the full range
|
||||||
|
const std::vector<uint8_t> byte_nd = {'[', '$', 'B', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 1, 2, 3, 0xC8};
|
||||||
|
CHECK(json::from_bjdata(byte_nd) == json::parse(R"({"_ArrayType_":"byte","_ArraySize_":[2,2],"_ArrayData_":[1,2,3,200]})"));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("do not accept NTFZ markers in ndarray optimized type (with count)")
|
SECTION("do not accept NTFZ markers in ndarray optimized type (with count)")
|
||||||
{
|
{
|
||||||
json _;
|
json _;
|
||||||
@@ -3455,7 +3522,7 @@ TEST_CASE("BJData")
|
|||||||
CHECK(json::from_bjdata(v0, true, false).is_discarded());
|
CHECK(json::from_bjdata(v0, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vi = {'[', '$', '#'};
|
std::vector<uint8_t> const vi = {'[', '$', '#'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x23 is not a permitted optimized array type", json::parse_error&);
|
||||||
CHECK(json::from_bjdata(vi, true, false).is_discarded());
|
CHECK(json::from_bjdata(vi, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vU = {'[', '$', 'U'};
|
std::vector<uint8_t> const vU = {'[', '$', 'U'};
|
||||||
@@ -4622,3 +4689,60 @@ TEST_CASE("BJData large strings and binaries (chunked reader)")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BJData optimized container type marker is validated for count 0")
|
||||||
|
{
|
||||||
|
// the '$' type marker must be a legal marker even if no element follows
|
||||||
|
SECTION("invalid marker")
|
||||||
|
{
|
||||||
|
// 'Z', 'T', 'F', 'N', 'S', 'H', '[' and '{' are excluded in BJData
|
||||||
|
for (const char marker :
|
||||||
|
{'x', 'E', 'Z', 'T', 'F', 'N', 'S', 'H', '[', '{'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const char open :
|
||||||
|
{'[', '{'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const int count :
|
||||||
|
{
|
||||||
|
0, 1
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(marker)
|
||||||
|
CAPTURE(open)
|
||||||
|
CAPTURE(count)
|
||||||
|
const std::vector<std::uint8_t> v = {static_cast<std::uint8_t>(open), '$', static_cast<std::uint8_t>(marker), '#', 'i', static_cast<std::uint8_t>(count)};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&);
|
||||||
|
CHECK(json::from_bjdata(v, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("error message")
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> v = {'[', '$', 'x', '#', 'i', 0};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x78 is not a permitted optimized array type", json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("valid marker")
|
||||||
|
{
|
||||||
|
for (const char marker :
|
||||||
|
{'i', 'U', 'I', 'u', 'l', 'm', 'L', 'M', 'h', 'd', 'D', 'C'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(marker)
|
||||||
|
const std::vector<std::uint8_t> va = {'[', '$', static_cast<std::uint8_t>(marker), '#', 'i', 0};
|
||||||
|
const std::vector<std::uint8_t> vo = {'{', '$', static_cast<std::uint8_t>(marker), '#', 'i', 0};
|
||||||
|
CHECK(json::from_bjdata(va) == json::array());
|
||||||
|
CHECK(json::from_bjdata(vo) == json::object());
|
||||||
|
}
|
||||||
|
|
||||||
|
// an optimized array of bytes is read as a binary value
|
||||||
|
const std::vector<std::uint8_t> vb = {'[', '$', 'B', '#', 'i', 0};
|
||||||
|
CHECK(json::from_bjdata(vb) == json::binary({}));
|
||||||
|
}
|
||||||
|
}
|
||||||
+249
-17
@@ -802,12 +802,11 @@ TEST_CASE("BSON")
|
|||||||
CHECK(json::from_bson(result, true, false) == j);
|
CHECK(json::from_bson(result, true, false) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("binary member with subtype 0x02 (old binary) keeps its inner length prefix (lenient parsing)")
|
SECTION("binary member with subtype 0x02 (old binary) carries an inner length")
|
||||||
{
|
{
|
||||||
// documented lenient behavior (see gh-5333): the payload for
|
// the payload of binary subtype 0x02 ("old binary") starts with an
|
||||||
// binary subtype 0x02 ("old binary") is returned as-is,
|
// int32 that repeats the payload size; the bytes below are what
|
||||||
// including its own inner 4-byte length prefix; it is not
|
// pymongo and the bson-corpus (binary.json) use
|
||||||
// stripped or reinterpreted
|
|
||||||
std::vector<std::uint8_t> const input =
|
std::vector<std::uint8_t> const input =
|
||||||
{
|
{
|
||||||
0x17, 0x00, 0x00, 0x00, // size (little endian)
|
0x17, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
@@ -815,16 +814,87 @@ TEST_CASE("BSON")
|
|||||||
|
|
||||||
0x06, 0x00, 0x00, 0x00, // size of binary (little endian)
|
0x06, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||||
0x02, // "old binary" subtype
|
0x02, // "old binary" subtype
|
||||||
0x02, 0x00, 0x00, 0x00, // inner length prefix (part of the old-binary payload)
|
0x02, 0x00, 0x00, 0x00, // inner length
|
||||||
0x68, 0x69, // payload ('h', 'i')
|
0x68, 0x69, // payload ('h', 'i')
|
||||||
|
|
||||||
0x00 // end marker
|
0x00 // end marker
|
||||||
};
|
};
|
||||||
|
|
||||||
// the inner length prefix is part of the (unmodified) payload
|
// the inner length is not part of the payload
|
||||||
const std::vector<std::uint8_t> expected_payload = {0x02, 0x00, 0x00, 0x00, 0x68, 0x69};
|
const json expected = { { "entry", json::binary({0x68, 0x69}, 0x02) } };
|
||||||
const json expected = { { "entry", json::binary(expected_payload, 0x02) } };
|
|
||||||
CHECK(json::from_bson(input) == expected);
|
CHECK(json::from_bson(input) == expected);
|
||||||
|
CHECK(json::to_bson(expected) == input);
|
||||||
|
CHECK(json::from_bson(json::to_bson(expected)) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary member with subtype 0x02 (old binary) and empty payload")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x15, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary
|
||||||
|
0x04, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||||
|
0x02, // "old binary" subtype
|
||||||
|
0x00, 0x00, 0x00, 0x00, // inner length
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
const json expected = { { "entry", json::binary({}, 0x02) } };
|
||||||
|
CHECK(json::from_bson(input) == expected);
|
||||||
|
CHECK(json::to_bson(expected) == input);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary member with subtype 0x02 (old binary) with an invalid inner length")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> input =
|
||||||
|
{
|
||||||
|
0x17, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary
|
||||||
|
0x06, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||||
|
0x02, // "old binary" subtype
|
||||||
|
0x02, 0x00, 0x00, 0x00, // inner length
|
||||||
|
0x68, 0x69, // payload ('h', 'i')
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("inner length too long")
|
||||||
|
{
|
||||||
|
input[19] = 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("inner length too short")
|
||||||
|
{
|
||||||
|
input[19] = 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("inner length negative")
|
||||||
|
{
|
||||||
|
input[19] = 0xFF;
|
||||||
|
input[20] = 0xFF;
|
||||||
|
input[21] = 0xFF;
|
||||||
|
input[22] = 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAPTURE(input)
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary member with subtype 0x02 (old binary) without an inner length")
|
||||||
|
{
|
||||||
|
// the payload is shorter than the inner length field
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x14, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary
|
||||||
|
0x03, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||||
|
0x02, // "old binary" subtype
|
||||||
|
0x01, 0x02, 0x03, // payload without an inner length
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Some more complex document")
|
SECTION("Some more complex document")
|
||||||
@@ -1178,7 +1248,7 @@ TEST_CASE("BSON document size mismatch")
|
|||||||
SECTION("top-level document with a negative size")
|
SECTION("top-level document with a negative size")
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> const input = {0xFF, 0xFF, 0xFF, 0xFF, 0x00};
|
std::vector<std::uint8_t> const input = {0xFF, 0xFF, 0xFF, 0xFF, 0x00};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size -1 does not match the number of bytes read (5)", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BSON document: document size -1 is less than 5", json::parse_error&);
|
||||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1190,7 +1260,7 @@ TEST_CASE("BSON document size mismatch")
|
|||||||
// enclosing document instead of "d"
|
// enclosing document instead of "d"
|
||||||
std::vector<std::uint8_t> const input =
|
std::vector<std::uint8_t> const input =
|
||||||
{
|
{
|
||||||
0x00, 0x00, 0x00, 0x00, // outer size
|
0x14, 0x00, 0x00, 0x00, // outer size
|
||||||
0x03, 'd', 0x00, // entry: embedded document "d"
|
0x03, 'd', 0x00, // entry: embedded document "d"
|
||||||
0xFF, 0xFF, 0xFF, 0x7F, // embedded size 0x7FFFFFFF
|
0xFF, 0xFF, 0xFF, 0x7F, // embedded size 0x7FFFFFFF
|
||||||
0x0A, 'a', 0x00, // entry: null "a"
|
0x0A, 'a', 0x00, // entry: null "a"
|
||||||
@@ -1198,7 +1268,24 @@ TEST_CASE("BSON document size mismatch")
|
|||||||
0x08, 'h', 0x00, 0x01, // entry: bool "h" = true
|
0x08, 'h', 0x00, 0x01, // entry: bool "h" = true
|
||||||
0x00 // outer end marker
|
0x00 // outer end marker
|
||||||
};
|
};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON document: document size 2147483647 does not match the number of bytes read (8)", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 11: syntax error while parsing BSON document: document size 2147483647 exceeds the size of the enclosing document", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("embedded document whose size is smaller than its content")
|
||||||
|
{
|
||||||
|
// {"a":null} is 8 bytes, but the length prefix claims 7
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x14, 0x00, 0x00, 0x00, // outer size
|
||||||
|
0x03, 'd', 0x00, // entry: embedded document "d"
|
||||||
|
0x07, 0x00, 0x00, 0x00, // embedded size 7 (real is 8)
|
||||||
|
0x0A, 'a', 0x00, // entry: null "a"
|
||||||
|
0x00, // embedded end marker
|
||||||
|
0x08, 'h', 0x00, 0x01, // entry: bool "h" = true
|
||||||
|
0x00 // outer end marker
|
||||||
|
};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON document: document size 7 does not match the number of bytes read (8)", json::parse_error&);
|
||||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1207,18 +1294,163 @@ TEST_CASE("BSON document size mismatch")
|
|||||||
// array [42] is 12 bytes, but the length prefix claims 13
|
// array [42] is 12 bytes, but the length prefix claims 13
|
||||||
std::vector<std::uint8_t> const input =
|
std::vector<std::uint8_t> const input =
|
||||||
{
|
{
|
||||||
0x00, 0x00, 0x00, 0x00, // outer size
|
0x14, 0x00, 0x00, 0x00, // outer size
|
||||||
0x04, 'a', 0x00, // entry: array "a"
|
0x04, 'a', 0x00, // entry: array "a"
|
||||||
0x0D, 0x00, 0x00, 0x00, // array size 13 (real is 12)
|
0x0D, 0x00, 0x00, 0x00, // array size 13 (real is 12)
|
||||||
0x10, '0', 0x00, 0x2A, 0x00, 0x00, 0x00, // entry: int32 "0" = 42
|
0x10, '0', 0x00, 0x2A, 0x00, 0x00, 0x00, // entry: int32 "0" = 42
|
||||||
0x00, // array end marker
|
0x00, // array end marker
|
||||||
0x00 // outer end marker
|
0x00 // outer end marker
|
||||||
};
|
};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 13 does not match the number of bytes read (12)", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 11: syntax error while parsing BSON document: document size 13 exceeds the size of the enclosing document", json::parse_error&);
|
||||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BSON declared document size bounds the content")
|
||||||
|
{
|
||||||
|
// records the events that a SAX parser receives
|
||||||
|
struct recorder : nlohmann::json_sax<json>
|
||||||
|
{
|
||||||
|
std::string events;
|
||||||
|
|
||||||
|
bool null() override
|
||||||
|
{
|
||||||
|
events += "null ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool boolean(bool /*val*/) override
|
||||||
|
{
|
||||||
|
events += "boolean ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool number_integer(json::number_integer_t /*val*/) override
|
||||||
|
{
|
||||||
|
events += "integer ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool number_unsigned(json::number_unsigned_t /*val*/) override
|
||||||
|
{
|
||||||
|
events += "unsigned ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override
|
||||||
|
{
|
||||||
|
events += "float ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool string(std::string& /*val*/) override
|
||||||
|
{
|
||||||
|
events += "string ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool binary(json::binary_t& /*val*/) override
|
||||||
|
{
|
||||||
|
events += "binary ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool start_object(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
events += "{ ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool key(std::string& /*val*/) override
|
||||||
|
{
|
||||||
|
events += "key ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool end_object() override
|
||||||
|
{
|
||||||
|
events += "} ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool start_array(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
events += "[ ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool end_array() override
|
||||||
|
{
|
||||||
|
events += "] ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||||
|
{
|
||||||
|
events += "error ";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("the content of a too small document is not reported")
|
||||||
|
{
|
||||||
|
// {"a":1} is 12 bytes; the length prefix claims 5, so the element
|
||||||
|
// does not fit
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x05, 0x00, 0x00, 0x00,
|
||||||
|
0x10, 'a', 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
recorder rec;
|
||||||
|
CHECK(!json::sax_parse(input, &rec, json::input_format_t::bson));
|
||||||
|
CHECK(rec.events == "{ error ");
|
||||||
|
|
||||||
|
// also without strict mode
|
||||||
|
recorder rec2;
|
||||||
|
CHECK(!json::sax_parse(input, &rec2, json::input_format_t::bson, false));
|
||||||
|
CHECK(rec2.events == "{ error ");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("an embedded document that does not fit its parent is not opened")
|
||||||
|
{
|
||||||
|
// the embedded document ends where the enclosing one does, so there
|
||||||
|
// is no room for the terminator of the enclosing document
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x0C, 0x00, 0x00, 0x00,
|
||||||
|
0x03, 'd', 0x00,
|
||||||
|
0x05, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
recorder rec;
|
||||||
|
CHECK(!json::sax_parse(input, &rec, json::input_format_t::bson));
|
||||||
|
CHECK(rec.events == "{ key error ");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("an embedded document that fits exactly")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x0D, 0x00, 0x00, 0x00,
|
||||||
|
0x03, 'd', 0x00,
|
||||||
|
0x05, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
CHECK(json::from_bson(input) == json({{"d", json::object()}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a document size that is less than 5")
|
||||||
|
{
|
||||||
|
for (const std::int32_t size :
|
||||||
|
{
|
||||||
|
0, 1, 4, -1, (std::numeric_limits<std::int32_t>::min)()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(size)
|
||||||
|
std::vector<std::uint8_t> input =
|
||||||
|
{
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
input[0] = static_cast<std::uint8_t>(size & 0xFF);
|
||||||
|
input[1] = static_cast<std::uint8_t>((size >> 8) & 0xFF);
|
||||||
|
input[2] = static_cast<std::uint8_t>((size >> 16) & 0xFF);
|
||||||
|
input[3] = static_cast<std::uint8_t>((size >> 24) & 0xFF);
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&);
|
||||||
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("BSON nesting does not consume the call stack")
|
TEST_CASE("BSON nesting does not consume the call stack")
|
||||||
{
|
{
|
||||||
// An embedded document or array used to be read by calling back into the
|
// An embedded document or array used to be read by calling back into the
|
||||||
@@ -1923,7 +2155,7 @@ TEST_CASE("Invalid document size handling")
|
|||||||
{
|
{
|
||||||
std::vector<std::uint8_t> const v = {0x04, 0x00, 0x00, 0x00, 0x00};
|
std::vector<std::uint8_t> const v = {0x04, 0x00, 0x00, 0x00, 0x00};
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 4 does not match the number of bytes read (5)", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BSON document: document size 4 is less than 5", json::parse_error&);
|
||||||
CHECK(json::from_bson(v, true, false).is_discarded());
|
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1938,7 +2170,7 @@ TEST_CASE("Invalid document size handling")
|
|||||||
0x00
|
0x00
|
||||||
};
|
};
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 16: syntax error while parsing BSON document: document size 5 does not match the number of bytes read (16)", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 5 is too small for its content", json::parse_error&);
|
||||||
CHECK(json::from_bson(v, true, false).is_discarded());
|
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1969,7 +2201,7 @@ TEST_CASE("Invalid document size handling")
|
|||||||
0x00 // object terminator
|
0x00 // object terminator
|
||||||
};
|
};
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 5 does not match the number of bytes read (12)", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 12: syntax error while parsing BSON document: document size 5 is too small for its content", json::parse_error&);
|
||||||
CHECK(json::from_bson(v, true, false).is_discarded());
|
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+148
-3
@@ -1807,6 +1807,93 @@ TEST_CASE("CBOR")
|
|||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("tag in front of a key")
|
||||||
|
{
|
||||||
|
const auto ignore = json::cbor_tag_handler_t::ignore;
|
||||||
|
const auto store = json::cbor_tag_handler_t::store;
|
||||||
|
const json expected = {{"a", 10}};
|
||||||
|
|
||||||
|
// the tags are skipped in front of a text string key, like in
|
||||||
|
// front of a value
|
||||||
|
const std::vector<std::vector<std::uint8_t>> tagged =
|
||||||
|
{
|
||||||
|
{0xA1, 0xC0, 0x61, 0x61, 0x0A}, // tag in the head
|
||||||
|
{0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A}, // 1-byte tag number
|
||||||
|
{0xA1, 0xD9, 0x01, 0x2C, 0x61, 0x61, 0x0A}, // 2-byte tag number
|
||||||
|
{0xA1, 0xDA, 0x00, 0x01, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 4-byte tag number
|
||||||
|
{0xA1, 0xDB, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 8-byte tag number
|
||||||
|
{0xA1, 0xC0, 0xD8, 0x05, 0xC1, 0x61, 0x61, 0x0A}, // chained tags
|
||||||
|
{0xBF, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // indefinite-length map
|
||||||
|
{0xBF, 0x61, 0x62, 0x01, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // second key tagged
|
||||||
|
{0xA1, 0xC0, 0x7F, 0x61, 0x61, 0xFF, 0x0A}, // indefinite-length text key
|
||||||
|
{0xA1, 0xC0, 0x61, 0x61, 0xC0, 0x0A}, // tagged key and value
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& input : tagged)
|
||||||
|
{
|
||||||
|
CAPTURE(input)
|
||||||
|
json const j_ignore = json::from_cbor(input, true, true, ignore);
|
||||||
|
json const j_store = json::from_cbor(input, true, true, store);
|
||||||
|
CHECK(j_ignore.at("a") == 10);
|
||||||
|
CHECK(j_store.at("a") == 10);
|
||||||
|
CHECK(j_ignore == j_store);
|
||||||
|
}
|
||||||
|
CHECK(json::from_cbor(tagged[0], true, true, ignore) == expected);
|
||||||
|
CHECK(json::from_cbor(tagged[1], true, true, store) == expected);
|
||||||
|
|
||||||
|
// nested maps
|
||||||
|
CHECK(json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x61, 0x61, 0xA1, 0xD8, 0x05, 0x61, 0x62, 0x01}), true, true, ignore) == json{{"a", {{"b", 1}}}});
|
||||||
|
|
||||||
|
// the tag handler error still rejects every tag
|
||||||
|
for (const auto& input : tagged)
|
||||||
|
{
|
||||||
|
CAPTURE(input)
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_cbor(input), json::parse_error&);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A})), "[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 tag; last byte: 0xD8", json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a tag does not turn a key of another type into a text string
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x01, 0x01}), true, true, ignore), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8, 0x05, 0x41, 0x61, 0x01}), true, true, store), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR object key: only string keys are supported, but found a byte string; last byte: 0x41", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x01, 0x01}), true, false, ignore).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of input after the tag, or inside the tag number
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0}), true, true, ignore), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8}), true, true, ignore), json::parse_error&);
|
||||||
|
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xDB, 0x00, 0x00}), true, true, store), json::parse_error&);
|
||||||
|
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xBF, 0xC0}), true, true, store), json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAX: the key event is reported without the tag (start_object,
|
||||||
|
// key, number_unsigned, end_object are four events)
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> input = {0xA1, 0xC0, 0x61, 0x61, 0x0A};
|
||||||
|
SaxCountdown four(4);
|
||||||
|
CHECK(json::sax_parse(input, &four, json::input_format_t::cbor, true, false, false, ignore));
|
||||||
|
SaxCountdown three(3);
|
||||||
|
CHECK(!json::sax_parse(input, &three, json::input_format_t::cbor, true, false, false, ignore));
|
||||||
|
SaxCountdown error_handler(4);
|
||||||
|
CHECK(!json::sax_parse(input, &error_handler, json::input_format_t::cbor));
|
||||||
|
}
|
||||||
|
|
||||||
|
// a long chain of tags needs no recursion
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> input = {0xA1};
|
||||||
|
input.insert(input.end(), 100000, 0xC0);
|
||||||
|
input.insert(input.end(), {0x61, 0x61, 0x0A});
|
||||||
|
CHECK(json::from_cbor(input, true, true, ignore) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("non-string key (see #2766 and #3381)")
|
SECTION("non-string key (see #2766 and #3381)")
|
||||||
{
|
{
|
||||||
// only text strings map to JSON object keys; any other key is
|
// only text strings map to JSON object keys; any other key is
|
||||||
@@ -1915,9 +2002,10 @@ TEST_CASE("CBOR")
|
|||||||
{
|
{
|
||||||
json _;
|
json _;
|
||||||
|
|
||||||
// the chunks are concatenated as is, without checking that each
|
// with the default error handler keep, the chunks are concatenated
|
||||||
// chunk is valid UTF-8 on its own (RFC 8949, Section 3.2.3), so
|
// as is, without checking that each chunk is valid UTF-8 on its
|
||||||
// a code point split across two chunks yields a valid string
|
// own (RFC 8949, Section 3.2.3), so a code point split across two
|
||||||
|
// chunks yields a valid string
|
||||||
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})));
|
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})));
|
||||||
CHECK(_ == "\xc3\xa9");
|
CHECK(_ == "\xc3\xa9");
|
||||||
CHECK(_.dump() == "\"\xc3\xa9\"");
|
CHECK(_.dump() == "\"\xc3\xa9\"");
|
||||||
@@ -1937,6 +2025,63 @@ TEST_CASE("CBOR")
|
|||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6");
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed UTF-8 in a chunk of an indefinite-length string (RFC 8949, Section 3.2.3)")
|
||||||
|
{
|
||||||
|
const auto strict = json::error_handler_t::strict;
|
||||||
|
const auto replace = json::error_handler_t::replace;
|
||||||
|
const auto ignore = json::error_handler_t::ignore;
|
||||||
|
const auto keep = json::error_handler_t::keep;
|
||||||
|
const auto tags = json::cbor_tag_handler_t::error;
|
||||||
|
|
||||||
|
// a code point split over two chunks
|
||||||
|
const std::vector<std::uint8_t> split = {0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff};
|
||||||
|
// a 4-byte code point split over two chunks
|
||||||
|
const std::vector<std::uint8_t> split_emoji = {0x7f, 0x62, 0xf0, 0x9f, 0x62, 0x98, 0x80, 0xff};
|
||||||
|
// the same in an object key
|
||||||
|
const std::vector<std::uint8_t> split_key = {0xa1, 0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff, 0x01};
|
||||||
|
// a valid chunk followed by one with a truncated code point
|
||||||
|
const std::vector<std::uint8_t> truncated = {0x7f, 0x62, 0x41, 0xc3, 0x61, 0xa9, 0xff};
|
||||||
|
|
||||||
|
// strict: the string is invalid
|
||||||
|
for (const auto& input :
|
||||||
|
{
|
||||||
|
split, split_emoji, split_key, truncated
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(input)
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_cbor(input, true, true, tags, strict), json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(input, true, false, tags, strict).is_discarded());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(split, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(split_key, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace and ignore: every chunk is sanitized on its own
|
||||||
|
CHECK(json::from_cbor(split, true, true, tags, replace) == "\xEF\xBF\xBD\xEF\xBF\xBD");
|
||||||
|
CHECK(json::from_cbor(split, true, true, tags, ignore) == "");
|
||||||
|
CHECK(json::from_cbor(truncated, true, true, tags, replace) == "A\xEF\xBF\xBD\xEF\xBF\xBD");
|
||||||
|
CHECK(json::from_cbor(truncated, true, true, tags, ignore) == "A");
|
||||||
|
CHECK(json::from_cbor(split_key, true, true, tags, replace) == json{{"\xEF\xBF\xBD\xEF\xBF\xBD", 1}});
|
||||||
|
|
||||||
|
// keep: unchanged, the chunks are concatenated as is
|
||||||
|
CHECK(json::from_cbor(split, true, true, tags, keep) == "\xc3\xa9");
|
||||||
|
CHECK(json::from_cbor(split_emoji, true, true, tags, keep) == "\xf0\x9f\x98\x80");
|
||||||
|
|
||||||
|
// conforming input is unaffected by the error handler
|
||||||
|
const std::vector<std::uint8_t> valid = {0x7f, 0x62, 0xc3, 0xa9, 0x64, 0xf0, 0x9f, 0x98, 0x80, 0x60, 0xff};
|
||||||
|
for (const auto handler :
|
||||||
|
{
|
||||||
|
strict, replace, ignore, keep
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(json::from_cbor(valid, true, true, tags, handler) == "\xc3\xa9\xf0\x9f\x98\x80");
|
||||||
|
CHECK(json::from_cbor(std::vector<std::uint8_t>({0x7f, 0xff}), true, true, tags, handler) == "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("many chunks in indefinite-length string")
|
SECTION("many chunks in indefinite-length string")
|
||||||
{
|
{
|
||||||
constexpr std::size_t chunks = 100000;
|
constexpr std::size_t chunks = 100000;
|
||||||
|
|||||||
@@ -1,364 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json_view.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
using nlohmann::ordered_json;
|
|
||||||
using nlohmann::json_document;
|
|
||||||
using nlohmann::ordered_json_editable_document;
|
|
||||||
using nlohmann::json_view;
|
|
||||||
using nlohmann::ordered_json_document;
|
|
||||||
|
|
||||||
#include "json_view_test_helpers.hpp"
|
|
||||||
using json_view_test::generator;
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// These tests were split off unit-json_view_dump.cpp, whose object file got too
|
|
||||||
// large for the MinGW assembler ("file too big"). Like that file, this one is
|
|
||||||
// also built with C++17, as it mentions JSON_HAS_CPP_17.
|
|
||||||
|
|
||||||
TEST_CASE("json_view comparison")
|
|
||||||
{
|
|
||||||
SECTION("equality of the values parse() produces")
|
|
||||||
{
|
|
||||||
generator g;
|
|
||||||
std::vector<std::string> texts;
|
|
||||||
for (int i = 0; i < 600; ++i)
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
g.value(text, 0);
|
|
||||||
texts.push_back(text);
|
|
||||||
// the same value written differently: sorted keys, canonical numbers
|
|
||||||
texts.push_back(json::parse(text).dump(1));
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i + 2 < texts.size(); ++i)
|
|
||||||
{
|
|
||||||
for (std::size_t k = i; k < i + 3; ++k)
|
|
||||||
{
|
|
||||||
CAPTURE(texts[i])
|
|
||||||
CAPTURE(texts[k])
|
|
||||||
const json_document a = json_document::parse(texts[i]);
|
|
||||||
const json_document b = json_document::parse(texts[k]);
|
|
||||||
const json ja = json::parse(texts[i]);
|
|
||||||
const json jb = json::parse(texts[k]);
|
|
||||||
CHECK((a.root() == b.root()) == (ja == jb));
|
|
||||||
CHECK((a.root() != b.root()) == (ja != jb));
|
|
||||||
CHECK((a.root() == jb) == (ja == jb));
|
|
||||||
CHECK((jb == a.root()) == (ja == jb));
|
|
||||||
CHECK((a.root() != jb) == (ja != jb));
|
|
||||||
CHECK((jb != a.root()) == (ja != jb));
|
|
||||||
|
|
||||||
// ordered_json compares members in order
|
|
||||||
const ordered_json_document oa = ordered_json_document::parse(texts[i]);
|
|
||||||
const ordered_json_document ob = ordered_json_document::parse(texts[k]);
|
|
||||||
const ordered_json oja = ordered_json::parse(texts[i]);
|
|
||||||
const ordered_json ojb = ordered_json::parse(texts[k]);
|
|
||||||
CHECK((oa.root() == ob.root()) == (oja == ojb));
|
|
||||||
CHECK((oa.root() == ojb) == (oja == ojb));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("objects whose first keys differ or repeat")
|
|
||||||
{
|
|
||||||
const std::array<const char*, 17> texts =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
R"({"a":1,"b":2})", R"({"a":1,"c":2})", R"({"b":1,"a":2})", R"({"a":1,"a":2,"b":3})", R"({"a":2,"b":3})",
|
|
||||||
R"({"a":1,"b":1,"a":2})", R"({"a":2,"b":1})", R"({"a":1,"a":2})", R"({"b":1})", R"({"a":1,"b":2,"c":3,"d":4,"e":5})",
|
|
||||||
R"({"a":1,"b":2,"c":3,"d":4,"f":5})", R"({"a":1,"b":2,"c":3,"a":4,"e":5})", R"({"a":4,"b":2,"c":3,"e":5})",
|
|
||||||
R"({"\u0061":1,"b":2})", R"({})", R"([{"a":1,"b":2}])", R"([{"a":1,"c":2}])"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (const char* const x : texts)
|
|
||||||
{
|
|
||||||
for (const char* const y : texts)
|
|
||||||
{
|
|
||||||
CAPTURE(x)
|
|
||||||
CAPTURE(y)
|
|
||||||
const ordered_json_document dx = ordered_json_document::parse(x);
|
|
||||||
const ordered_json_document dy = ordered_json_document::parse(y);
|
|
||||||
const ordered_json_editable_document ex = ordered_json_editable_document::parse(x);
|
|
||||||
const ordered_json jx = ordered_json::parse(x);
|
|
||||||
const ordered_json jy = ordered_json::parse(y);
|
|
||||||
CHECK((dx.root() == dy.root()) == (jx == jy));
|
|
||||||
CHECK((dx.root() == jy) == (jx == jy));
|
|
||||||
CHECK((ex.root() == dy.root()) == (jx == jy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("objects with duplicate keys, small and large")
|
|
||||||
{
|
|
||||||
// n members with keys in descending order; with duplicates, some keys
|
|
||||||
// repeat later (the first after unique ones in key order); in reverse,
|
|
||||||
// the members are in ascending key order
|
|
||||||
const auto object = [](int n, bool duplicates, bool reverse, int value)
|
|
||||||
{
|
|
||||||
std::string s = "{";
|
|
||||||
for (int i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
const int k = reverse ? i : n - 1 - i;
|
|
||||||
s += (i != 0 ? ",\"k" : "\"k") + std::to_string(100 + k) + "\":" + std::to_string(value + k);
|
|
||||||
}
|
|
||||||
if (duplicates)
|
|
||||||
{
|
|
||||||
for (int k = n / 2; k < n; k += 3)
|
|
||||||
{
|
|
||||||
s += ",\"k" + std::to_string(100 + k) + "\":" + std::to_string(value + k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s + "}";
|
|
||||||
};
|
|
||||||
std::vector<std::string> texts;
|
|
||||||
for (const int n :
|
|
||||||
{
|
|
||||||
3, 16, 17, 40
|
|
||||||
})
|
|
||||||
{
|
|
||||||
for (const int flags :
|
|
||||||
{
|
|
||||||
0, 1, 2, 3
|
|
||||||
})
|
|
||||||
{
|
|
||||||
texts.push_back(object(n, (flags & 1) != 0, (flags & 2) != 0, 0));
|
|
||||||
}
|
|
||||||
texts.push_back(object(n, true, false, 1)); // other values
|
|
||||||
}
|
|
||||||
// many objects in one comparison (working memory reused across objects)
|
|
||||||
std::string array = "[";
|
|
||||||
std::string array_other = "[";
|
|
||||||
for (std::size_t i = 0; i < texts.size(); ++i)
|
|
||||||
{
|
|
||||||
array += (i != 0 ? "," : "") + texts[i];
|
|
||||||
array_other += (i != 0 ? "," : "") + texts[texts.size() - 1 - i];
|
|
||||||
}
|
|
||||||
texts.push_back(array + "]");
|
|
||||||
texts.push_back(array_other + "]");
|
|
||||||
for (const std::string& x : texts)
|
|
||||||
{
|
|
||||||
for (const std::string& y : texts)
|
|
||||||
{
|
|
||||||
CAPTURE(x)
|
|
||||||
CAPTURE(y)
|
|
||||||
const ordered_json_document dx = ordered_json_document::parse(x);
|
|
||||||
const ordered_json_document dy = ordered_json_document::parse(y);
|
|
||||||
const ordered_json jx = ordered_json::parse(x);
|
|
||||||
const ordered_json jy = ordered_json::parse(y);
|
|
||||||
CHECK((dx.root() == dy.root()) == (jx == jy));
|
|
||||||
CHECK((dx.root() == jy) == (jx == jy));
|
|
||||||
const json_document ux = json_document::parse(x);
|
|
||||||
const json uy = json::parse(y);
|
|
||||||
CHECK((ux.root() == uy) == (json::parse(x) == uy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("numbers, duplicate keys, member order")
|
|
||||||
{
|
|
||||||
const auto same = [](const char* x, const char* y)
|
|
||||||
{
|
|
||||||
const json_document dx = json_document::parse(x);
|
|
||||||
const json_document dy = json_document::parse(y);
|
|
||||||
return dx.root() == dy.root();
|
|
||||||
};
|
|
||||||
CHECK(same("1", "1.0"));
|
|
||||||
CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]"));
|
|
||||||
CHECK(!same("1", "1.5"));
|
|
||||||
CHECK(same("18446744073709551615", "18446744073709551615"));
|
|
||||||
CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})"));
|
|
||||||
CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})"));
|
|
||||||
CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})"));
|
|
||||||
CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})"));
|
|
||||||
CHECK(!same("[1, 2]", "[2, 1]"));
|
|
||||||
CHECK(!same("\"a\"", "\"b\""));
|
|
||||||
CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\""));
|
|
||||||
CHECK(!same("null", "false"));
|
|
||||||
CHECK(!same("[]", "{}"));
|
|
||||||
const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
|
||||||
const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
|
||||||
CHECK(dup.root() == last.root());
|
|
||||||
// (== compares as parse() resolves duplicates, a lookup finds the first)
|
|
||||||
CHECK(dup.root()["a"].materialize() == 1);
|
|
||||||
CHECK(dup.root()["a"] != last.root()["a"]);
|
|
||||||
const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
|
||||||
const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
|
||||||
CHECK(ab.root() != ba.root());
|
|
||||||
|
|
||||||
// discarded values compare as basic_json's do
|
|
||||||
const json discarded(json::value_t::discarded);
|
|
||||||
CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested
|
|
||||||
CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty)
|
|
||||||
const json_document null_document = json_document::parse("null");
|
|
||||||
CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty)
|
|
||||||
CHECK(!(null_document.root() == discarded));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("values that are the same text, or the same value written differently, at any depth")
|
|
||||||
{
|
|
||||||
// nested arrays and objects around depths where the walk over equal text gives up
|
|
||||||
for (const int depth :
|
|
||||||
{
|
|
||||||
1, 5, 47, 48, 49, 50, 60, 300
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(depth)
|
|
||||||
std::string head;
|
|
||||||
std::string tail;
|
|
||||||
for (int i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
head += i % 2 == 0 ? R"({"k":[1,"s",{"x":true},)" : R"([null,2.5,{"y":)";
|
|
||||||
tail.insert(0, i % 2 == 0 ? "]}" : "}]");
|
|
||||||
}
|
|
||||||
// the same text, one number changed, and one number written differently
|
|
||||||
const auto with_number = [&](const char* number)
|
|
||||||
{
|
|
||||||
std::string result = head;
|
|
||||||
result += number;
|
|
||||||
result += tail;
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
const std::string text = with_number("7");
|
|
||||||
const std::string changed = with_number("8");
|
|
||||||
const std::string spelled = with_number("7.0");
|
|
||||||
const json_document a = json_document::parse(text);
|
|
||||||
const json_document b = json_document::parse(text);
|
|
||||||
const json_document c = json_document::parse(changed);
|
|
||||||
const json_document d = json_document::parse(spelled);
|
|
||||||
CHECK(a.root() == b.root());
|
|
||||||
CHECK(a.root() != c.root());
|
|
||||||
CHECK(c.root() != a.root());
|
|
||||||
CHECK(a.root() == d.root());
|
|
||||||
CHECK(d.root() == a.root());
|
|
||||||
CHECK(a.root() == json::parse(text));
|
|
||||||
CHECK(a.root() != json::parse(changed));
|
|
||||||
CHECK(json::parse(spelled) == a.root());
|
|
||||||
const nlohmann::json_editable_document e = nlohmann::json_editable_document::parse(text);
|
|
||||||
CHECK(a.root() == e.root());
|
|
||||||
CHECK(e.root() == a.root());
|
|
||||||
CHECK(c.root() != e.root());
|
|
||||||
CHECK(e.root() == e.root());
|
|
||||||
const ordered_json_document oa = ordered_json_document::parse(text);
|
|
||||||
const ordered_json_document oc = ordered_json_document::parse(changed);
|
|
||||||
CHECK(oa.root() != oc.root());
|
|
||||||
CHECK(oa.root() == ordered_json::parse(text));
|
|
||||||
CHECK(oc.root() != ordered_json::parse(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
// not the same text, but the same value
|
|
||||||
const auto same = [](const char* x, const char* y)
|
|
||||||
{
|
|
||||||
const json_document dx = json_document::parse(x);
|
|
||||||
const json_document dy = json_document::parse(y);
|
|
||||||
const bool equal = dx.root() == dy.root();
|
|
||||||
CHECK(equal == (json::parse(x) == json::parse(y)));
|
|
||||||
CHECK(equal == (dx.root() == json::parse(y)));
|
|
||||||
return equal;
|
|
||||||
};
|
|
||||||
CHECK(same(R"({"a":1,"a":2})", R"({"a":3,"a":2})"));
|
|
||||||
CHECK(same(R"({"a":1,"b":[1,{"c":2,"c":3}]})", R"({"b":[1,{"c":9,"c":3}],"a":1})"));
|
|
||||||
CHECK(!same(R"({"a":1,"a":2})", R"({"a":2,"a":3})"));
|
|
||||||
CHECK(same("[1.0,-0,0,1e2]", "[1.00,0,-0,100.0]"));
|
|
||||||
CHECK(same(R"(["\u00e9","\n"])", "[\"\xc3\xa9\",\"\\n\"]"));
|
|
||||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":false}]})"));
|
|
||||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"c":null}]})"));
|
|
||||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":null,"c":1}]})"));
|
|
||||||
|
|
||||||
// edited documents: moved sequences, links, and values of new nodes
|
|
||||||
nlohmann::json_editable_document edited = nlohmann::json_editable_document::parse(R"({"a":[1,2,3],"b":{"c":"d"},"e":0.5})");
|
|
||||||
const json_document plain = json_document::parse(R"({"a":[1,2,3,4],"b":{"c":"x","y":null},"e":0.5,"z":[]})");
|
|
||||||
edited.push_back(edited.root()["a"], 4);
|
|
||||||
edited.set(edited.root()["b"], "c", "x");
|
|
||||||
edited.set(edited.root()["b"], "y", nullptr);
|
|
||||||
edited.set(edited.root(), "z", json::array());
|
|
||||||
CHECK(edited.root() == plain.root());
|
|
||||||
CHECK(plain.root() == edited.root());
|
|
||||||
edited.erase(edited.root()["a"], 0);
|
|
||||||
CHECK(edited.root() != plain.root());
|
|
||||||
CHECK(edited.root()["a"] == json::parse("[2,3,4]"));
|
|
||||||
const json_document tail = json_document::parse("[2,3,4]");
|
|
||||||
CHECK(edited.root()["a"] == tail.root());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("large documents, with a difference at any place")
|
|
||||||
{
|
|
||||||
const auto make = [](std::size_t count, std::size_t changed, bool swap_members)
|
|
||||||
{
|
|
||||||
std::string text = R"({"b":1,"list":[)";
|
|
||||||
for (std::size_t i = 0; i < count; ++i)
|
|
||||||
{
|
|
||||||
text += (i != 0 ? "," : "");
|
|
||||||
const std::string id = std::to_string(i == changed ? i + 1 : i);
|
|
||||||
if (swap_members && i % 3 == 0)
|
|
||||||
{
|
|
||||||
text += R"({"v":[1.5,"s",{"k":)";
|
|
||||||
text += id;
|
|
||||||
text += R"(}],"id":)";
|
|
||||||
text += id;
|
|
||||||
text += "}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text += R"({"id":)";
|
|
||||||
text += id;
|
|
||||||
text += R"(,"v":[1.5,"s",{"k":)";
|
|
||||||
text += id;
|
|
||||||
text += "}]}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text + R"(],"a":{"y":2,"x":3}})";
|
|
||||||
};
|
|
||||||
const std::size_t count = 3000;
|
|
||||||
for (const std::size_t changed :
|
|
||||||
{
|
|
||||||
count, std::size_t{0}, std::size_t{1}, count / 2, count - 1
|
|
||||||
})
|
|
||||||
{
|
|
||||||
for (const bool swap :
|
|
||||||
{
|
|
||||||
false, true
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(changed)
|
|
||||||
CAPTURE(swap)
|
|
||||||
const std::string text_a = make(count, count, false);
|
|
||||||
const std::string text_b = make(count, changed, swap);
|
|
||||||
const json_document a = json_document::parse(text_a);
|
|
||||||
const json_document b = json_document::parse(text_b);
|
|
||||||
const bool equal = changed == count;
|
|
||||||
CHECK((a.root() == b.root()) == equal);
|
|
||||||
CHECK((b.root() == a.root()) == equal);
|
|
||||||
CHECK((a.root() == json::parse(text_b)) == equal);
|
|
||||||
CHECK((json::parse(text_b) == a.root()) == equal);
|
|
||||||
CHECK((a.root()["list"] == b.root()["list"]) == equal);
|
|
||||||
const ordered_json_document oa = ordered_json_document::parse(text_a);
|
|
||||||
const ordered_json_document ob = ordered_json_document::parse(text_b);
|
|
||||||
CHECK((oa.root() == ob.root()) == (equal && !swap));
|
|
||||||
CHECK((oa.root() == ordered_json::parse(text_b)) == (equal && !swap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep nesting")
|
|
||||||
{
|
|
||||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
|
||||||
const json_document a = json_document::parse(deep);
|
|
||||||
const json_document b = json_document::parse(deep);
|
|
||||||
CHECK(a.root() == b.root());
|
|
||||||
CHECK(a.root() == json::parse(deep));
|
|
||||||
const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']');
|
|
||||||
const json_document c = json_document::parse(other);
|
|
||||||
CHECK(a.root() != c.root());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -432,6 +432,101 @@ TEST_CASE("json_view dump")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("json_view comparison")
|
||||||
|
{
|
||||||
|
SECTION("equality of the values parse() produces")
|
||||||
|
{
|
||||||
|
generator g;
|
||||||
|
std::vector<std::string> texts;
|
||||||
|
for (int i = 0; i < 600; ++i)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
g.value(text, 0);
|
||||||
|
texts.push_back(text);
|
||||||
|
// the same value written differently: sorted keys, canonical numbers
|
||||||
|
texts.push_back(json::parse(text).dump(1));
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i + 2 < texts.size(); ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = i; k < i + 3; ++k)
|
||||||
|
{
|
||||||
|
CAPTURE(texts[i])
|
||||||
|
CAPTURE(texts[k])
|
||||||
|
const json_document a = json_document::parse(texts[i]);
|
||||||
|
const json_document b = json_document::parse(texts[k]);
|
||||||
|
const json ja = json::parse(texts[i]);
|
||||||
|
const json jb = json::parse(texts[k]);
|
||||||
|
CHECK((a.root() == b.root()) == (ja == jb));
|
||||||
|
CHECK((a.root() != b.root()) == (ja != jb));
|
||||||
|
CHECK((a.root() == jb) == (ja == jb));
|
||||||
|
CHECK((jb == a.root()) == (ja == jb));
|
||||||
|
CHECK((a.root() != jb) == (ja != jb));
|
||||||
|
CHECK((jb != a.root()) == (ja != jb));
|
||||||
|
|
||||||
|
// ordered_json compares members in order
|
||||||
|
const ordered_json_document oa = ordered_json_document::parse(texts[i]);
|
||||||
|
const ordered_json_document ob = ordered_json_document::parse(texts[k]);
|
||||||
|
const ordered_json oja = ordered_json::parse(texts[i]);
|
||||||
|
const ordered_json ojb = ordered_json::parse(texts[k]);
|
||||||
|
CHECK((oa.root() == ob.root()) == (oja == ojb));
|
||||||
|
CHECK((oa.root() == ojb) == (oja == ojb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("numbers, duplicate keys, member order")
|
||||||
|
{
|
||||||
|
const auto same = [](const char* x, const char* y)
|
||||||
|
{
|
||||||
|
const json_document dx = json_document::parse(x);
|
||||||
|
const json_document dy = json_document::parse(y);
|
||||||
|
return dx.root() == dy.root();
|
||||||
|
};
|
||||||
|
CHECK(same("1", "1.0"));
|
||||||
|
CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]"));
|
||||||
|
CHECK(!same("1", "1.5"));
|
||||||
|
CHECK(same("18446744073709551615", "18446744073709551615"));
|
||||||
|
CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})"));
|
||||||
|
CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})"));
|
||||||
|
CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})"));
|
||||||
|
CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})"));
|
||||||
|
CHECK(!same("[1, 2]", "[2, 1]"));
|
||||||
|
CHECK(!same("\"a\"", "\"b\""));
|
||||||
|
CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\""));
|
||||||
|
CHECK(!same("null", "false"));
|
||||||
|
CHECK(!same("[]", "{}"));
|
||||||
|
const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||||
|
const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
||||||
|
CHECK(dup.root() == last.root());
|
||||||
|
// (== compares as parse() resolves duplicates, a lookup finds the first)
|
||||||
|
CHECK(dup.root()["a"].materialize() == 1);
|
||||||
|
CHECK(dup.root()["a"] != last.root()["a"]);
|
||||||
|
const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
||||||
|
const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
||||||
|
CHECK(ab.root() != ba.root());
|
||||||
|
|
||||||
|
// discarded values compare as basic_json's do
|
||||||
|
const json discarded(json::value_t::discarded);
|
||||||
|
CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested
|
||||||
|
CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty)
|
||||||
|
const json_document null_document = json_document::parse("null");
|
||||||
|
CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty)
|
||||||
|
CHECK(!(null_document.root() == discarded));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting")
|
||||||
|
{
|
||||||
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
|
const json_document a = json_document::parse(deep);
|
||||||
|
const json_document b = json_document::parse(deep);
|
||||||
|
CHECK(a.root() == b.root());
|
||||||
|
CHECK(a.root() == json::parse(deep));
|
||||||
|
const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']');
|
||||||
|
const json_document c = json_document::parse(other);
|
||||||
|
CHECK(a.root() != c.root());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("json_view large objects")
|
TEST_CASE("json_view large objects")
|
||||||
{
|
{
|
||||||
// objects with 128 members or more are looked up with a hash index
|
// objects with 128 members or more are looked up with a hash index
|
||||||
|
|||||||
@@ -682,64 +682,6 @@ TEST_CASE("json_view images: ownership")
|
|||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
TEST_CASE("json_view images: errors")
|
TEST_CASE("json_view images: errors")
|
||||||
{
|
{
|
||||||
SECTION("float tokens: a changed byte is accepted if the text is still a number with the digits the node records")
|
|
||||||
{
|
|
||||||
for (const char* const token :
|
|
||||||
{
|
|
||||||
"-12.375", "0.5", "123456.0", "-0.0", "9999999999999999999.5", "0.000000000000000000000001", "1.25e10", "5E-3"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(token)
|
|
||||||
const std::string text = std::string("[") + token + "]";
|
|
||||||
const json_document d = json_document::parse(text);
|
|
||||||
const std::vector<std::uint8_t> image = d.save();
|
|
||||||
const std::size_t at = text_at(image) + 1; // the first byte of the token
|
|
||||||
const std::uint16_t recorded = node_at(image, 1).extra;
|
|
||||||
CHECK(loaded_dump(image) == json::parse(text).dump());
|
|
||||||
for (std::size_t pos = 0; token[pos] != '\0'; ++pos)
|
|
||||||
{
|
|
||||||
for (unsigned int byte = 0; byte < 256; ++byte)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> changed = image;
|
|
||||||
changed[at + pos] = static_cast<std::uint8_t>(byte);
|
|
||||||
std::string t = token;
|
|
||||||
t[pos] = static_cast<char>(byte);
|
|
||||||
// the digits of the integer part and of the fraction of the changed token
|
|
||||||
std::size_t integer = 0;
|
|
||||||
std::size_t fraction = 0;
|
|
||||||
std::size_t k = (t[0] == '-') ? 1 : 0;
|
|
||||||
for (; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k)
|
|
||||||
{
|
|
||||||
++integer;
|
|
||||||
}
|
|
||||||
if (k < t.size() && t[k] == '.')
|
|
||||||
{
|
|
||||||
for (++k; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k)
|
|
||||||
{
|
|
||||||
++fraction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// (as a JSON text, the token may have white space around it)
|
|
||||||
const bool float_token = json::accept(t) && t.find_first_of(std::string(" \t\r\n\0", 5)) == std::string::npos && (t.find('.') != std::string::npos || t.find_first_of("eE") != std::string::npos) // NOLINT(abseil-string-find-str-contains)
|
|
||||||
&& ((integer | (fraction << 8U)) == recorded);
|
|
||||||
CAPTURE(pos)
|
|
||||||
CAPTURE(byte)
|
|
||||||
std::string result;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = loaded_dump(changed);
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
CHECK(std::string(e.what()) == check_failed);
|
|
||||||
result = "rejected";
|
|
||||||
}
|
|
||||||
CHECK((result != "rejected") == float_token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("invalid UTF-8 of a decoded string: the compact dump writes it, the others throw")
|
SECTION("invalid UTF-8 of a decoded string: the compact dump writes it, the others throw")
|
||||||
{
|
{
|
||||||
const json_document d = json_document::parse(R"({"k\n":"\u00e9x"})");
|
const json_document d = json_document::parse(R"({"k\n":"\u00e9x"})");
|
||||||
|
|||||||
@@ -2023,7 +2023,7 @@ TEST_CASE("UBJSON")
|
|||||||
CHECK(json::from_ubjson(v0, true, false).is_discarded());
|
CHECK(json::from_ubjson(v0, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vi = {'[', '$', '#'};
|
std::vector<uint8_t> const vi = {'[', '$', '#'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x23 is not a permitted optimized array type", json::parse_error&);
|
||||||
CHECK(json::from_ubjson(vi, true, false).is_discarded());
|
CHECK(json::from_ubjson(vi, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vT = {'[', '$', 'T'};
|
std::vector<uint8_t> const vT = {'[', '$', 'T'};
|
||||||
@@ -3348,3 +3348,55 @@ TEST_CASE("UBJSON large strings (chunked reader)")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("UBJSON optimized container type marker is validated for count 0")
|
||||||
|
{
|
||||||
|
// the '$' type marker must be a legal marker even if no element follows
|
||||||
|
SECTION("invalid marker")
|
||||||
|
{
|
||||||
|
for (const char marker :
|
||||||
|
{'x', 'E', 'B', 'u', 'm', 'M', 'h'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const char open :
|
||||||
|
{'[', '{'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const int count :
|
||||||
|
{
|
||||||
|
0, 1
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(marker)
|
||||||
|
CAPTURE(open)
|
||||||
|
CAPTURE(count)
|
||||||
|
const std::vector<std::uint8_t> v = {static_cast<std::uint8_t>(open), '$', static_cast<std::uint8_t>(marker), '#', 'i', static_cast<std::uint8_t>(count)};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_ubjson(v), json::parse_error&);
|
||||||
|
CHECK(json::from_ubjson(v, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("error message")
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> v = {'[', '$', 'x', '#', 'i', 0};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x78 is not a permitted optimized array type", json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("valid marker")
|
||||||
|
{
|
||||||
|
for (const char marker :
|
||||||
|
{'Z', 'N', 'T', 'F', 'i', 'U', 'I', 'l', 'L', 'd', 'D', 'H', 'C', 'S', '[', '{'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(marker)
|
||||||
|
const std::vector<std::uint8_t> va = {'[', '$', static_cast<std::uint8_t>(marker), '#', 'i', 0};
|
||||||
|
const std::vector<std::uint8_t> vo = {'{', '$', static_cast<std::uint8_t>(marker), '#', 'i', 0};
|
||||||
|
CHECK(json::from_ubjson(va) == json::array());
|
||||||
|
CHECK(json::from_ubjson(vo) == json::object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user