Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ff56d7c91 | ||
|
|
4b09029f4e | ||
|
|
6e8e7d01df | ||
|
|
077c65872b | ||
|
|
2638f276d0 | ||
|
|
66d4f3602b | ||
|
|
612e9ade2c | ||
|
|
b3ab23e11c | ||
|
|
31538a91ed | ||
|
|
bf8dca5c16 | ||
|
|
d19e030bf1 | ||
|
|
e359852b76 | ||
|
|
734912bed6 | ||
|
|
91cc42b302 | ||
|
|
95412a36d0 | ||
|
|
d2309300e9 | ||
|
|
d3d39a9c10 | ||
|
|
24ff6eaa43 | ||
|
|
9d9bf927ff | ||
|
|
cc39e1f8da | ||
|
|
5ac95db5a9 | ||
|
|
d1d142633a |
No files matched your search
@@ -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.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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 | incomplete, but all JSON types are supported |
|
| 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.
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -1155,12 +1168,16 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa
|
|||||||
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, the largest value the library can store in that byte.
|
[`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
|
||||||
@@ -3000,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 != '#'))
|
||||||
{
|
{
|
||||||
@@ -3204,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';
|
||||||
@@ -3217,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;
|
||||||
@@ -4391,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
|
||||||
{
|
{
|
||||||
@@ -4496,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', '[', '{'
|
||||||
@@ -4538,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;
|
||||||
|
|||||||
Generated
+167
-6
@@ -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
|
||||||
@@ -20467,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 != '#'))
|
||||||
{
|
{
|
||||||
@@ -20671,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';
|
||||||
@@ -20684,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;
|
||||||
@@ -21858,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
|
||||||
{
|
{
|
||||||
@@ -21963,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', '[', '{'
|
||||||
@@ -22005,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':
|
||||||
@@ -27995,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -28008,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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28171,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
|
||||||
*/
|
*/
|
||||||
@@ -28232,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;
|
||||||
}
|
}
|
||||||
@@ -30140,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)
|
||||||
{
|
{
|
||||||
@@ -30348,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;
|
||||||
|
|||||||
+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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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