Correct documentation errors found while hunting for bugs

- patch/patch_inplace: list the JSON pointer errors parse_error.106-109
  and out_of_range.402/404, and quote the actual parse_error.105 message.
- unflatten: list parse_error.106/107/108 and out_of_range.404.
- to_bson: list out_of_range.415 (binary subtype above 255) and note
  that 412 and 415 are new in 3.13.0.
- to_string: state that string_t must be convertible to std::string, also
  in the StringType requirements table.
- JSON Lines: a `while (input >> j)` loop also throws after the last value
  for concatenated JSON values; show a loop that works for both.
- BON8: a string gets 0xFF only if nothing follows it in the message; a
  string at the end of an array or object is ended by 0xFE.
- custom_string_type.hpp: add operator+=(char), which the "Always
  required" list asks for (json_pointer::to_string, flatten, unflatten,
  and diff did not compile), and an ADL int_to_string for diff and items.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-29 19:50:03 +02:00
parent 8ee8bff661
commit 29a647a08b
9 changed files with 82 additions and 14 deletions
+15 -1
View File
@@ -26,10 +26,24 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`parse_error.104`](../../home/exceptions.md#jsonexceptionparse_error104) if the JSON patch does not consist of
an array of objects.
- Throws [`parse_error.105`](../../home/exceptions.md#jsonexceptionparse_error105) if the JSON patch is malformed (e.g.,
mandatory attributes are missing); example: `"operation add must have member path"`.
mandatory attributes are missing); example: `"operation 'add' must have member 'path'"`.
- Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if an array index is out of range.
- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in a "path" or
"from" member begins with '0'; example: `"array index '01' must not begin with '0'"`.
- Throws [`parse_error.107`](../../home/exceptions.md#jsonexceptionparse_error107) if a "path" or "from" member is not
empty and does not begin with a slash (`/`); example: `"JSON pointer must be empty or begin with '/' - was: 'a'"`.
- Throws [`parse_error.108`](../../home/exceptions.md#jsonexceptionparse_error108) if a tilde (`~`) in a "path" or
"from" member is not followed by `0` or `1`; example: `"escape character '~' must be followed with '0' or '1'"`.
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a "path" or
"from" member is not a number; example: `"array index 'foo' is not a number"`.
- Throws [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if the array index `-` is used
where an existing element is required (the "path" of "replace", the "from" of "move" and "copy"); example:
`"array index '-' (3) is out of range"`.
- Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a JSON pointer inside the patch
could not be resolved successfully in the current JSON value; example: `"key baz not found"`.
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token of a JSON
pointer inside the patch cannot be resolved, e.g., `-` in a "remove" operation or `1a` for an array; example:
`"unresolved reference token '-'"`.
- Throws [`out_of_range.405`](../../home/exceptions.md#jsonexceptionout_of_range405) if JSON pointer has no parent
("add", "remove", "move")
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
@@ -22,10 +22,24 @@ No guarantees, value may be corrupted by an unsuccessful patch operation.
- Throws [`parse_error.104`](../../home/exceptions.md#jsonexceptionparse_error104) if the JSON patch does not consist of
an array of objects.
- Throws [`parse_error.105`](../../home/exceptions.md#jsonexceptionparse_error105) if the JSON patch is malformed (e.g.,
mandatory attributes are missing); example: `"operation add must have member path"`.
mandatory attributes are missing); example: `"operation 'add' must have member 'path'"`.
- Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if an array index is out of range.
- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in a "path" or
"from" member begins with '0'; example: `"array index '01' must not begin with '0'"`.
- Throws [`parse_error.107`](../../home/exceptions.md#jsonexceptionparse_error107) if a "path" or "from" member is not
empty and does not begin with a slash (`/`); example: `"JSON pointer must be empty or begin with '/' - was: 'a'"`.
- Throws [`parse_error.108`](../../home/exceptions.md#jsonexceptionparse_error108) if a tilde (`~`) in a "path" or
"from" member is not followed by `0` or `1`; example: `"escape character '~' must be followed with '0' or '1'"`.
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a "path" or
"from" member is not a number; example: `"array index 'foo' is not a number"`.
- Throws [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if the array index `-` is used
where an existing element is required (the "path" of "replace", the "from" of "move" and "copy"); example:
`"array index '-' (3) is out of range"`.
- Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a JSON pointer inside the patch
could not be resolved successfully in the current JSON value; example: `"key baz not found"`.
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token of a JSON
pointer inside the patch cannot be resolved, e.g., `-` in a "remove" operation or `1a` for an array; example:
`"unresolved reference token '-'"`.
- Throws [`out_of_range.405`](../../home/exceptions.md#jsonexceptionout_of_range405) if JSON pointer has no parent
("add", "remove", "move")
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
@@ -43,6 +43,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
string, or binary value exceeds the range of the 32-bit BSON length field; example:
`"BSON length 2147483661 exceeds maximum of 2147483647"`
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
exceeds 255, the maximum of the BSON binary subtype; example:
`"subtype 300 is too large for the BSON binary subtype (max 255)"`
## Complexity
@@ -92,4 +95,5 @@ pass before anything is written.
## Version history
- Added in version 3.4.0.
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
+2 -1
View File
@@ -10,7 +10,8 @@ This function implements a user-defined to_string for JSON objects.
## Template parameters
`BasicJsonType`
: a specialization of [`basic_json`](index.md)
: a specialization of [`basic_json`](index.md) whose [`string_t`](string_t.md) is convertible to `#!cpp std::string`;
for other string types, use [`dump`](dump.md), which returns a `string_t`
## Return value
@@ -27,8 +27,17 @@ The function can throw the following exceptions:
- Throws [`type_error.315`](../../home/exceptions.md#jsonexceptiontype_error315) if object values are not primitive
- Throws [`type_error.313`](../../home/exceptions.md#jsonexceptiontype_error313) if a key (JSON pointer) leads to a
conflicting nesting; example: `"invalid value to unflatten"`
- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in a key begins
with '0'; example: `"array index '01' must not begin with '0'"`
- Throws [`parse_error.107`](../../home/exceptions.md#jsonexceptionparse_error107) if a key is not empty and does not
begin with a slash (`/`); example: `"JSON pointer must be empty or begin with '/' - was: 'a'"`
- Throws [`parse_error.108`](../../home/exceptions.md#jsonexceptionparse_error108) if a tilde (`~`) in a key is not
followed by `0` or `1`; example: `"escape character '~' must be followed with '0' or '1'"`
- 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 cannot be an array index; example:
`"unresolved reference token 'x'"`
## Complexity
@@ -1,5 +1,6 @@
#pragma once
#include <cstddef>
#include <ostream>
#include <string>
@@ -8,10 +9,10 @@
// and nothing more of std::string's interface.
//
// Covers the "Always required" members, the extras needed for the binary
// formats, and the extras needed for JSON Pointer / flatten / unflatten /
// diff. Extending it further (e.g. for std::hash<basic_json> or to_bson) is
// a matter of adding the extra members listed in the "Required for other
// functionality" table.
// formats, JSON Pointer / flatten / unflatten, and the int_to_string overload
// needed for diff and items. Extending it further (e.g. for
// std::hash<basic_json> or to_bson) is a matter of adding the extra members
// listed in the "Required for other functionality" table.
//
// See https://json.nlohmann.me/features/types/template_parameters/#stringtype
class custom_string_type
@@ -93,6 +94,11 @@ class custom_string_type
data_.append(other.data_);
return *this;
}
custom_string_type& operator+=(char c)
{
data_.push_back(c);
return *this;
}
size_type find_first_of(char c, size_type pos = 0) const
{
@@ -116,6 +122,12 @@ class custom_string_type
return data_.end();
}
// found by ADL; converts array indices to keys in diff and items
friend void int_to_string(custom_string_type& target, std::size_t value)
{
target.data_ = std::to_string(value);
}
friend bool operator==(const custom_string_type& lhs, const custom_string_type& rhs)
{
return lhs.data_ == rhs.data_;
@@ -53,8 +53,9 @@ The library uses the following mapping from JSON values types to BON8 types acco
An integer that takes 2 to 4 bytes starts with a UTF-8 lead byte (0xC2..0xF7) that is followed by a byte that cannot
continue a UTF-8 character: 0x00..0x7F for positive and 0xC0..0xFF for negative integers. A string is terminated by
0xFF only if it is empty, if another string follows it, or if it is the last value of the message; otherwise, the first
byte of the next value ends it.
0xFF only if it is empty, if another string follows it, or if nothing follows it in the message; otherwise, the byte
after it ends it: the first byte of the next value, or the 0xFE that ends an array or object. For example, `["e"]` is
serialized as 0x81 0x65 0xFF, but `[1,2,3,4,"e"]` as 0x85 0x91 0x92 0x93 0x94 0x65 0xFE.
!!! success "Complete mapping"
@@ -140,7 +141,7 @@ Non-negative integers are read as number_unsigned, negative integers as number_i
arrays and objects with up to four elements that are terminated by 0xFE, unsorted object keys, or a 0xFF after a
string that would also end without it, are accepted. A second 0xFF is not a terminator but an empty string.
Strings must be valid UTF-8, and the last string 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
@@ -46,8 +46,20 @@ JSON Lines input with more than one value is treated as invalid JSON by the [`pa
}
```
with a JSON Lines input does not work, because the parser will try to parse one value after the last one.
with a JSON Lines input does not work, because the parser will try to parse one value after the last one and throw
a [`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) exception. The same happens for a
stream of *concatenated* (non-newline-delimited) JSON values: `operator>>` reads them one at a time, but the loop
above throws after the last value. To read either format with `operator>>`, check for the end of the stream before
each read:
This is different from parsing a stream of *concatenated* (non-newline-delimited) JSON values, for which
`operator>>` does work, provided that a value that is a number is followed by whitespace -- see its
[notes](../../api/operator_gtgt.md#notes) for details.
```cpp
json j;
while (input >> std::ws && input.peek() != std::char_traits<char>::eof())
{
input >> j;
std::cout << j << std::endl;
}
```
A value that is a number must be followed by whitespace -- see the [notes](../../api/operator_gtgt.md#notes) of
`operator>>` for details.
@@ -394,6 +394,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
| [`to_bson`](../../api/basic_json/to_bson.md) | `find(value_type)` and `npos` |
| [`parse`](../../api/basic_json/parse.md) from a `string_t` | the input adapters must accept it; otherwise pass a character range |
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
### Compatible types