Compare commits

...
Author SHA1 Message Date
Niels Lohmann 7ff56d7c91 docs: do not list invalid UTF-8 strings as a BON8 serialization gap
JSON text must be valid UTF-8, and to_bon8 rejects ill-formed strings by design (type_error.316), as documented on the BON8 page. Restore the original cell and realign the table.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:54:03 +01:00
Niels Lohmann 4b09029f4e docs: drop the discarded-value remark from the BON8 page
Discarded values are not mentioned on the individual format pages.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:54:02 +01:00
Niels Lohmann 6e8e7d01df Address review of the BON8 documentation
Restore the correct statement that marker bytes cannot begin a UTF-8 character (0x80..0xBF occur in valid UTF-8 as continuation bytes), and reword two sentences.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:54:02 +01:00
Niels Lohmann 077c65872b docs: fix BON8 documentation gaps found in the format review
Verified each claim with small C++ probes before writing it.

- index.md: BON8 serialization is also incomplete for invalid UTF-8.
- bon8.md: document duplicate keys (last wins), the error ids for
  malformed input incl. 0xF5..0xF7 + 0x80..0xBF, exact string
  termination wording, wide integers and discarded values in the
  unsupported list, and title two untitled admonitions.
- exceptions.md: to_bon8 in type_error.316, BON8 in parse_error.112
  (with example) and .113, wide integers in out_of_range.407.
- binary_values.md: bytes 0..39 take one byte in BON8.
- from_bon8.md: note on strict UTF-8 and duplicate keys.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:54:01 +01:00
Niels Lohmann 2638f276d0 docs: format tables, use the Oxford comma
Reformat the tables changed by this PR (BJData serialization and
deserialization mappings, completeness and binary-value tables) so all
rows are aligned, and add the Oxford comma to lists of three or more
items in the added text.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:53:55 +01:00
Niels Lohmann 66d4f3602b Address review of the BJData documentation
The $ type restriction exists since Draft 2 (Draft 3 added B), $ requires a count, ND char above 127 is rejected on read, and the new API page is listed in alphabetical order in the nav.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:53:54 +01:00
Niels Lohmann 612e9ade2c Regenerate single_include and apply astyle
Result of make amalgamate (including make pretty) for the preceding changes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:53:53 +01:00
Niels Lohmann b3ab23e11c docs: correct and complete the BJData documentation
Verify the BJData chapter against the library and the Draft 2/3/4
specifications and fix what did not match:

- bjdata.md: correct the list of unused markers (N, not Z), the ND-array
  example (3x2, not 2x3), the conditions under which an object is packed
  as an ND-array (exactly three keys, row vectors, float-only elements for
  single/double, char 0..127, byte only in Draft 3), the binary table row
  and notes (which flag/draft combinations write [$B), the "$" type
  restriction (Draft 3, Z is excluded too), the H marker in the
  deserialization table, and the claim of a complete deserialization
  mapping. Document the supported drafts, that Draft 4 (SoA containers,
  extension type E) and column-major ND-arrays are rejected, ND-arrays of
  half floats, duplicate object keys, and link the Draft 3 specifications
  instead of master.
- Add an API page for basic_json::bjdata_version_t with an example, and
  link it from the nav, the API index and to_bjdata.
- Update binary_formats/index.md and binary_values.md, which still claimed
  that BJData has no binary type, and fix a wrong byte comment there.
- Document parse_error.115 and the out_of_range.408 cases for from_bjdata,
  the precise other_error.502 condition for to_bjdata, and reword the note
  on truncated unsigned integers in exceptions.md (only ND-array elements
  were truncated before 3.13.0).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:53:52 +01:00
Niels Lohmann 31538a91ed Read ND-arrays of half floats and enforce the char limit in ND-arrays
BJData N-dimensional arrays with element type 'h' (float16) failed to
read with a misleading parse_error.112, because the reader's dtype name
table lacked 'h'. It now maps to "half", the JData name. The writer still
does not pack "half"; such an object is written as a plain object and
reads back equal.

BJData limits 'C' (char) to 0..127 in Draft 2 and Draft 3, and the 1-D
reader enforces this, but ND-arrays with _ArrayType_ "char" wrote values
up to 255 as 'C' elements and read them back without a check. The writer
now only packs values in 0..127 (others fall back to a plain object that
round-trips), and the reader rejects 'C' elements above 127 with
parse_error.113 like the 1-D reader.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-26 06:53:51 +01:00
17 changed files with 416 additions and 126 deletions

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.
+1
View File
@@ -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
+5 -2
View File
@@ -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)
@@ -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>&quot;id&quot;: 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>&quot;id&quot;: 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>&quot;name&quot;: &quot;json&quot;,</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>&quot;name&quot;: &quot;json&quot;,</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>&quot;pi&quot;: 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>&quot;pi&quot;</code></td></tr> <tr><td><code>&quot;pi&quot;: 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>&quot;pi&quot;</code></td></tr>
<tr><td><code>&quot;tags&quot;: [</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>&quot;tags&quot;</code></td></tr> <tr><td><code>&quot;tags&quot;: [</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>&quot;tags&quot;</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`.
@@ -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.
+8 -5
View File
@@ -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:
+23 -9
View File
@@ -346,7 +346,7 @@ been read, or (with `strict = true`) further bytes follow the value.
### 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"
@@ -1233,8 +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` for a value that contains a non-empty array, object, or binary value. UBJSON `use_type = true` but `use_size = false`, and the value contains a non-empty array or object (or, with BJData Draft 3,
requires a size marker (`#`) after a type marker (`$`); scalars and empty containers do not throw. 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"
+1
View File
@@ -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
@@ -3277,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';
@@ -3290,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;
@@ -4655,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':
@@ -3848,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)
{ {
@@ -4056,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;
+27 -2
View File
@@ -20744,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';
@@ -20757,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;
@@ -22122,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':
@@ -30276,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)
{ {
@@ -30484,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;
+67
View File
@@ -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
@@ -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 _;