Compare commits

..
Author SHA1 Message Date
Niels Lohmann 49e9eaac4f Address review of the MessagePack documentation
Name parse_error.113 for a 0xC1 map key, move the duplicate-key sentence out of the ext section, and keep the 0xC1 row in marker order.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 20:05:25 +01:00
Niels Lohmann 4b35877ae5 Document MessagePack ext types and fix related doc errors
Add an "Extension types (ext, fixext)" section to the MessagePack page:
ext/fixext are read as binary, the signed ext type is exposed as an
unsigned subtype 0..255 (-1 reads as 255), Timestamp is not interpreted,
subtypes are always written as ext/fixext, and subtypes above 255 throw
out_of_range.415. Add the 0xC1 row to the deserialization table, note
that duplicate keys keep the last value, and correct the NaN/Infinity
explanation, the "31 bytes / 15 elements" wording, the ill-formed UTF-8
sentence, and the generic 0xC1-key error message.

Add fixext16 and the unsigned subtype mapping to binary_t.md and
binary_values.md, reword "maximum of the MessagePack ext type" in
to_msgpack.md and exceptions.md, and mark MessagePack deserialization
as incomplete (non-string keys) in the binary formats overview.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 20:05:24 +01:00
Niels Lohmann 34f9be2aa2 docs: drop discarded-value remarks
Remove the mention that a discarded value cannot be serialized to CBOR
(type_error.321) from the exception-safety and mapping sections. The
integer table and all added prose already follow the table-format and
Oxford-comma rules.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 20:05:23 +01:00
Niels Lohmann 8c4818d5f7 Address review of the CBOR changes
Cite RFC 8949 Section 5.3.1 for duplicate keys, describe the out_of_range.408 condition precisely, rewrap a long line, and update the stale get_string comment (per-chunk UTF-8 check). single_include regenerated.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 17:56:22 +01:00
Niels Lohmann df1bca6dae 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-25 17:46:08 +01:00
Niels Lohmann b22cad367c docs: correct and extend the CBOR documentation
Fix the negative-integer rows (they used MessagePack boundaries), the
number_integer range of the 0x1B row, and the half-precision entry in the
list of unused types; correct the NaN history (0xF9 since 3.8.0) and the
tagged-item wording (only the tag directly in front of a byte string is
stored, and only in the 0xD8..0xDB form). Document that to_cbor is not
RFC 8949 deterministic, that duplicate keys are accepted (last wins),
tags in front of keys, per-chunk UTF-8 validation, the exceptions
parse_error.113, out_of_range.406/408 of from_cbor, the pointer and
C array input traps, the partially written output on exceptions, and the
store/ignore results in the binary values example. Drop the claim that
the CBOR reader needs StringType::append(const StringType&) and
BinaryType::insert(pos, first, last).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 17:46:05 +01:00
Niels Lohmann 95169a63fa Skip CBOR tags in front of map keys and validate indefinite text chunks
A tag in front of a text string map key was rejected with parse_error.113
even with cbor_tag_handler_t::ignore or store, although tags in front of
values are handled. The tags are now skipped iteratively before the key is
read; the handler error still rejects them.

The chunks of an indefinite-length text string were concatenated before
the single UTF-8 check, so a code point split over two chunks, or an
invalid chunk, was accepted. RFC 8949, Section 3.2.3 requires every chunk
to be valid on its own. With error_handler_t::strict, replace and ignore
each chunk is now checked; keep is unchanged. The chunks are appended with
append(ptr, len), so StringType needs nothing new.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 17:46:05 +01:00
21 changed files with 423 additions and 155 deletions

No files matched your search

+3 -2
View File
@@ -90,8 +90,9 @@ type `#!cpp binary_t*` must be dereferenced.
- MessagePack
- If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1,
fixext2, fixext4, fixext8) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The subtype is
then added as a signed 8-bit integer.
fixext2, fixext4, fixext8, fixext16) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The
subtype is then written as the signed 8-bit ext type: subtypes 128 to 255 map to the negative (reserved) ext
types, and on reading the ext type is exposed as an unsigned subtype from 0 to 255.
- If no subtype is given, the bin family (bin8, bin16, bin32) is used.
- BSON
@@ -15,11 +15,13 @@ error
: report a parse error in case of a tag (the `from_cbor` overloads throw a `parse_error` exception by default)
ignore
: ignore tags
: ignore tags (also in front of a text string map key)
store
: store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are
read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored.
read as if the tag were ignored. If several tags precede a byte string, only the tag directly in front of it is
stored, and only when it has the form 0xd8..0xdb (a tag 0..23 written as 0xc0..0xd7 is skipped). Tags in front of a
text string map key are skipped.
## Examples
+2 -9
View File
@@ -61,18 +61,11 @@ Strong exception safety: if an exception occurs, the original value stays intact
## Complexity
1. Logarithmic in the size of the JSON object.
2. Logarithmic in the size of the JSON object.
3. At most one lookup per reference token of `ptr`, each logarithmic in the size of the container at that level:
O(*n* log *m*) for *n* reference tokens and containers of size at most *m*.
4. Deleted; a call with an integral argument does not compile.
Logarithmic in the size of the JSON object.
## Notes
- Overloads (1) and (2) always return `#!cpp false` when executed on a JSON type that is not an object. Overload (3)
also works on arrays and on primitive values: the empty JSON pointer resolves in every JSON value (so
`#!cpp json(1).contains(""_json_pointer)` returns `#!cpp true`), and `#!cpp json({1, 2}).contains("/1"_json_pointer)`
returns `#!cpp true`.
- This method always returns `#!cpp false` when executed on a JSON type that is not an object.
- This method can be executed on any JSON value type.
- Calling this function with an integer argument (for example, `#!cpp contains(0)`) does not compile: such an argument
would otherwise implicitly convert to a null `#!cpp const char*` and, from there, cause undefined behavior when
+12 -4
View File
@@ -32,8 +32,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- an `std::istream` object
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- a C-style array of characters (its terminating NUL is read as trailing data: `parse_error.110` unless `strict` is
`false`)
- a pointer to a null-terminated string of single byte characters (read up to the first 0x00 byte, which is a valid
CBOR byte)
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
@@ -89,8 +91,14 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
used in the given input or if the input is not valid CBOR
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of
other types are not supported, as JSON object keys are always strings), or if a string value or object key is not
valid UTF-8 and `error_handler` is `strict`
other types are not supported, as JSON object keys are always strings), if the head of a string or byte string is
malformed (including a chunk of the wrong type or an indefinite-length string nested inside an indefinite-length
string), or if a string value or object key (or one chunk of an indefinite-length string) is not valid UTF-8 and
`error_handler` is `strict`
- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if an array or map announces
a length that `std::size_t` cannot hold, or that equals `std::size_t(-1)` (for example `9B FF FF FF FF FF FF FF FF`)
- Throws [out_of_range.406](../../home/exceptions.md#jsonexceptionout_of_range406) if a floating-point number does not
fit into `number_float_t`
## Complexity
+2 -1
View File
@@ -42,7 +42,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
## Exception safety
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
Strong guarantee: if an exception is thrown, there are no changes in the JSON value. Bytes that were already written to
the output adapter are not taken back.
## Exceptions
@@ -53,7 +53,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
exceeds 255, the maximum of the MessagePack ext type; example:
exceeds 255, the largest value the library can store in the one-byte MessagePack ext type; example:
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
- 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`
+5 -10
View File
@@ -36,9 +36,9 @@ The function can throw the following exceptions:
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a key is not a
number; example: `"array index 'one' is not a number"`
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a level becomes an array
(because one of its keys is `0`) and another key at that level is empty (as in `/a/`), is `-`, or begins with a digit
but is not a valid array index (such as `1a`); examples: `"unresolved reference token '-'"`,
`"unresolved reference token ''"`
(because one of its keys is `0`) and another key at that level begins with a digit but is not a valid array index
(such as `1a`), or is `-`; example:
`"unresolved reference token '-'"`
## Complexity
@@ -46,7 +46,7 @@ Linear in the size of the JSON value.
## Notes
Empty objects and arrays are flattened by [`flatten()`](flatten.md) to `#!json null` values and cannot be unflattened to
Empty objects and arrays are flattened by [`flatten()`](flatten.md) to `#!json null` values and cannot unflattened to
their original type.
A flattened array and a flattened object whose keys are array indices are indistinguishable, because both are
@@ -54,12 +54,7 @@ described by the same JSON pointers. A value is therefore restored as an array i
reference token `0`, and as an object otherwise: `#!json {"2": 1}` is restored unchanged, whereas `#!json {"0": 1}` is
restored as `#!json [1]`. This decision does not depend on the order in which the flattened object is iterated.
A third case is an object that has the key `0` next to a key that is not a valid array index: `#!json {"0": 1, "b": 2}`
is flattened to `#!json {"/0": 1, "/b": 2}`, and unflatten treats the level as an array and throws
[`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) because `b` is not a number. (For the keys
`-`, `1a`, or the empty key instead of `b`, it throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404).)
Apart from these three cases, for a JSON value `j`, the following is always true:
Apart from these two cases, for a JSON value `j`, the following is always true:
`#!cpp j == j.flatten().unflatten()`.
## Examples
-3
View File
@@ -45,9 +45,6 @@ This is equivalent to Python's `dict.get(key, default)`.
return default_value;
}
```
with one exception: if the JSON value is neither an array nor an object, the function throws
[`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) before looking at `ptr`, even for the empty
pointer, whereas `at` returns the value itself for the empty pointer.
!!! note "Differences to `at` and `operator[]`"
@@ -12,8 +12,7 @@ Parent of this JSON pointer; in case this JSON pointer is the root, the root its
## Exception safety
Strong exception safety: if an exception occurs, the original value stays intact. The function is not `#!cpp noexcept`
and allocates a new JSON pointer, so it can throw `#!cpp std::bad_alloc`; it throws no library exceptions.
No-throw guarantee: this function never throws exceptions.
## Complexity
+1 -2
View File
@@ -37,8 +37,7 @@ the stream `o`
1. Throws [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) if a string stored inside the JSON
value is not UTF-8 encoded. Note that unlike the [`dump`](basic_json/dump.md) member functions, no `error_handler`
can be set.
2. Throws no library exceptions. As the string representation is built with [`to_string`](json_pointer/to_string.md),
`#!cpp std::bad_alloc` is possible.
2. None.
## Complexity
@@ -11,7 +11,7 @@ extremely small code sizes, fairly small message size, and extensibility without
## Byte layout
CBOR encodes every data item as a *head* followed by optional content. The first byte of the head holds the *major type* in its upper three bits (unsigned integer, negative integer, byte string, text string, array, map, tag, or simple/float) and an *additional information* value in its lower five bits. Additional information below 24 is the argument itself (for example, a small integer or a short length); the values 24 to 27 announce that the argument follows in 1, 2, 4, or 8 big-endian bytes. Strings, arrays, and maps are prefixed with their length (or count of elements/pairs), and content follows directly, so a reader always knows how many items to expect without a terminator.
CBOR encodes every data item as a *head* followed by optional content. The first byte of the head holds the *major type* in its upper three bits (unsigned integer, negative integer, byte string, text string, array, map, tag, or simple/float) and an *additional information* value in its lower five bits. Additional information below 24 is the argument itself (for example, a small integer or a short length); the values 24 to 27 announce that the argument follows in 1, 2, 4, or 8 big-endian bytes. Strings, arrays, and maps are prefixed with their length (or count of elements/pairs), and content follows directly. A reader of definite-length items knows how many items to expect without a terminator; indefinite-length items (additional information 31) are instead closed by the "break" byte 0xFF, which `from_cbor` reads but `to_cbor` never writes.
Consider the following JSON value (shown formatted for readability):
@@ -52,16 +52,16 @@ The library uses the following mapping from JSON values types to CBOR types acco
| null | `null` | Null | 0xF6 |
| boolean | `true` | True | 0xF5 |
| boolean | `false` | False | 0xF4 |
| number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B |
| number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A |
| number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 |
| number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 |
| number_integer | -9223372036854775808..-4294967297 | Negative integer (8 bytes follow) | 0x3B |
| number_integer | -4294967296..-65537 | Negative integer (4 bytes follow) | 0x3A |
| number_integer | -65536..-257 | Negative integer (2 bytes follow) | 0x39 |
| number_integer | -256..-25 | Negative integer (1 byte follow) | 0x38 |
| number_integer | -24..-1 | Negative integer | 0x20..0x37 |
| number_integer | 0..23 | Integer | 0x00..0x17 |
| number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 |
| number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 |
| number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A |
| number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B |
| number_integer | 4294967296..9223372036854775807 | Unsigned integer (8 bytes follow) | 0x1B |
| number_unsigned | 0..23 | Integer | 0x00..0x17 |
| number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 |
| number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 |
@@ -95,7 +95,24 @@ see "binary" cells in the table above.
!!! success "Complete mapping"
The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value.
The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value, with one
exception: an integer outside the range of CBOR integers (only possible with a number type wider than 64 bits)
throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407).
!!! info "Not a deterministic encoding"
`to_cbor` does not produce the deterministic encoding of
[RFC 8949, Section 4.2.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-4.2.1), and not always the preferred
serialization of Section 4.1:
- floating-point numbers are written as single precision (0xFA) when that is exact and as double precision (0xFB)
otherwise; half precision (0xF9) is used only for `NaN` and `Infinity`/`-Infinity` (`1.0` is `fa 3f 80 00 00`);
- a binary subtype 0..23 is written as `d8 xx` instead of the one-byte tag 0xC0..0xD7 (`d8 01 41 01`);
- object members are written in the order of `object_t` (for the default `std::map`: sorted by the key string, not
by the encoded key bytes).
Integers and lengths use the shortest form, and indefinite-length items are never written. Decoders must accept all
of this; the RFC does not require the preferred serialization.
!!! info "NaN/infinity handling"
@@ -105,9 +122,8 @@ see "binary" cells in the table above.
!!! note
Prior to version 3.13.0, NaN and Infinity were instead serialized as a CBOR double-precision float (type 0xFB,
9 bytes total), because the check used to select a smaller encoding compared magnitudes with NaN, which is
always `false` and caused the intended half-precision path to be skipped.
Prior to version 3.8.0, NaN and Infinity were instead serialized as a CBOR double-precision float (type 0xFB,
9 bytes total).
!!! info "Unused CBOR types"
@@ -124,7 +140,6 @@ see "binary" cells in the table above.
- expected conversions (0xD5..0xD7)
- simple values (0xE0..0xF3, 0xF8)
- undefined (0xF7)
- half-precision floats (0xF9)
- break (0xFF)
!!! info "Tagged items"
@@ -226,10 +241,20 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https://
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
general-purpose CBOR library instead.
A tag in front of a text string key is handled like a tag in front of a value: it is skipped with
`cbor_tag_handler_t::ignore` or `cbor_tag_handler_t::store` and rejected with the default `error`. A tag does not
turn a key of another type into a text string.
A map with duplicate keys is well-formed, but invalid under
[RFC 8949, Section 5.3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1). `from_cbor` accepts it and the
last value wins; the [SAX interface](../parsing/sax_interface.md) reports every occurrence.
!!! warning "Ill-formed UTF-8 in text strings"
[RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings (major
type 3) to be valid UTF-8, but leaves it up to the decoder whether to enforce this, so checking is opt-in: with the
type 3) to be valid UTF-8 (a string with ill-formed UTF-8 is "well-formed but invalid"), and
[Section 5.3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1) leaves it up to the decoder whether to
verify this, so checking is opt-in: with the
[`error_handler`](../../api/basic_json/from_cbor.md) parameter left at `keep` (the default), `from_cbor()` accepts a
text string (object keys included) whose bytes are not valid UTF-8 and hands them back unchanged. Passing
`error_handler_t::strict` makes `from_cbor()` check and throw
@@ -242,9 +267,14 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https://
[`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception
instead. Byte strings (major type 2) are unaffected, since they are not required to hold text.
The chunks of an indefinite-length text string are checked one by one, as required by
[RFC 8949, Section 3.2.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3): a code point cannot be spread
over two chunks. With `error_handler_t::strict` such a string throws `parse_error.113`; `replace` and `ignore`
sanitize each chunk on its own.
!!! warning "Tagged items"
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the tag directly in front of it is stored, and only when it has the form 0xD8..0xDB (a tag 0..23 written as 0xC0..0xD7 is skipped); for example, `D8 05 C1 41 AA` yields subtype `null`, whereas `C1 D8 05 41 AA` yields 5. Tags in front of a text string map key are skipped as well. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
??? example "Example: deserialize a JSON value from CBOR"
@@ -22,7 +22,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors.
| BON8 | incomplete: no unsigned integers above int64 | complete |
| 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 |
| MessagePack | complete | complete |
| MessagePack | complete | incomplete, but all JSON types are supported |
| UBJSON | complete | complete |
### Binary values
@@ -11,7 +11,7 @@ extra byte in addition to the strings themselves.
## Byte layout
MessagePack encodes each value as a one-byte *format marker*, optionally followed by a length or the payload. The marker range is partitioned to keep common values small: small non-negative integers (0 to 127) and small negative integers (-32 to -1) are the marker byte itself; short strings, arrays, and maps (up to 31 or 15 elements) hide their length in the marker's lower bits. Larger values use dedicated markers that announce the width (8, 16, 32, or 64 bits) of the length or number that follows, in big-endian byte order. Like CBOR, containers are length-prefixed, so no terminator is needed.
MessagePack encodes each value as a one-byte *format marker*, optionally followed by a length or the payload. The marker range is partitioned to keep common values small: small non-negative integers (0 to 127) and small negative integers (-32 to -1) are the marker byte itself; short strings, arrays, and maps (up to 31 bytes for strings and up to 15 elements for arrays and maps) hide their length in the marker's lower bits. Larger values use dedicated markers that announce the width (8, 16, 32, or 64 bits) of the length or number that follows, in big-endian byte order. Like CBOR, containers are length-prefixed, so no terminator is needed.
Consider the following JSON value (shown formatted for readability):
@@ -109,8 +109,9 @@ specification:
!!! note
Prior to version 3.13.0, NaN and Infinity were instead serialized as a MessagePack float 64 (type 0xCB, 9 bytes
total), because the check used to select the smaller float 32 encoding compared magnitudes with NaN, which is
always `false` and caused the float 32 path to be skipped.
total), because the check used to select the smaller float 32 encoding failed for NaN (all comparisons are `false`)
and for Infinity and -Infinity (outside the float range checked against `std::numeric_limits<float>::lowest()` and
`max()`), so the float 32 path was skipped.
??? example "Example: serialize a JSON value to MessagePack"
@@ -135,6 +136,7 @@ The library maps MessagePack types to JSON value types as follows:
| fixarray | array | 0x90..0x9F |
| fixstr | string | 0xA0..0xBF |
| nil | `null` | 0xC0 |
| (never used) | – | 0xC1 |
| false | `false` | 0xC2 |
| true | `true` | 0xC3 |
| float 32 | number_float | 0xCA |
@@ -167,6 +169,30 @@ The library maps MessagePack types to JSON value types as follows:
| fixext 16 | binary | 0xD8 |
| negative fixint | number_integer | 0xE0-0xFF |
The byte 0xC1 is reserved as "never used" by the specification. As a value it is rejected with
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) (`invalid byte: 0xC1`); as a map key it is
rejected with [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) and the generic "expected length
specification" message, because the key is read as a string.
Duplicate keys in a map are accepted; the last value wins.
### Extension types (ext, fixext)
MessagePack ext and fixext values are read as binary values, and the ext type byte is stored as the binary subtype.
Although the specification defines the type as a signed 8-bit integer (0 to 127 for applications, -128 to -1 reserved,
with -1 being the Timestamp extension), the library reads it as an unsigned byte: a negative ext type `n` becomes
subtype `256+n`, so ext type -1 is read as subtype 255 and -128 as subtype 128. The Timestamp extension is not
interpreted; it is returned as binary with subtype 255. Use
[`has_subtype()`](../../api/byte_container_with_subtype/has_subtype.md) to tell a `bin` value (no subtype) from an ext
value (subtype, even if it is 0).
When serializing, a binary value with a subtype is always written as fixext (1, 2, 4, 8, or 16 bytes) or ext, even
for subtype 0 (`d6 00 01 02 03 04`); a binary value without a subtype is written as bin. Subtypes 128 to 255 are
written as the reserved negative ext types (255 as -1), so `json::binary({1,2,3,4}, 255)` becomes
`d6 ff 01 02 03 04`, which other MessagePack libraries decode as a 32-bit timestamp. A subtype above 255 throws
[`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415). The payload length is limited to
4294967295 bytes like all other lengths.
!!! info
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
@@ -177,7 +203,8 @@ The library maps MessagePack types to JSON value types as follows:
JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in the
MessagePack specification, this library restricts map keys to `str` values. Maps with keys of any other type are
rejected with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
`allow_exceptions` set to `false`, a discarded value) naming the type of the key that was found, for instance:
`allow_exceptions` set to `false`, a discarded value). For most key types the message names the type that was
found, for instance:
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
@@ -189,7 +216,7 @@ The library maps MessagePack types to JSON value types as follows:
!!! warning "Ill-formed UTF-8 in string values"
The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain
a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged.
a byte sequence that is not valid UTF-8, and recommends that a deserializer provide a way to obtain the original bytes.
This library follows that by default: with its
[`error_handler`](../../api/basic_json/from_msgpack.md) parameter left at `keep` (the default),
`from_msgpack()` reads `str` bytes (object keys included) as-is, without validating them, so such a value
+14 -2
View File
@@ -301,6 +301,8 @@ representation using the length of the byte array.
[`json::from_cbor`](../api/basic_json/from_cbor.md) (see
[`cbor_tag_handler_t`](../api/basic_json/cbor_tag_handler_t.md)).
With `ignore`, the subtype is dropped (`"subtype": null`); with `store`, the tag is kept (`"subtype": 42`):
```json
{
"binary": {
@@ -310,11 +312,21 @@ representation using the length of the byte array.
}
```
```json
{
"binary": {
"bytes": [202, 254, 186, 190],
"subtype": 42
}
}
```
### MessagePack
[MessagePack](binary_formats/messagepack.md) supports binary values and subtypes. If a subtype is given, the ext family
is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, ext8, ext16, and
ext32. The subtype is then added as a signed 8-bit integer.
is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, fixext16, ext8,
ext16, and ext32. The subtype is then written as the signed 8-bit ext type: subtypes 128 to 255 map to the negative
(reserved) ext types, and on reading the ext type is exposed as an unsigned subtype from 0 to 255.
If no subtype is given, the bin family (bin8, bin16, bin32) is used.
-11
View File
@@ -37,10 +37,6 @@ Then every value inside the JSON document can be identified as follows:
Note `/` does not identify the root (i.e., the whole document), but an object entry with empty key `""`. See
[RFC 6901](https://tools.ietf.org/html/rfc6901) for more information.
[RFC 6901, section 4](https://tools.ietf.org/html/rfc6901#section-4) states that evaluation fails if an object has
non-unique member names. A `basic_json` object holds one value per key (see [`object_t`](../api/basic_json/object_t.md)
for how duplicate keys are handled while parsing), so evaluating a JSON Pointer never encounters duplicates.
## JSON Pointer creation
JSON Pointers can be created from a string:
@@ -58,13 +54,6 @@ auto p = "/nested/one"_json_pointer;
The escaping rules of [RFC 6901](https://tools.ietf.org/html/rfc6901) are implemented. See the
[constructor documentation](../api/json_pointer/json_pointer.md) for more information.
!!! note "URI fragment representation"
[RFC 6901, section 6](https://tools.ietf.org/html/rfc6901#section-6) also defines a URI fragment identifier
representation (`#/a%20b`). It is not supported: `#!cpp json::json_pointer("#/a")` throws
[`parse_error.107`](../home/exceptions.md#jsonexceptionparse_error107). Strip the leading `#` and
percent-decode the remainder before creating the JSON pointer.
## Value access
JSON Pointers can be used in the [`at`](../api/basic_json/at.md), [`operator[]`](../api/basic_json/operator%5B%5D.md),
@@ -368,8 +368,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
`#!cpp std::from_chars` is not available or declines the token, and `std::strtold` reads up to the null character. A type whose `data()`
is not null-terminated does not fail to compile; with such a `NumberFloatType` it can silently misparse
floating-point numbers.
- `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md), and `append(const StringType&)`,
used by the CBOR reader for indefinite-length strings. The library's internal string concatenation additionally has
- `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md) and by the binary readers. The library's internal string concatenation additionally has
to append a `#!cpp char` and a `#!cpp const char*`; for each it selects between `append(arg)`, `#!cpp operator+=`,
`append(first, last)`, and `append(data, size)`.
- The comparison operator `==` against another `StringType`, and `<` for use as a key of the chosen
@@ -390,7 +389,6 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
described in RFC 6901. Neither `find(const StringType&, size_type)`, nor `substr(pos, count)`, nor
`replace(pos, count, const StringType&)` is required.
- `empty()`.
- `resize(n)` (one argument) -- used by `to_string` to size the result before the reference tokens are copied into it.
- `begin()` and `end()` -- used by
[`operator[](const json_pointer&)`](../../api/basic_json/operator%5B%5D.md) to decide whether a reference token
denotes an array index.
@@ -679,8 +677,7 @@ using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;
[`StringType`](#stringtype), whose non-`#!cpp const` `data()` is C++17 and later only.
- Default-constructible, copy-constructible, and move-constructible.
- Member functions `size()`, `empty()`, `data()`, `resize()`, `operator[]`, `back()`, `begin()`, `end()`, `cbegin()`,
and `cend()` with random-access iterators, and `insert(pos, first, last)`, which the CBOR reader uses to join the
chunks of an indefinite-length byte string. `push_back()` is **not** required.
and `cend()` with random-access iterators. `push_back()` is **not** required.
- Comparison operators: `==` is used by
[`byte_container_with_subtype`](../../api/byte_container_with_subtype/index.md), the relational operators by
[`basic_json`'s comparison operators](../../api/basic_json/operator_le.md).
+2 -7
View File
@@ -1083,14 +1083,9 @@ a JSON pointer exceeds the range of `size_type` (e.g., on 32-bit platforms).
```
enum value out of range
array index 18446744073709551615 exceeds size_type
array index 18446744073709551616 exceeds size_type
```
The second message is thrown, for instance, by `#!cpp json::parse("[1, 2]").at("/18446744073709551615"_json_pointer)`
on a 64-bit platform, where `size_type` is `#!cpp std::size_t` and this index is `SIZE_MAX`. An index that does not
fit into 64 bits (such as `18446744073709551616`) is not reported as 410 but as
[`out_of_range.404`](#jsonexceptionout_of_range404).
### json.exception.out_of_range.411
A JSON Patch `add` operation cannot be applied because the target location's parent is neither an object nor an array. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902), an `add` target must reference a member of an existing object or an element of an existing array; a primitive value (string, number, boolean, etc.) cannot receive a new member or element.
@@ -1158,7 +1153,7 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa
### json.exception.out_of_range.415
MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255.
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255, the largest value the library can store in that byte.
!!! failure "Example message"
+75 -11
View File
@@ -1218,6 +1218,10 @@ class binary_reader
// level as required by RFC 8949, Section 3.2.3
bool indefinite = false;
// the chunk currently being validated; only used for the chunks of an
// indefinite-length string when the UTF-8 check can do something
string_t chunk;
while (true)
{
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string")))
@@ -1237,26 +1241,82 @@ class binary_reader
}
// a break marker closes the indefinite-length string; outside
// of one it falls through to the error below
// of one it falls through to the error below. Every chunk has
// been checked on its own, so there is nothing left to check.
if (indefinite && current == 0xFF)
{
return check_string_utf8(result, context);
}
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
{
return false;
return true;
}
if (!indefinite)
{
return check_string_utf8(result, context);
return get_cbor_string_chunk(result, false) && check_string_utf8(result, context);
}
if (error_handler == error_handler_t::keep)
{
// nothing to check: read the chunk directly into the result
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true)))
{
return false;
}
}
else
{
// RFC 8949, Section 3.2.3: each chunk is a text string of its
// own and must be valid UTF-8 by itself, i.e., a code point
// cannot be spread over two chunks
chunk.clear();
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context)))
{
return false;
}
result.append(chunk.data(), chunk.size());
}
get();
}
}
/*!
@brief skips the tags in front of a CBOR object key
A tag may wrap any data item, so it may also wrap a key. Like in front of a
value, the tags are skipped for the handlers ignore and store; with the
handler error they are left to @ref get_cbor_object_key, which rejects the
key. The tag loop is iterative, so the number of tags is not limited by the
recursion depth.
@param[in] tag_handler how CBOR tags should be treated
@return whether the tags could be skipped (false on EOF in a tag argument)
@pre @a current is the first byte of the key
*/
bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler)
{
if (tag_handler == cbor_tag_handler_t::error)
{
return true;
}
// 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in
// the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB)
while (current >= 0xC0 && current <= 0xDB)
{
if (current >= 0xD8)
{
std::uint64_t tag_to_ignore{};
if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore)))
{
return false;
}
}
get();
}
return true;
}
/*!
@brief reads a CBOR object key
@@ -1623,6 +1683,10 @@ class binary_reader
if (top.is_object)
{
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
{
return false;
@@ -4130,9 +4194,9 @@ class binary_reader
// sequence), UBJSON, BJData, or BSON requires a decoder to reject
// ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
// rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
// @ref error_handler, applied once the whole string (all chunks of
// an indefinite-length CBOR string included) has been assembled, by
// @ref check_string_utf8 at the call site.
// @ref error_handler, applied by @ref check_string_utf8 at the call
// site to the whole string or, for an indefinite-length CBOR string,
// to each chunk on its own (RFC 8949, Section 3.2.3).
return get_bytes(len, "string", result);
}
+75 -11
View File
@@ -18685,6 +18685,10 @@ class binary_reader
// level as required by RFC 8949, Section 3.2.3
bool indefinite = false;
// the chunk currently being validated; only used for the chunks of an
// indefinite-length string when the UTF-8 check can do something
string_t chunk;
while (true)
{
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string")))
@@ -18704,26 +18708,82 @@ class binary_reader
}
// a break marker closes the indefinite-length string; outside
// of one it falls through to the error below
// of one it falls through to the error below. Every chunk has
// been checked on its own, so there is nothing left to check.
if (indefinite && current == 0xFF)
{
return check_string_utf8(result, context);
}
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
{
return false;
return true;
}
if (!indefinite)
{
return check_string_utf8(result, context);
return get_cbor_string_chunk(result, false) && check_string_utf8(result, context);
}
if (error_handler == error_handler_t::keep)
{
// nothing to check: read the chunk directly into the result
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true)))
{
return false;
}
}
else
{
// RFC 8949, Section 3.2.3: each chunk is a text string of its
// own and must be valid UTF-8 by itself, i.e., a code point
// cannot be spread over two chunks
chunk.clear();
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context)))
{
return false;
}
result.append(chunk.data(), chunk.size());
}
get();
}
}
/*!
@brief skips the tags in front of a CBOR object key
A tag may wrap any data item, so it may also wrap a key. Like in front of a
value, the tags are skipped for the handlers ignore and store; with the
handler error they are left to @ref get_cbor_object_key, which rejects the
key. The tag loop is iterative, so the number of tags is not limited by the
recursion depth.
@param[in] tag_handler how CBOR tags should be treated
@return whether the tags could be skipped (false on EOF in a tag argument)
@pre @a current is the first byte of the key
*/
bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler)
{
if (tag_handler == cbor_tag_handler_t::error)
{
return true;
}
// 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in
// the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB)
while (current >= 0xC0 && current <= 0xDB)
{
if (current >= 0xD8)
{
std::uint64_t tag_to_ignore{};
if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore)))
{
return false;
}
}
get();
}
return true;
}
/*!
@brief reads a CBOR object key
@@ -19090,6 +19150,10 @@ class binary_reader
if (top.is_object)
{
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
{
return false;
@@ -21597,9 +21661,9 @@ class binary_reader
// sequence), UBJSON, BJData, or BSON requires a decoder to reject
// ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict,
// rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via
// @ref error_handler, applied once the whole string (all chunks of
// an indefinite-length CBOR string included) has been assembled, by
// @ref check_string_utf8 at the call site.
// @ref error_handler, applied by @ref check_string_utf8 at the call
// site to the whole string or, for an indefinite-length CBOR string,
// to each chunk on its own (RFC 8949, Section 3.2.3).
return get_bytes(len, "string", result);
}
+148 -3
View File
@@ -1807,6 +1807,93 @@ TEST_CASE("CBOR")
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
}
SECTION("tag in front of a key")
{
const auto ignore = json::cbor_tag_handler_t::ignore;
const auto store = json::cbor_tag_handler_t::store;
const json expected = {{"a", 10}};
// the tags are skipped in front of a text string key, like in
// front of a value
const std::vector<std::vector<std::uint8_t>> tagged =
{
{0xA1, 0xC0, 0x61, 0x61, 0x0A}, // tag in the head
{0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A}, // 1-byte tag number
{0xA1, 0xD9, 0x01, 0x2C, 0x61, 0x61, 0x0A}, // 2-byte tag number
{0xA1, 0xDA, 0x00, 0x01, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 4-byte tag number
{0xA1, 0xDB, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 8-byte tag number
{0xA1, 0xC0, 0xD8, 0x05, 0xC1, 0x61, 0x61, 0x0A}, // chained tags
{0xBF, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // indefinite-length map
{0xBF, 0x61, 0x62, 0x01, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // second key tagged
{0xA1, 0xC0, 0x7F, 0x61, 0x61, 0xFF, 0x0A}, // indefinite-length text key
{0xA1, 0xC0, 0x61, 0x61, 0xC0, 0x0A}, // tagged key and value
};
for (const auto& input : tagged)
{
CAPTURE(input)
json const j_ignore = json::from_cbor(input, true, true, ignore);
json const j_store = json::from_cbor(input, true, true, store);
CHECK(j_ignore.at("a") == 10);
CHECK(j_store.at("a") == 10);
CHECK(j_ignore == j_store);
}
CHECK(json::from_cbor(tagged[0], true, true, ignore) == expected);
CHECK(json::from_cbor(tagged[1], true, true, store) == expected);
// nested maps
CHECK(json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x61, 0x61, 0xA1, 0xD8, 0x05, 0x61, 0x62, 0x01}), true, true, ignore) == json{{"a", {{"b", 1}}}});
// the tag handler error still rejects every tag
for (const auto& input : tagged)
{
CAPTURE(input)
json _;
CHECK_THROWS_AS(_ = json::from_cbor(input), json::parse_error&);
}
{
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a tag; last byte: 0xD8", json::parse_error&);
}
// a tag does not turn a key of another type into a text string
{
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x01, 0x01}), true, true, ignore), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8, 0x05, 0x41, 0x61, 0x01}), true, true, store), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR object key: only string keys are supported, but found a byte string; last byte: 0x41", json::parse_error&);
CHECK(json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0, 0x01, 0x01}), true, false, ignore).is_discarded());
}
// end of input after the tag, or inside the tag number
{
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xC0}), true, true, ignore), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xD8}), true, true, ignore), json::parse_error&);
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0xDB, 0x00, 0x00}), true, true, store), json::parse_error&);
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xBF, 0xC0}), true, true, store), json::parse_error&);
}
// SAX: the key event is reported without the tag (start_object,
// key, number_unsigned, end_object are four events)
{
const std::vector<std::uint8_t> input = {0xA1, 0xC0, 0x61, 0x61, 0x0A};
SaxCountdown four(4);
CHECK(json::sax_parse(input, &four, json::input_format_t::cbor, true, false, false, ignore));
SaxCountdown three(3);
CHECK(!json::sax_parse(input, &three, json::input_format_t::cbor, true, false, false, ignore));
SaxCountdown error_handler(4);
CHECK(!json::sax_parse(input, &error_handler, json::input_format_t::cbor));
}
// a long chain of tags needs no recursion
{
std::vector<std::uint8_t> input = {0xA1};
input.insert(input.end(), 100000, 0xC0);
input.insert(input.end(), {0x61, 0x61, 0x0A});
CHECK(json::from_cbor(input, true, true, ignore) == expected);
}
}
SECTION("non-string key (see #2766 and #3381)")
{
// only text strings map to JSON object keys; any other key is
@@ -1915,9 +2002,10 @@ TEST_CASE("CBOR")
{
json _;
// the chunks are concatenated as is, without checking that each
// chunk is valid UTF-8 on its own (RFC 8949, Section 3.2.3), so
// a code point split across two chunks yields a valid string
// with the default error handler keep, the chunks are concatenated
// as is, without checking that each chunk is valid UTF-8 on its
// own (RFC 8949, Section 3.2.3), so a code point split across two
// chunks yields a valid string
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})));
CHECK(_ == "\xc3\xa9");
CHECK(_.dump() == "\"\xc3\xa9\"");
@@ -1937,6 +2025,63 @@ TEST_CASE("CBOR")
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6");
}
SECTION("ill-formed UTF-8 in a chunk of an indefinite-length string (RFC 8949, Section 3.2.3)")
{
const auto strict = json::error_handler_t::strict;
const auto replace = json::error_handler_t::replace;
const auto ignore = json::error_handler_t::ignore;
const auto keep = json::error_handler_t::keep;
const auto tags = json::cbor_tag_handler_t::error;
// a code point split over two chunks
const std::vector<std::uint8_t> split = {0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff};
// a 4-byte code point split over two chunks
const std::vector<std::uint8_t> split_emoji = {0x7f, 0x62, 0xf0, 0x9f, 0x62, 0x98, 0x80, 0xff};
// the same in an object key
const std::vector<std::uint8_t> split_key = {0xa1, 0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff, 0x01};
// a valid chunk followed by one with a truncated code point
const std::vector<std::uint8_t> truncated = {0x7f, 0x62, 0x41, 0xc3, 0x61, 0xa9, 0xff};
// strict: the string is invalid
for (const auto& input :
{
split, split_emoji, split_key, truncated
})
{
CAPTURE(input)
json _;
CHECK_THROWS_AS(_ = json::from_cbor(input, true, true, tags, strict), json::parse_error&);
CHECK(json::from_cbor(input, true, false, tags, strict).is_discarded());
}
{
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(split, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(split_key, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
}
// replace and ignore: every chunk is sanitized on its own
CHECK(json::from_cbor(split, true, true, tags, replace) == "\xEF\xBF\xBD\xEF\xBF\xBD");
CHECK(json::from_cbor(split, true, true, tags, ignore) == "");
CHECK(json::from_cbor(truncated, true, true, tags, replace) == "A\xEF\xBF\xBD\xEF\xBF\xBD");
CHECK(json::from_cbor(truncated, true, true, tags, ignore) == "A");
CHECK(json::from_cbor(split_key, true, true, tags, replace) == json{{"\xEF\xBF\xBD\xEF\xBF\xBD", 1}});
// keep: unchanged, the chunks are concatenated as is
CHECK(json::from_cbor(split, true, true, tags, keep) == "\xc3\xa9");
CHECK(json::from_cbor(split_emoji, true, true, tags, keep) == "\xf0\x9f\x98\x80");
// conforming input is unaffected by the error handler
const std::vector<std::uint8_t> valid = {0x7f, 0x62, 0xc3, 0xa9, 0x64, 0xf0, 0x9f, 0x98, 0x80, 0x60, 0xff};
for (const auto handler :
{
strict, replace, ignore, keep
})
{
CHECK(json::from_cbor(valid, true, true, tags, handler) == "\xc3\xa9\xf0\x9f\x98\x80");
CHECK(json::from_cbor(std::vector<std::uint8_t>({0x7f, 0xff}), true, true, tags, handler) == "");
}
}
SECTION("many chunks in indefinite-length string")
{
constexpr std::size_t chunks = 100000;
-50
View File
@@ -1044,56 +1044,6 @@ TEST_CASE("flatten of structured values")
CHECK(flat_object.unflatten() == nlohmann::ordered_json::parse(R"({"b": {"1": 2}})"));
}
#if !defined(JSON_NOEXCEPTION)
SECTION("an object with the key \"0\" next to a non-index key does not round-trip")
{
// the third documented case in which j != j.flatten().unflatten()
const json value = {{"a", {{"0", 1}, {"b", 2}}}};
const json flat = value.flatten();
CHECK(flat == json({{"/a/0", 1}, {"/a/b", 2}}));
CHECK_THROWS_WITH_AS(flat.unflatten(),
"[json.exception.parse_error.109] parse error: array index 'b' is not a number", json::parse_error&);
// the same for other keys that are not valid array indices
CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/1a", 2}}).unflatten(),
"[json.exception.out_of_range.404] unresolved reference token '1a'", json::out_of_range&);
CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/-", 2}}).unflatten(),
"[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&);
}
SECTION("unflatten with the empty token among array indices")
{
CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/", 2}}).unflatten(),
"[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&);
CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/1", 2}, {"/a/", 3}}).unflatten(),
"[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&);
// without an index "0", the empty token is an ordinary object key
CHECK(json({{"/a/", 2}}).unflatten() == json({{"a", {{"", 2}}}}));
}
SECTION("value() with the empty pointer on a primitive root")
{
// unlike at(), value() rejects primitive values before it looks at the pointer
const json number = 1;
CHECK(number.at(""_json_pointer) == 1);
CHECK_THROWS_WITH_AS(number.value(""_json_pointer, 7),
"[json.exception.type_error.306] cannot use value() with number", json::type_error&);
const json string = "s";
CHECK_THROWS_WITH_AS(string.value(""_json_pointer, 7),
"[json.exception.type_error.306] cannot use value() with string", json::type_error&);
// contains() accepts the empty pointer on every value
CHECK(number.contains(""_json_pointer));
CHECK_FALSE(number.contains("/a"_json_pointer));
// arrays and objects return the default for a missing location
CHECK(json::array().value(""_json_pointer, json(7)) == json::array());
CHECK(json::array().value("/0"_json_pointer, 7) == 7);
}
#endif
SECTION("objects and arrays interleaved")
{
const json value =