Merge remote-tracking branch 'origin/develop' into claude/great-moser-6a6c1c

Resolves a conflict in include/nlohmann/detail/input/input_adapters.hpp
between this branch's memcpy fast path for get_elements() and develop's
lazy-diagnostics feature (#5234), which added a `begin` iterator member,
`get_consumed_count()`, and `copy_consumed_range()` to the same class.
The two features are orthogonal (lexer.hpp uses the new diagnostics
helpers; binary_reader.hpp uses get_elements()) and both are preserved.
single_include/nlohmann/json.hpp is regenerated from the merged sources.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-07-05 10:49:43 +02:00
44 changed files with 1000 additions and 90 deletions
@@ -47,7 +47,10 @@ jobs:
var hasPatch = artifacts.data.artifacts.some((artifact) => artifact.name == "amalgamation-patch"); var hasPatch = artifacts.data.artifacts.some((artifact) => artifact.name == "amalgamation-patch");
core.setOutput('has_patch', String(hasPatch)); core.setOutput('has_patch', String(hasPatch));
- run: unzip pr.zip # Extract the untrusted PR artifact into a dedicated empty directory and
# read only the two expected files by fixed path afterwards. This avoids a
# malicious archive overwriting workspace files or escaping via ../ paths.
- run: unzip -o pr.zip -d ./pr_artifact
- name: 'Comment on PR' - name: 'Comment on PR'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -55,8 +58,19 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
script: | script: |
var fs = require('fs'); var fs = require('fs');
const author = fs.readFileSync('./author') // Both values come from a fork-triggered workflow and are therefore
const issue_number = Number(fs.readFileSync('./number')); // attacker-controlled. Validate them strictly before use to prevent
// Markdown/mention injection and bogus REST API filters.
const author = fs.readFileSync('./pr_artifact/author', 'utf8').trim();
if (!/^[A-Za-z0-9-]{1,39}$/.test(author)) {
core.setFailed(`Refusing to proceed: untrusted author value '${author}' is not a valid GitHub username.`);
return;
}
const issue_number = Number(fs.readFileSync('./pr_artifact/number', 'utf8').trim());
if (!Number.isInteger(issue_number) || issue_number <= 0) {
core.setFailed('Refusing to proceed: untrusted PR number is not a positive integer.');
return;
}
const opts = github.rest.issues.listForRepo.endpoint.merge({ const opts = github.rest.issues.listForRepo.endpoint.merge({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
+17 -6
View File
@@ -41,12 +41,23 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
# Scan code using project's configuration on https://semgrep.dev/manage # The former returntocorp/semgrep-action is deprecated (the org was renamed
- uses: returntocorp/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # to semgrep/*); the maintained approach is to install the CLI and invoke
with: # it directly. We use `semgrep scan` (not `semgrep ci`, which requires a
publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} # login token): with no SEMGREP_APP_TOKEN configured this is exactly what
publishDeployment: ${{ secrets.SEMGREP_DEPLOYMENT_ID }} # the old action fell back to, running community rules with no token.
generateSarif: "1" # SEMGREP_APP_TOKEN is still passed through so registry auth works if a
# token is ever added.
- name: Install Semgrep
run: python3 -m pip install --user semgrep
# `semgrep scan --sarif` always exits 0 even with findings; continue-on-error
# is a safety net so the SARIF upload still runs if the scan itself errors.
- name: Run Semgrep
run: semgrep scan --config auto --sarif --output=semgrep.sarif
continue-on-error: true
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
# Upload SARIF file generated in previous step # Upload SARIF file generated in previous step
- name: Upload SARIF file - name: Upload SARIF file
+1
View File
@@ -54,6 +54,7 @@ This function is only needed to express two edge cases that cannot be realized w
- [`basic_json(initializer_list_t)`](basic_json.md) - create a JSON value from an initializer list - [`basic_json(initializer_list_t)`](basic_json.md) - create a JSON value from an initializer list
- [`object`](object.md) - create a JSON object value from an initializer list - [`object`](object.md) - create a JSON object value from an initializer list
- [Creating JSON values](../../features/creating_values.md) - the article on creating JSON values
## Version history ## Version history
+1
View File
@@ -75,6 +75,7 @@ Binary values are serialized as an object containing two keys:
- [to_string](to_string.md) returns a string representation of a JSON value - [to_string](to_string.md) returns a string representation of a JSON value
- [operator<<](../operator_ltlt.md) serialize to stream - [operator<<](../operator_ltlt.md) serialize to stream
- [Serialization](../../features/serialization.md) - the serialization article
## Version history ## Version history
@@ -64,6 +64,7 @@ Logarithmic in the size of the container, O(log(`size()`)).
- [emplace_back](emplace_back.md) add a value to an array - [emplace_back](emplace_back.md) add a value to an array
- [insert](insert.md) add values to an array/object - [insert](insert.md) add values to an array/object
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history ## Version history
@@ -58,6 +58,7 @@ Amortized constant.
- [operator+=](operator+=.md) add a value to an array/object - [operator+=](operator+=.md) add a value to an array/object
- [push_back](push_back.md) add a value to an array/object - [push_back](push_back.md) add a value to an array/object
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history ## Version history
+1
View File
@@ -206,6 +206,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
- [clear](clear.md) clears the contents - [clear](clear.md) clears the contents
- [insert](insert.md) add values to an array/object - [insert](insert.md) add values to an array/object
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history ## Version history
@@ -88,6 +88,7 @@ std::string format_as(const BasicJsonType& j)
- [dump](dump.md) - [dump](dump.md)
- [std::formatter<basic_json>](std_formatter.md) - the `std::format` (C++20) equivalent - [std::formatter<basic_json>](std_formatter.md) - the `std::format` (C++20) equivalent
- [Serialization](../../features/serialization.md) - the serialization article
## Version history ## Version history
+1
View File
@@ -155,6 +155,7 @@ overload (3).
- [get_ptr](get_ptr.md) get a pointer to the stored value - [get_ptr](get_ptr.md) get a pointer to the stored value
- [get_ref](get_ref.md) get a reference to the stored value - [get_ref](get_ref.md) get a reference to the stored value
- [operator ValueType](operator_ValueType.md) get a value via implicit conversion - [operator ValueType](operator_ValueType.md) get a value via implicit conversion
- [Converting values](../../features/conversions.md) - the type conversions article
## Version history ## Version history
@@ -62,6 +62,7 @@ Depends on the `json_serializer<ValueType>::from_json()` implementation.
- [get](get.md) get a value (explicit conversion) - [get](get.md) get a value (explicit conversion)
- [get_ref](get_ref.md) get a reference to the stored value - [get_ref](get_ref.md) get a reference to the stored value
- [get_ptr](get_ptr.md) get a pointer to the stored value - [get_ptr](get_ptr.md) get a pointer to the stored value
- [Converting values](../../features/conversions.md) - the type conversions article
## Version history ## Version history
@@ -57,6 +57,7 @@ the initializer list constructor `basic_json(initializer_list_t, bool, value_t)`
- [`basic_json(initializer_list_t)`](basic_json.md) - create a JSON value from an initializer list - [`basic_json(initializer_list_t)`](basic_json.md) - create a JSON value from an initializer list
- [`array`](array.md) - create a JSON array value from an initializer list - [`array`](array.md) - create a JSON array value from an initializer list
- [Creating JSON values](../../features/creating_values.md) - the article on creating JSON values
## Version history ## Version history
@@ -78,6 +78,7 @@ Linear in the size of the JSON value.
## See also ## See also
- [get](get.md) get a value (explicit conversion) - [get](get.md) get a value (explicit conversion)
- [Converting values](../../features/conversions.md) - the type conversions article
## Version history ## Version history
@@ -115,6 +115,7 @@ invalidates all iterators and all references.
- [emplace_back](emplace_back.md) add a value to an array - [emplace_back](emplace_back.md) add a value to an array
- [operator+=](operator+=.md) add a value to an array/object - [operator+=](operator+=.md) add a value to an array/object
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history ## Version history
@@ -50,6 +50,7 @@ provides `<format>`, controlled by the [`JSON_HAS_STD_FORMAT`](../macros/json_ha
- [dump](dump.md) - serialization - [dump](dump.md) - serialization
- [operator<<(std::ostream&)](../operator_ltlt.md) - serialize to stream - [operator<<(std::ostream&)](../operator_ltlt.md) - serialize to stream
- [format_as](format_as.md) - customization point used by `fmt::format` (fmtlib) - [format_as](format_as.md) - customization point used by `fmt::format` (fmtlib)
- [Serialization](../../features/serialization.md) - the serialization article
## Version history ## Version history
@@ -59,6 +59,7 @@ std::string to_string(const BasicJsonType& j)
## See also ## See also
- [dump](dump.md) - [dump](dump.md)
- [Serialization](../../features/serialization.md) - the serialization article
## Version history ## Version history
@@ -149,6 +149,7 @@ Basic guarantee: if an exception is thrown during the operation, the JSON value
- [insert](insert.md) add values to an array/object - [insert](insert.md) add values to an array/object
- [merge_patch](merge_patch.md) applies a JSON Merge Patch - [merge_patch](merge_patch.md) applies a JSON Merge Patch
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history ## Version history
@@ -56,6 +56,10 @@ Linear.
--8<-- "examples/operator_literal_json.output" --8<-- "examples/operator_literal_json.output"
``` ```
## See also
- [Creating JSON values](../features/creating_values.md) - the article on creating JSON values
## Version history ## Version history
- Added in version 1.0.0. - Added in version 1.0.0.
+6
View File
@@ -80,6 +80,12 @@ Linear.
```json ```json
--8<-- "examples/operator_ltlt__json_pointer.output" --8<-- "examples/operator_ltlt__json_pointer.output"
``` ```
## See also
- [dump](basic_json/dump.md) - serialize to a JSON-formatted string
- [Serialization](../features/serialization.md) - the serialization article
## Version history ## Version history
1. Added in version 1.0.0. Added support for indentation character and deprecated 1. Added in version 1.0.0. Added support for indentation character and deprecated
+2 -2
View File
@@ -85,7 +85,7 @@ Some important things:
If you just want to serialize/deserialize some structs, the `to_json`/`from_json` functions can be a lot of boilerplate. If you just want to serialize/deserialize some structs, the `to_json`/`from_json` functions can be a lot of boilerplate.
There are several macros to make your life easier as long as you want to use a JSON object as serialization. The macros are following the naming pattern, and you can chose the macro based on the needed features: There are several macros to make your life easier as long as you want to use a JSON object as serialization. The macros are following the naming pattern, and you can choose the macro based on the needed features:
- All the macros start with `NLOHMANN_DEFINE`. - All the macros start with `NLOHMANN_DEFINE`.
- If you want a macro for the derived object, use the [`DERIVED_TYPE`](../api/macros/nlohmann_define_derived_type.md) variant, otherwise use `TYPE`. - If you want a macro for the derived object, use the [`DERIVED_TYPE`](../api/macros/nlohmann_define_derived_type.md) variant, otherwise use `TYPE`.
@@ -139,7 +139,7 @@ For _derived_ classes and structs, use the following macros
```cpp ```cpp
namespace ns { namespace ns {
struct person_derived : person { struct person_derived : person {
std:string email; std::string email;
}; };
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(person_derived, person, email) NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(person_derived, person, email)
+1 -1
View File
@@ -11,7 +11,7 @@ Runtime assertions can be switched off by defining the preprocessor macro `NDEBU
## Change assertion behavior ## Change assertion behavior
The behavior of runtime assertions can be changes by defining macro [`JSON_ASSERT(x)`](../api/macros/json_assert.md) The behavior of runtime assertions can be changed by defining macro [`JSON_ASSERT(x)`](../api/macros/json_assert.md)
before including the `json.hpp` header. before including the `json.hpp` header.
## Function with runtime assertions ## Function with runtime assertions
@@ -123,7 +123,7 @@ The library uses the following mapping from JSON values types to BJData types ac
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/master/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"
@@ -146,7 +146,7 @@ The library uses the following mapping from JSON values types to BJData types ac
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/master/Binary_JData_Specification.md#optimized-binary-array
??? example ??? example
@@ -52,28 +52,29 @@ The library uses the following mapping from JSON values types to BSON types:
The library maps BSON record types to JSON value types as follows: The library maps BSON record types to JSON value types as follows:
| BSON type | BSON marker byte | JSON value type | | BSON type | BSON marker byte | JSON value type |
|-----------------------|------------------|-----------------| |--------------------------|------------------|-----------------|
| double | 0x01 | number_float | | double | 0x01 | number_float |
| string | 0x02 | string | | string | 0x02 | string |
| document | 0x03 | object | | document | 0x03 | object |
| array | 0x04 | array | | array | 0x04 | array |
| binary | 0x05 | binary | | binary | 0x05 | binary |
| undefined | 0x06 | *unsupported* | | undefined | 0x06 | *unsupported* |
| ObjectId | 0x07 | *unsupported* | | ObjectId | 0x07 | *unsupported* |
| boolean | 0x08 | boolean | | boolean | 0x08 | boolean |
| UTC Date-Time | 0x09 | *unsupported* | | UTC Date-Time | 0x09 | *unsupported* |
| null | 0x0A | null | | null | 0x0A | null |
| Regular Expr. | 0x0B | *unsupported* | | Regular Expr. | 0x0B | *unsupported* |
| DB Pointer | 0x0C | *unsupported* | | DB Pointer | 0x0C | *unsupported* |
| JavaScript Code | 0x0D | *unsupported* | | JavaScript Code | 0x0D | *unsupported* |
| Symbol | 0x0E | *unsupported* | | Symbol | 0x0E | *unsupported* |
| JavaScript Code | 0x0F | *unsupported* | | JavaScript Code w/ scope | 0x0F | *unsupported* |
| int32 | 0x10 | number_integer | | int32 | 0x10 | number_integer |
| uint64(Timestamp) | 0x11 | number_unsigned | | uint64(Timestamp) | 0x11 | number_unsigned |
| 128-bit decimal float | 0x13 | *unsupported* | | int64 | 0x12 | number_integer |
| Max Key | 0x7F | *unsupported* | | 128-bit decimal float | 0x13 | *unsupported* |
| Min Key | 0xFF | *unsupported* | | Max Key | 0x7F | *unsupported* |
| Min Key | 0xFF | *unsupported* |
!!! warning "Incomplete mapping" !!! warning "Incomplete mapping"
@@ -5,7 +5,7 @@ extremely small code sizes, fairly small message size, and extensibility without
!!! abstract "References" !!! abstract "References"
- [CBOR Website](http://cbor.io) - the main source on CBOR - [CBOR Website](http://cbor.io) - the main source on CBOR
- [CBOR Playground](http://cbor.me) - an interactive webpage to translate between JSON and CBOR - [CBOR Playground](http://cbor.me) - an interactive webpage to translate between JSON and CBOR
- [RFC 7049](https://tools.ietf.org/html/rfc7049) - the CBOR specification - [RFC 7049](https://tools.ietf.org/html/rfc7049) - the CBOR specification
@@ -37,22 +37,22 @@ The library uses the following mapping from JSON values types to CBOR types acco
| number_float | *any value representable by a float* | Single-Precision Float | 0xFA | | number_float | *any value representable by a float* | Single-Precision Float | 0xFA |
| number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB | | number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB |
| string | *length*: 0..23 | UTF-8 string | 0x60..0x77 | | string | *length*: 0..23 | UTF-8 string | 0x60..0x77 |
| string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 | | string | *length*: 24..255 | UTF-8 string (1 byte follow) | 0x78 |
| string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 | | string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 |
| string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A | | string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A |
| string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B | | string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B |
| array | *size*: 0..23 | array | 0x80..0x97 | | array | *size*: 0..23 | array | 0x80..0x97 |
| array | *size*: 23..255 | array (1 byte follow) | 0x98 | | array | *size*: 24..255 | array (1 byte follow) | 0x98 |
| array | *size*: 256..65535 | array (2 bytes follow) | 0x99 | | array | *size*: 256..65535 | array (2 bytes follow) | 0x99 |
| array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A | | array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A |
| array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B | | array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B |
| object | *size*: 0..23 | map | 0xA0..0xB7 | | object | *size*: 0..23 | map | 0xA0..0xB7 |
| object | *size*: 23..255 | map (1 byte follow) | 0xB8 | | object | *size*: 24..255 | map (1 byte follow) | 0xB8 |
| object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 | | object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 |
| object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA | | object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA |
| object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB | | object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB |
| binary | *size*: 0..23 | byte string | 0x40..0x57 | | binary | *size*: 0..23 | byte string | 0x40..0x57 |
| binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 | | binary | *size*: 24..255 | byte string (1 byte follow) | 0x58 |
| binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 | | binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 |
| binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A | | binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A |
| binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B | | binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B |
@@ -41,7 +41,7 @@ See [binary values](../binary_values.md) for more information.
|--------------------|-------------|--------------|-------------------|---------------| |--------------------|-------------|--------------|-------------------|---------------|
| BJData | 53.2 % | 91.1 % | 78.1 % | 96.6 % | | BJData | 53.2 % | 91.1 % | 78.1 % | 96.6 % |
| BJData (size) | 58.6 % | 92.1 % | 86.7 % | 97.4 % | | BJData (size) | 58.6 % | 92.1 % | 86.7 % | 97.4 % |
| BJData (size+tyoe) | 58.6 % | 92.1 % | 86.5 % | 97.4 % | | BJData (size+type) | 58.6 % | 92.1 % | 86.5 % | 97.4 % |
| BSON | 85.8 % | 95.2 % | 95.8 % | 106.7 % | | BSON | 85.8 % | 95.2 % | 95.8 % | 106.7 % |
| CBOR | 50.5 % | 86.3 % | 68.4 % | 88.0 % | | CBOR | 50.5 % | 86.3 % | 68.4 % | 88.0 % |
| MessagePack | 50.5 % | 86.0 % | 68.5 % | 87.9 % | | MessagePack | 50.5 % | 86.0 % | 68.5 % | 87.9 % |
+9 -9
View File
@@ -41,7 +41,7 @@ binary.has_subtype(); // returns false
binary_with_subtype.has_subtype(); // returns true binary_with_subtype.has_subtype(); // returns true
binary_with_subtype.clear_subtype(); binary_with_subtype.clear_subtype();
binary_with_subtype.has_subtype(); // returns true binary_with_subtype.has_subtype(); // returns false
binary_with_subtype.set_subtype(42); binary_with_subtype.set_subtype(42);
binary.set_subtype(23); binary.set_subtype(23);
@@ -146,7 +146,7 @@ as an array of uint8 values. The library implements this translation.
auto v = json::to_bjdata(j); auto v = json::to_bjdata(j);
``` ```
`v` is a `std::vector<std::uint8t>` with the following 20 elements: `v` is a `std::vector<std::uint8_t>` with the following 20 elements:
```c ```c
0x7B // '{' 0x7B // '{'
@@ -158,10 +158,10 @@ as an array of uint8 values. The library implements this translation.
0x7D // '}' 0x7D // '}'
``` ```
The following code uses the type and size optimization for UBJSON: The following code uses the type and size optimization for BJData:
```cpp ```cpp
// convert to UBJSON using the size and type optimization // convert to BJData using the size and type optimization
auto v = json::to_bjdata(j, true, true); auto v = json::to_bjdata(j, true, true);
``` ```
@@ -178,7 +178,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 UBJSON has **no binary type**, and deserializing `v` would Note that subtype (42) is **not** serialized and that BJData has **no binary type**, and deserializing `v` would
yield the following value: yield the following value:
```json ```json
@@ -205,7 +205,7 @@ unsigned 8-bit integer. If no subtype is given, the generic binary subtype 0x00
auto v = json::to_bson(j); auto v = json::to_bson(j);
``` ```
`v` is a `std::vector<std::uint8t>` with the following 22 elements: `v` is a `std::vector<std::uint8_t>` with the following 22 elements:
```c ```c
0x16 0x00 0x00 0x00 // number of bytes in the document 0x16 0x00 0x00 0x00 // number of bytes in the document
@@ -247,7 +247,7 @@ byte array.
auto v = json::to_cbor(j); auto v = json::to_cbor(j);
``` ```
`v` is a `std::vector<std::uint8t>` with the following 15 elements: `v` is a `std::vector<std::uint8_t>` with the following 15 elements:
```c ```c
0xA1 // map(1) 0xA1 // map(1)
@@ -291,7 +291,7 @@ If no subtype is given, the bin family (bin8, bin16, bin32) is used.
auto v = json::to_msgpack(j); auto v = json::to_msgpack(j);
``` ```
`v` is a `std::vector<std::uint8t>` with the following 14 elements: `v` is a `std::vector<std::uint8_t>` with the following 14 elements:
```c ```c
0x81 // fixmap1 0x81 // fixmap1
@@ -331,7 +331,7 @@ as an array of uint8 values. The library implements this translation.
auto v = json::to_ubjson(j); auto v = json::to_ubjson(j);
``` ```
`v` is a `std::vector<std::uint8t>` with the following 20 elements: `v` is a `std::vector<std::uint8_t>` with the following 20 elements:
```c ```c
0x7B // '{' 0x7B // '{'
+91
View File
@@ -0,0 +1,91 @@
# Converting values
A `basic_json` value stores JSON data, but most of the time you want to move that data into ordinary C++ types (an
`#!cpp int`, a `#!cpp std::string`, a `#!cpp std::vector`, or one of your own structs) and back. This page describes how
these conversions work.
## Getting values out
The [`get`](../api/basic_json/get.md) function template returns a copy of the stored value converted to the requested
type:
```cpp
json j = R"({"name": "Mary", "age": 42, "hobbies": ["hiking", "reading"]})"_json;
auto name = j["name"].get<std::string>(); // "Mary"
auto age = j["age"].get<int>(); // 42
auto hobbies = j["hobbies"].get<std::vector<std::string>>(); // {"hiking", "reading"}
```
!!! note "Getting a string without quotes"
A frequent point of confusion: use [`get`](../api/basic_json/get.md), **not** [`dump`](serialization.md), to read a
string value. `#!cpp j["name"].get<std::string>()` yields `#!cpp Mary`, whereas `#!cpp j["name"].dump()` yields the
JSON text `#!cpp "Mary"` (**with** quotes), because `dump` always produces a JSON text.
Alternatively, [`get_to`](../api/basic_json/get_to.md) writes into an existing variable and deduces the target type,
which avoids repeating it:
??? example
```cpp
--8<-- "examples/get_to.cpp"
```
Output:
```json
--8<-- "examples/get_to.output"
```
The library already knows how to convert to and from the scalar types and the STL containers (such as
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
object back to a `#!cpp std::map` or a JSON array back to a `#!cpp std::vector` therefore works without any extra code:
```cpp
json j = {{"one", 1}, {"two", 2}};
auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}}
```
## Implicit conversions
By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted:
```cpp
json j = "Hello";
std::string s = j; // implicit conversion, same as j.get<std::string>()
```
Implicit conversions are convenient but can be surprising (for example, in overload resolution or with `auto`). They can
be disabled by defining [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) to `#!cpp 0`,
which forces the explicit `get` form and can catch unintended conversions at compile time.
!!! warning "Conversions do not range-check numbers"
Just like C++ itself, the `get` family performs numeric conversions without range checks — retrieving a
floating-point value as an integer truncates it, and narrowing conversions may overflow. See
[number conversion](types/number_handling.md#number-conversion) for details and how to guard against it.
## Putting values in
The reverse direction works the same way: assigning or constructing a `json` from a C++ value converts it to JSON.
```cpp
std::vector<int> numbers = {1, 2, 3};
json j = numbers; // [1,2,3]
```
## Your own types
The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide
`to_json`/`from_json` functions (or use one of the convenience macros). This is described in detail on the
[arbitrary types conversions](arbitrary_types.md) page. Enums can be mapped to strings as described in
[specializing enum conversion](enum_conversion.md).
## See also
- [`get`](../api/basic_json/get.md) - get a copy converted to a given type
- [`get_to`](../api/basic_json/get_to.md) - convert into an existing variable
- [`get_ref`](../api/basic_json/get_ref.md) / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
@@ -0,0 +1,103 @@
# Creating JSON values
There are several ways to create a JSON value in memory. This page gives an overview; to read a value from JSON text
instead, see [parsing](parsing/index.md).
## From C++ values
Any value of a supported C++ type can be assigned to or used to construct a `json`:
```cpp
json j_number = 42;
json j_float = 3.141;
json j_string = "Hello";
json j_boolean = true;
json j_null = nullptr;
json j_vector = std::vector<int>{1, 2, 3}; // array
```
See [converting values](conversions.md) for the full set of supported types.
## With initializer lists
Objects and arrays can be written concisely with brace-enclosed initializer lists:
```cpp
// an array
json array = {1, 2, 3, 4};
// an object (a list of key/value pairs)
json object = {
{"pi", 3.141},
{"happy", true},
{"name", "Niels"},
{"nothing", nullptr},
{"list", {1, 0, 2}},
{"object", {{"currency", "USD"}, {"value", 42.99}}}
};
```
The library decides between an array and an object based on the content: a list whose elements are all two-element lists
with a string as the first element is treated as an object, everything else as an array.
!!! warning "Ambiguous cases: `#!cpp {}` vs. `#!cpp []`"
Because the same `#!cpp {}` syntax is used for both arrays and objects, some cases are ambiguous. To force a
particular type, use the explicit factory functions [`json::array`](../api/basic_json/array.md) and
[`json::object`](../api/basic_json/object.md):
```cpp
json empty_array_explicit = json::array(); // []
json empty_object_explicit = json::object(); // {}
// a JSON array with one object, not an object with one member
json array_of_objects = json::array({{"key", "value"}}); // [{"key":"value"}]
```
Related to this, single-element brace initialization such as `#!cpp json j{value};` wraps the element in a
single-element **array** by default, and its behavior even differs between compilers. See the
[FAQ](../home/faq.md#brace-initialization-yields-arrays) for details and the opt-in
[`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) macro.
## Building incrementally
A value can also be built up piece by piece. Accessing a non-existing object key or array index with
[`operator[]`](element_access/unchecked_access.md) creates the element on the fly:
```cpp
json j; // null
j["answer"]["everything"] = 42; // becomes an object
j["list"] = {1, 0, 2};
j["list"].push_back(3); // [1,0,2,3]
```
See [modifying values](modifying_values.md) for [`push_back`](../api/basic_json/push_back.md),
[`emplace`](../api/basic_json/emplace.md), and related functions.
## With the `_json` literal
The `_json` [user-defined literal](../api/operator_literal_json.md) parses a string at the call site and is a
convenient way to write a JSON value inline:
??? example
```cpp
--8<-- "examples/operator_literal_json.cpp"
```
Output:
```json
--8<-- "examples/operator_literal_json.output"
```
Note this **parses** the string, so `#!cpp "42"_json` is the number `#!cpp 42`, whereas `#!cpp json("42")` is the JSON
string `#!json "42"`.
## See also
- [`basic_json` constructors](../api/basic_json/basic_json.md) - all ways to construct a value
- [`array`](../api/basic_json/array.md) / [`object`](../api/basic_json/object.md) - force array or object type
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
- [Converting values](conversions.md) - which C++ types can be used
- [Parsing](parsing/index.md) - create a value from JSON text
@@ -29,7 +29,7 @@ otherwise.
| `#!cpp j.at("hobbies").at(0)` | `#!json "hiking"` | | `#!cpp j.at("hobbies").at(0)` | `#!json "hiking"` |
| `#!cpp j.at("hobbies").at(1)` | `#!json "reading"` | | `#!cpp j.at("hobbies").at(1)` | `#!json "reading"` |
The return value is a reference, so it can be modified by the original value. The return value is a reference, so it can be used to modify the original value.
??? example "Write access" ??? example "Write access"
@@ -62,7 +62,7 @@ non-existing, an exception is thrown.
[json.exception.out_of_range.401] array index 3 is out of range [json.exception.out_of_range.401] array index 3 is out of range
``` ```
When you [extended diagnostic messages](../../home/exceptions.md#extended-diagnostic-messages) are enabled by When [extended diagnostic messages](../../home/exceptions.md#extended-diagnostic-messages) are enabled by
defining [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md), the exception further gives information where defining [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md), the exception further gives information where
the key or index is missing or out of range. the key or index is missing or out of range.
@@ -41,7 +41,7 @@ you want to access and a default value in case there is no value stored with tha
The value function is a template, and the return type of the function is determined by the type of the provided The value function is a template, and the return type of the function is determined by the type of the provided
default value unless otherwise specified. This can have unexpected effects. In the example below, we store a 64-bit default value unless otherwise specified. This can have unexpected effects. In the example below, we store a 64-bit
unsigned integer. We get exactly that value when using [`operator[]`](../../api/basic_json/operator[].md). However, unsigned integer. We get exactly that value when using [`operator[]`](../../api/basic_json/operator[].md). However,
when we call `value` and provide `#!c 0` as default value, then `#!c -1` is returned. The occurs, because `#!c 0` when we call `value` and provide `#!c 0` as default value, then `#!c -1` is returned. This occurs, because `#!c 0`
has type `#!c int` which overflows when handling the value `#!c 18446744073709551615`. has type `#!c int` which overflows when handling the value `#!c 18446744073709551615`.
To address this issue, either provide a correctly typed default value or use the template parameter to specify the To address this issue, either provide a correctly typed default value or use the template parameter to specify the
+53
View File
@@ -0,0 +1,53 @@
# Features
This section describes the features of the library in detail. If you are new to the library, the pages below are
roughly ordered along a typical workflow: create or parse a value, access and modify it, convert it to and from your own
C++ types, and finally serialize it again.
## Creating and reading values
- [Creating JSON values](creating_values.md) — build values from literals, initializer lists, and STL containers, and
understand the `#!cpp {}` vs. `#!cpp []` ambiguity.
- [Parsing](parsing/index.md) — read a JSON value from a string, file, or stream, including
[JSON Lines](parsing/json_lines.md), [callbacks](parsing/parser_callbacks.md), the
[SAX interface](parsing/sax_interface.md), and [error handling](parsing/parse_exceptions.md).
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
## Accessing and modifying values
- [Element access](element_access/index.md) — unchecked ([`operator[]`](element_access/unchecked_access.md)),
checked ([`at`](element_access/checked_access.md)), and access with a
[default value](element_access/default_value.md).
- [JSON Pointer](json_pointer.md) — address values deep inside a document with [RFC 6901](https://tools.ietf.org/html/rfc6901) pointers.
- [Iterators](iterators.md) — traverse arrays and objects.
- [Modifying values](modifying_values.md) — add, update, merge, and remove elements.
- [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) — apply and compute structured changes.
## Converting to and from C++ types
- [Converting values](conversions.md) — get values out with [`get`](../api/basic_json/get.md)/[`get_to`](../api/basic_json/get_to.md),
and understand implicit conversions.
- [Arbitrary types conversions](arbitrary_types.md) — teach the library about your own structs and classes.
- [Specializing enum conversion](enum_conversion.md) — map enums to strings instead of integers.
## Serializing values
- [Serialization](serialization.md) — turn a value back into JSON text with [`dump`](../api/basic_json/dump.md),
including pretty-printing and handling of non-ASCII and invalid UTF-8.
- [Binary formats](binary_formats/index.md) — encode values more compactly as
[BJData](binary_formats/bjdata.md), [BSON](binary_formats/bson.md), [CBOR](binary_formats/cbor.md),
[MessagePack](binary_formats/messagepack.md), or [UBJSON](binary_formats/ubjson.md).
- [Binary values](binary_values.md) — store and exchange raw byte sequences.
## How values are stored and configured
- [Types](types/index.md) and [number handling](types/number_handling.md) — how JSON types map to C++ types and how
numbers are treated.
- [Object order](object_order.md) — keep insertion order with [`ordered_json`](../api/ordered_json.md).
- [Runtime assertions](assertions.md), [supported macros](macros.md), the [`nlohmann` namespace](namespace.md), and
[C++ modules](modules.md) — build-time and runtime configuration.
!!! tip "Looking for a specific function?"
This section gives conceptual overviews. For the precise signature, parameters, and return value of a function, see
the [API Documentation](../api/basic_json/index.md).
+1 -1
View File
@@ -130,7 +130,7 @@ for (auto& [key, val] : j_object.items())
### Iterating strings and binary values ### Iterating strings and binary values
Note that "value" means a JSON value in this setting, not values stored in the underlying containers. That is, `*begin()` returns the complete string or binary array and is also safe the underlying string or binary array is empty. Note that "value" means a JSON value in this setting, not values stored in the underlying containers. That is, `*begin()` returns the complete string or binary array and is also safe if the underlying string or binary array is empty.
??? example ??? example
+1 -1
View File
@@ -28,7 +28,7 @@ The library can also calculate a JSON patch (i.e., a **diff**) given two JSON va
For two JSON values *source* and *target*, the following code yields always true: For two JSON values *source* and *target*, the following code yields always true:
```cüü ```cpp
source.patch(diff(source, target)) == target; source.patch(diff(source, target)) == target;
``` ```
@@ -0,0 +1,77 @@
# Modifying values
Once a JSON value exists, its content can be changed: elements can be added, replaced, merged, and removed. This page
gives an overview of the available operations. For read access, see [element access](element_access/index.md).
## Adding to arrays
New elements are appended to an array with [`push_back`](../api/basic_json/push_back.md) or constructed in place with
[`emplace_back`](../api/basic_json/emplace_back.md). If the value is `#!json null`, it is converted to an array first, so
these functions can also be used to build an array from scratch.
```cpp
json j; // null
j.push_back(1); // [1]
j.push_back(2); // [1,2]
j.emplace_back(3); // [1,2,3]
// operator+= is a shorthand for push_back
j += 4; // [1,2,3,4]
```
## Adding to objects
The most common way to add or replace a member is [`operator[]`](element_access/unchecked_access.md), which inserts the
key if it does not exist yet:
```cpp
json j;
j["name"] = "Mary"; // {"name":"Mary"}
j["name"] = "John"; // {"name":"John"} (replaced)
```
[`emplace`](../api/basic_json/emplace.md) inserts a member only if the key is not already present, and reports whether
the insertion happened — useful for "add if absent" semantics.
## Merging objects
To merge one object into another, [`update`](../api/basic_json/update.md) copies all members from another object,
overwriting existing keys (similar to Python's `dict.update`). This is the idiomatic way to combine two objects.
??? example
```cpp
--8<-- "examples/update.cpp"
```
Output:
```json
--8<-- "examples/update.output"
```
For a recursive merge that follows [RFC 7386](https://tools.ietf.org/html/rfc7386), see
[JSON Merge Patch](merge_patch.md). To apply a sequence of well-defined edit operations, see
[JSON Patch](json_patch.md).
## Removing elements
Elements are removed with [`erase`](../api/basic_json/erase.md), which accepts an object key, an array index, or an
iterator. [`clear`](../api/basic_json/clear.md) empties a value while keeping its type, and
[`operator[]`](element_access/unchecked_access.md) combined with assignment can overwrite a value entirely.
```cpp
json j = {{"a", 1}, {"b", 2}, {"c", 3}};
j.erase("b"); // {"a":1,"c":3}
json a = {1, 2, 3, 4};
a.erase(1); // [1,3,4] (erase by index)
```
## See also
- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array
- [`emplace`](../api/basic_json/emplace.md) - insert into an object if the key is absent
- [`update`](../api/basic_json/update.md) - merge objects
- [`erase`](../api/basic_json/erase.md) / [`clear`](../api/basic_json/clear.md) - remove elements
- [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) - structured modifications
+2 -1
View File
@@ -59,7 +59,7 @@ may complain about undefined references.
Different versions are not necessarily ABI-incompatible, but the project does not actively track changes in the ABI and Different versions are not necessarily ABI-incompatible, but the project does not actively track changes in the ABI and
recommends that all parts of a codebase exchanging library types be built with the same version. Users can, **at their recommends that all parts of a codebase exchanging library types be built with the same version. Users can, **at their
own risk**, disable the version component of the linline namespace, allowing different versions but not own risk**, disable the version component of the inline namespace, allowing different versions but not
configurations to be used in cases where the linker would otherwise output undefined reference errors. configurations to be used in cases where the linker would otherwise output undefined reference errors.
To do so, define [`NLOHMANN_JSON_NAMESPACE_NO_VERSION`](../api/macros/nlohmann_json_namespace_no_version.md) to `1`. To do so, define [`NLOHMANN_JSON_NAMESPACE_NO_VERSION`](../api/macros/nlohmann_json_namespace_no_version.md) to `1`.
@@ -71,6 +71,7 @@ section to emulate the effect of the `NLOHMANN_JSON_NAMESPACE_NO_VERSION` macro.
Disabling the namespace version component and mixing ABI-incompatible versions will result in crashes or incorrect Disabling the namespace version component and mixing ABI-incompatible versions will result in crashes or incorrect
behavior. You have been warned! behavior. You have been warned!
## Disabling the inline namespace completely ## Disabling the inline namespace completely
When interoperability with code using a pre-3.11.0 version of the library is required, users can, **at their own risk** When interoperability with code using a pre-3.11.0 version of the library is required, users can, **at their own risk**
+3 -3
View File
@@ -37,7 +37,7 @@ The default type `nlohmann::json` uses a `std::map` to store JSON objects, and t
## Alternative behavior: preserve insertion order ## Alternative behavior: preserve insertion order
If you do want to preserve the **insertion order**, you can try the type [`nlohmann::ordered_json`](https://github.com/nlohmann/json/issues/2179). If you do want to preserve the **insertion order**, you can use the type [`nlohmann::ordered_json`](../api/ordered_json.md).
??? example ??? example
@@ -103,7 +103,7 @@ Assume file `input.json` contains the JSON object above:
```json ```json
{ {
"one": 1, "one": 1,
"three": 3 "three": 3,
"two": 2, "two": 2
} }
``` ```
+52 -4
View File
@@ -1,13 +1,61 @@
# Parsing # Parsing
!!! note This library can create a JSON value from a wide range of inputs. This page gives an overview of the available parsing
functions and how they behave; the linked pages go into more detail.
This page is under construction.
## Input ## Input
The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be
- a string (`#!cpp std::string`, C string, or string literal),
- a `#!cpp std::istream` (e.g., an `#!cpp std::ifstream` reading from a file),
- a `#!cpp FILE*` pointer,
- a pair of iterators over a contiguous range (e.g., a `#!cpp std::vector<std::uint8_t>`), or
- a contiguous container.
```cpp
// parse from a string
json j = json::parse(R"({"happy": true, "pi": 3.141})");
// parse from a file
std::ifstream f("example.json");
json data = json::parse(f);
```
The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value.
Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format.
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
## SAX vs. DOM parsing ## SAX vs. DOM parsing
The library offers two parsing models:
- **DOM parsing** (the default): the complete input is read and stored as an in-memory `basic_json` value that can be
traversed and modified freely. This is what [`parse`](../../api/basic_json/parse.md) does, and it is the right choice
for most use cases.
- **SAX parsing**: instead of building a value, the parser reports events (such as "a string was read" or "an object
started") to a handler that you implement. This avoids building the full value in memory and is useful for very large
inputs or when you only need to extract parts of the input. See the [SAX interface](sax_interface.md) for details and
[`sax_parse`](../../api/basic_json/sax_parse.md) for the API.
You can influence a DOM parse without switching to the SAX interface by passing a
[parser callback](parser_callbacks.md), which is called during parsing and can, for example, discard parts of the input.
## Exceptions ## Exceptions
See [parsing and exceptions](parse_exceptions.md). When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
options.
## See also
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
- [SAX interface](sax_interface.md) - implement a custom SAX handler
- [parsing and exceptions](parse_exceptions.md) - control error handling
+129
View File
@@ -0,0 +1,129 @@
# Serialization
Serialization is the process of turning a JSON value back into JSON text. It is the counterpart to
[parsing](parsing/index.md). The central function is [`dump`](../api/basic_json/dump.md), which returns the JSON text as
a string.
```cpp
json j = {{"pi", 3.141}, {"happy", true}};
std::string s = j.dump(); // {"happy":true,"pi":3.141}
```
To write a value directly to a stream (for example, a file or `#!cpp std::cout`), the
[`operator<<`](../api/operator_ltlt.md) is provided:
```cpp
std::cout << j << std::endl;
```
!!! note "String, not raw value"
`dump` always returns a **JSON text**. Serializing a JSON string therefore includes the surrounding quotes and
escapes special characters. To obtain the *contained* string value without quotes, use
[`get<std::string>()`](conversions.md) instead of `dump`. See the [converting values](conversions.md) page.
## Pretty-printing
By default, `dump` produces the most compact representation without any superfluous whitespace. Passing a non-negative
`indent` argument pretty-prints the output with the given number of spaces per level:
??? example
```cpp
--8<-- "examples/dump.cpp"
```
Output:
```json
--8<-- "examples/dump.output"
```
The indentation character can be changed with the second argument (e.g., a tab `#!cpp '\t'`). An `indent` of `0` inserts
newlines but no leading spaces, and the default of `#!cpp -1` selects the compact single-line form.
## Non-ASCII characters
Strings are stored and serialized as UTF-8 (see [types](types/index.md#strings)). By default, `dump` copies valid
non-ASCII characters as-is. Setting the third argument `ensure_ascii` to `#!cpp true` escapes all non-ASCII characters
with `\uXXXX` sequences, so that the output contains only ASCII characters:
```cpp
json j = "苹果";
j.dump(); // "苹果"
j.dump(-1, ' ', true); // "苹果"
```
## Handling invalid UTF-8
If a string contains invalid UTF-8 sequences (for example, because it holds data in another encoding such as Latin-1),
serialization fails by default. The fourth argument of `dump` selects an
[`error_handler`](../api/basic_json/error_handler_t.md):
- `strict` (default) — throw a [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) exception.
- `replace` — replace invalid bytes with the Unicode replacement character U+FFFD (``).
- `ignore` — silently drop invalid bytes.
??? example
```cpp
--8<-- "examples/error_handler_t.cpp"
```
Output:
```json
--8<-- "examples/error_handler_t.output"
```
!!! tip "Avoiding invalid UTF-8"
The best fix is to ensure that all strings are UTF-8 encoded before storing them. See the
[FAQ on non-ASCII characters](../home/faq.md#parse-errors-reading-non-ascii-characters) for how to convert wide or
Latin-1 strings.
## Numbers, NaN, and binary values
- **Numbers** are serialized with enough precision to round-trip; see [number serialization](types/number_handling.md#number-serialization).
- **NaN and infinity** cannot be represented in JSON and are serialized as `#!json null`; see
[NaN handling](types/number_handling.md#nan-handling). The [binary formats](binary_formats/index.md) can preserve
them.
- **Binary values** have no JSON representation and are serialized as a helper object for debugging only; see
[binary values](binary_values.md#serialization).
## Using `std::format`, `std::print`, and `fmt`
Since version 3.12.0, JSON values can be formatted directly with C++20's
[`std::format`](https://en.cppreference.com/w/cpp/utility/format/format) whenever the standard library provides the
`<format>` header (controlled by [`JSON_HAS_STD_FORMAT`](../api/macros/json_has_std_format.md)). This is enabled by the
[`std::formatter<basic_json>`](../api/basic_json/std_formatter.md) specialization, which also makes JSON values work with
`std::format_to` and with C++23's `std::print`/`std::println`:
```cpp
std::print("{}", j); // compact, like j.dump()
std::print("{:2}", j); // pretty-printed with indent 2 (like j.dump(2))
std::println("{:#}", j); // pretty-printed with the default indent
```
The format spec mirrors the `dump` parameters: `#!cpp "{:#}"` pretty-prints, a width such as `#!cpp "{:2}"` sets the
indent, and a fill-and-align prefix such as `#!cpp "{:.>#}"` sets the indent character.
For the [{fmt}](https://github.com/fmtlib/fmt) library, the library ships a
[`format_as`](../api/basic_json/format_as.md) helper. Note its behavior depends on the `fmt` version; see the
[FAQ entry](../home/faq.md#using-json-values-with-stdformat-or-fmt) for the details and a recipe for a full
`fmt::formatter` specialization.
## Serializing to other formats
Besides JSON text, a value can also be serialized to the more compact [binary formats](binary_formats/index.md)
(BJData, BSON, CBOR, MessagePack, UBJSON).
## See also
- [`dump`](../api/basic_json/dump.md) - serialize to a JSON-formatted string
- [`operator<<`](../api/operator_ltlt.md) - serialize to a stream
- [`to_string`](../api/basic_json/to_string.md) - user-defined-conversion helper
- [`std::formatter<basic_json>`](../api/basic_json/std_formatter.md) - use JSON values with `std::format` and `std::print`
- [`format_as`](../api/basic_json/format_as.md) - use JSON values with the {fmt} library
- [Parsing](parsing/index.md) - the reverse operation
+1 -1
View File
@@ -137,7 +137,7 @@ The choice of `object_t` influences the behavior of the JSON class. With the def
### Key order ### Key order
The order name/value pairs are added to the object are *not* preserved by the library. Therefore, iterating an object may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in alphabetical order as `std::map` with `std::less` is used by default. Please note this behavior conforms to [RFC 8259](https://tools.ietf.org/html/rfc8259), because any order implements the specified "unordered" nature of JSON objects. The order in which name/value pairs are added to the object is *not* preserved by the library. Therefore, iterating an object may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in alphabetical order as `std::map` with `std::less` is used by default. Please note this behavior conforms to [RFC 8259](https://tools.ietf.org/html/rfc8259), because any order implements the specified "unordered" nature of JSON objects.
### Limits ### Limits
@@ -91,7 +91,7 @@ This is the same behavior as the code `#!c double x = 3.141592653589793238462643
!!! success "Interoperability" !!! success "Interoperability"
- The library interoperable with respect to the specification, because its supported range $[-2^{63}, 2^{64}-1]$ is - The library is interoperable with respect to the specification, because its supported range $[-2^{63}, 2^{64}-1]$ is
larger than the described range $[-2^{53}+1, 2^{53}-1]$. larger than the described range $[-2^{53}+1, 2^{53}-1]$.
- All integers outside the range $[-2^{63}, 2^{64}-1]$, as well as floating-point numbers are stored as `double`. - All integers outside the range $[-2^{63}, 2^{64}-1]$, as well as floating-point numbers are stored as `double`.
This also concurs with the specification above. This also concurs with the specification above.
@@ -153,7 +153,7 @@ NaN (not-a-number) cannot be expressed with the number syntax described above an
That is, there is no way to *parse* a NaN value. However, assignments can store NaN values in a JSON value. That is, there is no way to *parse* a NaN value. However, assignments can store NaN values in a JSON value.
This library serializes NaN values as `#!js null`. This corresponds to the behavior of JavaScript's This library serializes NaN values as `#!js null`. This corresponds to the behavior of JavaScript's
[`JSON.stringify`](https://www.w3schools.com/js/js_json_stringify.asp) function. [`JSON.stringify`](https://www.w3schools.com/js/js_json_stringify.asp) function.
!!! example !!! example
@@ -182,7 +182,7 @@ This library serializes NaN values as `#!js null`. This corresponds to the beha
### Number comparison ### Number comparison
Floating-point inside JSON values numbers are compared with `#!c json::number_float_t::operator==` which is Floating-point numbers inside JSON values are compared with `#!c json::number_float_t::operator==` which is
`#!c double::operator==` by default. `#!c double::operator==` by default.
!!! example "Alternative comparison functions" !!! example "Alternative comparison functions"
@@ -203,8 +203,8 @@ Floating-point inside JSON values numbers are compared with `#!c json::number_fl
```cpp ```cpp
bool my_equal(const_reference lhs, const_reference rhs) bool my_equal(const_reference lhs, const_reference rhs)
{ {
const auto lhs_type lhs.type(); const auto lhs_type = lhs.type();
const auto rhs_type rhs.type(); const auto rhs_type = rhs.type();
if (lhs_type == rhs_type) if (lhs_type == rhs_type)
{ {
switch(lhs_type) switch(lhs_type)
@@ -230,7 +230,7 @@ Floating-point inside JSON values numbers are compared with `#!c json::number_fl
### Number conversion ### Number conversion
Just like the C++ language itself, the `get` family of functions allows conversions between unsigned and signed Just like the C++ language itself, the `get` family of functions allows conversions between unsigned and signed
integers, and between integers and floating-point values to integers. This behavior may be surprising. integers, and between integers and floating-point values. This behavior may be surprising.
!!! warning "Unconditional number conversions" !!! warning "Unconditional number conversions"
+8 -3
View File
@@ -57,6 +57,7 @@ nav:
- home/customers.md - home/customers.md
- home/sponsors.md - home/sponsors.md
- Features: - Features:
- features/index.md
- features/arbitrary_types.md - features/arbitrary_types.md
- Binary Formats: - Binary Formats:
- features/binary_formats/index.md - features/binary_formats/index.md
@@ -67,16 +68,18 @@ nav:
- features/binary_formats/ubjson.md - features/binary_formats/ubjson.md
- features/binary_values.md - features/binary_values.md
- features/comments.md - features/comments.md
- features/trailing_commas.md - features/conversions.md
- features/creating_values.md
- Element Access: - Element Access:
- features/element_access/index.md - features/element_access/index.md
- features/element_access/unchecked_access.md - features/element_access/unchecked_access.md
- features/element_access/checked_access.md - features/element_access/checked_access.md
- features/element_access/default_value.md - features/element_access/default_value.md
- features/iterators.md - features/iterators.md
- features/json_pointer.md
- features/json_patch.md
- features/merge_patch.md - features/merge_patch.md
- features/json_patch.md
- features/json_pointer.md
- features/modifying_values.md
- features/modules.md - features/modules.md
- 'nlohmann Namespace': features/namespace.md - 'nlohmann Namespace': features/namespace.md
- features/object_order.md - features/object_order.md
@@ -87,8 +90,10 @@ nav:
- features/parsing/parser_callbacks.md - features/parsing/parser_callbacks.md
- features/parsing/sax_interface.md - features/parsing/sax_interface.md
- features/assertions.md - features/assertions.md
- features/serialization.md
- features/enum_conversion.md - features/enum_conversion.md
- features/macros.md - features/macros.md
- features/trailing_commas.md
- Types: - Types:
- features/types/index.md - features/types/index.md
- features/types/number_handling.md - features/types/number_handling.md
@@ -161,8 +161,18 @@ class iterator_input_adapter
public: public:
using char_type = typename std::iterator_traits<IteratorType>::value_type; using char_type = typename std::iterator_traits<IteratorType>::value_type;
// Whether the lexer may reconstruct already-consumed input on demand (for
// diagnostics) instead of copying every scanned character eagerly. This is
// only sound for multi-pass, randomly-addressable byte input: the iterator
// must be random-access (so the consumed prefix can be revisited in O(1))
// and each element must map 1:1 to an input byte (wide inputs are wrapped
// in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
&& sizeof(char_type) == 1;
iterator_input_adapter(IteratorType first, IteratorType last) iterator_input_adapter(IteratorType first, IteratorType last)
: current(std::move(first)), end(std::move(last)) : begin(first), current(std::move(first)), end(std::move(last))
{} {}
typename char_traits<char_type>::int_type get_character() typename char_traits<char_type>::int_type get_character()
@@ -177,6 +187,22 @@ class iterator_input_adapter
return char_traits<char_type>::eof(); return char_traits<char_type>::eof();
} }
// number of characters consumed from the input so far
std::size_t get_consumed_count() const
{
return static_cast<std::size_t>(std::distance(begin, current));
}
// append the already-consumed characters in the half-open range
// [first_index, last_index) to @a out; only valid when supports_seek
template<typename ContainerType>
void copy_consumed_range(std::size_t first_index, std::size_t last_index, ContainerType& out) const
{
const auto from = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(first_index));
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
out.insert(out.end(), from, to);
}
// Copy up to count * sizeof(T) bytes into dest, returning the number of // Copy up to count * sizeof(T) bytes into dest, returning the number of
// bytes actually read. For contiguous iterators (e.g. pointers) this is a // bytes actually read. For contiguous iterators (e.g. pointers) this is a
// single std::memcpy; for general iterators we fall back to processing the // single std::memcpy; for general iterators we fall back to processing the
@@ -239,6 +265,7 @@ class iterator_input_adapter
return count * sizeof(T); return count * sizeof(T);
} }
IteratorType begin;
IteratorType current; IteratorType current;
IteratorType end; IteratorType end;
+100 -7
View File
@@ -103,6 +103,28 @@ class lexer_base
} }
} }
}; };
// Detect whether an input adapter can reconstruct already-consumed input on
// demand (see iterator_input_adapter::supports_seek). Adapters that do not
// expose the flag - e.g. file, stream, wide-string, and user-defined adapters -
// are treated as non-seekable streaming input, for which the lexer keeps
// copying every scanned character eagerly. The value is read via tag dispatch
// on is_detected so the flag is only referenced for adapters that provide it.
template<typename InputAdapterType>
using detect_supports_seek = decltype(InputAdapterType::supports_seek);
template<typename InputAdapterType>
constexpr bool input_adapter_supports_seek(std::true_type /*detected*/)
{
return InputAdapterType::supports_seek;
}
template<typename InputAdapterType>
constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
{
return false;
}
/*! /*!
@brief lexical analysis @brief lexical analysis
@@ -118,6 +140,12 @@ class lexer : public lexer_base<BasicJsonType>
using char_type = typename InputAdapterType::char_type; using char_type = typename InputAdapterType::char_type;
using char_int_type = typename char_traits<char_type>::int_type; using char_int_type = typename char_traits<char_type>::int_type;
/// whether the last read token can be reconstructed from the input adapter
/// on demand (in error paths) instead of being copied on every scanned
/// character; see input_adapter_supports_seek
static constexpr bool lazy_token_string =
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
public: public:
using token_type = typename lexer_base<BasicJsonType>::token_type; using token_type = typename lexer_base<BasicJsonType>::token_type;
@@ -1327,8 +1355,24 @@ scan_number_done:
void reset() noexcept void reset() noexcept
{ {
token_buffer.clear(); token_buffer.clear();
token_string.clear();
decimal_point_position = std::string::npos; decimal_point_position = std::string::npos;
note_token_start(std::integral_constant<bool, lazy_token_string> {});
}
/// seekable adapter: remember where the current token starts so it can be
/// reconstructed from the input on error; current has already been
/// consumed, hence the -1
void note_token_start(std::true_type /*lazy*/) noexcept
{
token_string_start = ia.get_consumed_count() - 1;
}
/// streaming adapter: start copying the token eagerly, beginning with the
/// already-read first character
void note_token_start(std::false_type /*lazy*/) noexcept
{
token_string.clear();
token_string.push_back(char_traits<char_type>::to_char_type(current)); token_string.push_back(char_traits<char_type>::to_char_type(current));
} }
@@ -1357,10 +1401,9 @@ scan_number_done:
current = ia.get_character(); current = ia.get_character();
} }
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof())) // seekable adapters reconstruct the token lazily on error (see
{ // get_token_string), so the eager per-character copy is skipped
token_string.push_back(char_traits<char_type>::to_char_type(current)); capture_char(std::integral_constant<bool, lazy_token_string> {});
}
if (current == '\n') if (current == '\n')
{ {
@@ -1371,6 +1414,18 @@ scan_number_done:
return current; return current;
} }
/// seekable adapter: nothing to capture, the token is rebuilt on error
void capture_char(std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: copy the scanned character into token_string
void capture_char(std::false_type /*lazy*/)
{
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
}
/*! /*!
@brief unget current character (read it again on next get) @brief unget current character (read it again on next get)
@@ -1398,6 +1453,15 @@ scan_number_done:
--position.chars_read_current_line; --position.chars_read_current_line;
} }
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
}
/// seekable adapter: nothing was captured, so nothing to undo
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: drop the character copied by the matching get()
void uncapture_char(std::false_type /*lazy*/)
{
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof())) if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{ {
JSON_ASSERT(!token_string.empty()); JSON_ASSERT(!token_string.empty());
@@ -1455,14 +1519,38 @@ scan_number_done:
return position; return position;
} }
/// seekable adapter: rebuild the last read token from the input on demand
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
{
// a pending unget of a real (non-EOF) character means that character
// was consumed from the input but is not part of the token; EOF is
// never consumed, so it must not be subtracted (mirrors unget())
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u);
if (JSON_HEDLEY_LIKELY(stop >= token_string_start))
{
ia.copy_consumed_range(token_string_start, stop, out);
}
return out;
}
/// streaming adapter: the token was copied eagerly while scanning
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& /*out*/, std::false_type /*lazy*/) const
{
return token_string;
}
/// return the last read token (for errors only). Will never contain EOF /// return the last read token (for errors only). Will never contain EOF
/// (an arbitrary value that is not a valid char value, often -1), because /// (an arbitrary value that is not a valid char value, often -1), because
/// 255 may legitimately occur. May contain NUL, which should be escaped. /// 255 may legitimately occur. May contain NUL, which should be escaped.
std::string get_token_string() const std::string get_token_string() const
{ {
std::vector<char_type> reconstructed;
const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {});
// escape control characters // escape control characters
std::string result; std::string result;
for (const auto c : token_string) for (const auto c : chars)
{ {
if (static_cast<unsigned char>(c) <= '\x1F') if (static_cast<unsigned char>(c) <= '\x1F')
{ {
@@ -1623,9 +1711,14 @@ scan_number_done:
/// the start position of the current token /// the start position of the current token
position_t position {}; position_t position {};
/// raw input token string (for error messages) /// raw input token string for error messages; only populated for streaming
/// adapters (seekable adapters reconstruct it lazily via token_string_start)
std::vector<char_type> token_string {}; std::vector<char_type> token_string {};
/// start offset of the current token within the input, used to reconstruct
/// the last read token on error for seekable adapters (see collect_token_chars)
std::size_t token_string_start = 0;
/// buffer for variable-length tokens (numbers, strings) /// buffer for variable-length tokens (numbers, strings)
string_t token_buffer {}; string_t token_buffer {};
+128 -8
View File
@@ -6993,8 +6993,18 @@ class iterator_input_adapter
public: public:
using char_type = typename std::iterator_traits<IteratorType>::value_type; using char_type = typename std::iterator_traits<IteratorType>::value_type;
// Whether the lexer may reconstruct already-consumed input on demand (for
// diagnostics) instead of copying every scanned character eagerly. This is
// only sound for multi-pass, randomly-addressable byte input: the iterator
// must be random-access (so the consumed prefix can be revisited in O(1))
// and each element must map 1:1 to an input byte (wide inputs are wrapped
// in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
&& sizeof(char_type) == 1;
iterator_input_adapter(IteratorType first, IteratorType last) iterator_input_adapter(IteratorType first, IteratorType last)
: current(std::move(first)), end(std::move(last)) : begin(first), current(std::move(first)), end(std::move(last))
{} {}
typename char_traits<char_type>::int_type get_character() typename char_traits<char_type>::int_type get_character()
@@ -7009,6 +7019,22 @@ class iterator_input_adapter
return char_traits<char_type>::eof(); return char_traits<char_type>::eof();
} }
// number of characters consumed from the input so far
std::size_t get_consumed_count() const
{
return static_cast<std::size_t>(std::distance(begin, current));
}
// append the already-consumed characters in the half-open range
// [first_index, last_index) to @a out; only valid when supports_seek
template<typename ContainerType>
void copy_consumed_range(std::size_t first_index, std::size_t last_index, ContainerType& out) const
{
const auto from = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(first_index));
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
out.insert(out.end(), from, to);
}
// Copy up to count * sizeof(T) bytes into dest, returning the number of // Copy up to count * sizeof(T) bytes into dest, returning the number of
// bytes actually read. For contiguous iterators (e.g. pointers) this is a // bytes actually read. For contiguous iterators (e.g. pointers) this is a
// single std::memcpy; for general iterators we fall back to processing the // single std::memcpy; for general iterators we fall back to processing the
@@ -7071,6 +7097,7 @@ class iterator_input_adapter
return count * sizeof(T); return count * sizeof(T);
} }
IteratorType begin;
IteratorType current; IteratorType current;
IteratorType end; IteratorType end;
@@ -7551,6 +7578,28 @@ class lexer_base
} }
} }
}; };
// Detect whether an input adapter can reconstruct already-consumed input on
// demand (see iterator_input_adapter::supports_seek). Adapters that do not
// expose the flag - e.g. file, stream, wide-string, and user-defined adapters -
// are treated as non-seekable streaming input, for which the lexer keeps
// copying every scanned character eagerly. The value is read via tag dispatch
// on is_detected so the flag is only referenced for adapters that provide it.
template<typename InputAdapterType>
using detect_supports_seek = decltype(InputAdapterType::supports_seek);
template<typename InputAdapterType>
constexpr bool input_adapter_supports_seek(std::true_type /*detected*/)
{
return InputAdapterType::supports_seek;
}
template<typename InputAdapterType>
constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
{
return false;
}
/*! /*!
@brief lexical analysis @brief lexical analysis
@@ -7566,6 +7615,12 @@ class lexer : public lexer_base<BasicJsonType>
using char_type = typename InputAdapterType::char_type; using char_type = typename InputAdapterType::char_type;
using char_int_type = typename char_traits<char_type>::int_type; using char_int_type = typename char_traits<char_type>::int_type;
/// whether the last read token can be reconstructed from the input adapter
/// on demand (in error paths) instead of being copied on every scanned
/// character; see input_adapter_supports_seek
static constexpr bool lazy_token_string =
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
public: public:
using token_type = typename lexer_base<BasicJsonType>::token_type; using token_type = typename lexer_base<BasicJsonType>::token_type;
@@ -8775,8 +8830,24 @@ scan_number_done:
void reset() noexcept void reset() noexcept
{ {
token_buffer.clear(); token_buffer.clear();
token_string.clear();
decimal_point_position = std::string::npos; decimal_point_position = std::string::npos;
note_token_start(std::integral_constant<bool, lazy_token_string> {});
}
/// seekable adapter: remember where the current token starts so it can be
/// reconstructed from the input on error; current has already been
/// consumed, hence the -1
void note_token_start(std::true_type /*lazy*/) noexcept
{
token_string_start = ia.get_consumed_count() - 1;
}
/// streaming adapter: start copying the token eagerly, beginning with the
/// already-read first character
void note_token_start(std::false_type /*lazy*/) noexcept
{
token_string.clear();
token_string.push_back(char_traits<char_type>::to_char_type(current)); token_string.push_back(char_traits<char_type>::to_char_type(current));
} }
@@ -8805,10 +8876,9 @@ scan_number_done:
current = ia.get_character(); current = ia.get_character();
} }
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof())) // seekable adapters reconstruct the token lazily on error (see
{ // get_token_string), so the eager per-character copy is skipped
token_string.push_back(char_traits<char_type>::to_char_type(current)); capture_char(std::integral_constant<bool, lazy_token_string> {});
}
if (current == '\n') if (current == '\n')
{ {
@@ -8819,6 +8889,18 @@ scan_number_done:
return current; return current;
} }
/// seekable adapter: nothing to capture, the token is rebuilt on error
void capture_char(std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: copy the scanned character into token_string
void capture_char(std::false_type /*lazy*/)
{
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
}
/*! /*!
@brief unget current character (read it again on next get) @brief unget current character (read it again on next get)
@@ -8846,6 +8928,15 @@ scan_number_done:
--position.chars_read_current_line; --position.chars_read_current_line;
} }
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
}
/// seekable adapter: nothing was captured, so nothing to undo
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: drop the character copied by the matching get()
void uncapture_char(std::false_type /*lazy*/)
{
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof())) if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{ {
JSON_ASSERT(!token_string.empty()); JSON_ASSERT(!token_string.empty());
@@ -8903,14 +8994,38 @@ scan_number_done:
return position; return position;
} }
/// seekable adapter: rebuild the last read token from the input on demand
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
{
// a pending unget of a real (non-EOF) character means that character
// was consumed from the input but is not part of the token; EOF is
// never consumed, so it must not be subtracted (mirrors unget())
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u);
if (JSON_HEDLEY_LIKELY(stop >= token_string_start))
{
ia.copy_consumed_range(token_string_start, stop, out);
}
return out;
}
/// streaming adapter: the token was copied eagerly while scanning
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& /*out*/, std::false_type /*lazy*/) const
{
return token_string;
}
/// return the last read token (for errors only). Will never contain EOF /// return the last read token (for errors only). Will never contain EOF
/// (an arbitrary value that is not a valid char value, often -1), because /// (an arbitrary value that is not a valid char value, often -1), because
/// 255 may legitimately occur. May contain NUL, which should be escaped. /// 255 may legitimately occur. May contain NUL, which should be escaped.
std::string get_token_string() const std::string get_token_string() const
{ {
std::vector<char_type> reconstructed;
const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {});
// escape control characters // escape control characters
std::string result; std::string result;
for (const auto c : token_string) for (const auto c : chars)
{ {
if (static_cast<unsigned char>(c) <= '\x1F') if (static_cast<unsigned char>(c) <= '\x1F')
{ {
@@ -9071,9 +9186,14 @@ scan_number_done:
/// the start position of the current token /// the start position of the current token
position_t position {}; position_t position {};
/// raw input token string (for error messages) /// raw input token string for error messages; only populated for streaming
/// adapters (seekable adapters reconstruct it lazily via token_string_start)
std::vector<char_type> token_string {}; std::vector<char_type> token_string {};
/// start offset of the current token within the input, used to reconstruct
/// the last read token on error for seekable adapters (see collect_token_chars)
std::size_t token_string_start = 0;
/// buffer for variable-length tokens (numbers, strings) /// buffer for variable-length tokens (numbers, strings)
string_t token_buffer {}; string_t token_buffer {};
+113
View File
@@ -16,6 +16,12 @@ using nlohmann::json;
#endif #endif
#include <valarray> #include <valarray>
#include <algorithm>
#include <list>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
namespace namespace
{ {
@@ -1725,3 +1731,110 @@ TEST_CASE("parser class")
CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error); CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
} }
} }
// this test relies on parse errors being thrown, so it is skipped when
// exceptions are disabled (json::parse aborts instead of throwing there)
#if !defined(JSON_NOEXCEPTION)
namespace
{
// Return the exception message from parsing @a input, or a "<no error ...>"
// sentinel if the parse unexpectedly succeeds. json::parse is nodiscard, so the
// result is consumed (via size()) to keep -Wunused-result / -Werror happy.
template<typename InputType>
std::string parse_error_message(InputType&& input)
{
try
{
const json j = json::parse(std::forward<InputType>(input));
return "<no error, size " + std::to_string(j.size()) + ">";
}
catch (const json::exception& e)
{
return e.what();
}
}
template<typename IteratorType>
std::string parse_error_message_range(IteratorType first, IteratorType last)
{
try
{
const json j = json::parse(first, last);
return "<no error, size " + std::to_string(j.size()) + ">";
}
catch (const json::exception& e)
{
return e.what();
}
}
} // namespace
TEST_CASE("last-read diagnostics are identical across input adapters")
{
// The lexer reconstructs the "last read" token lazily for seekable adapters
// (contiguous byte input) and copies it eagerly for streaming adapters.
// Both strategies must yield byte-for-byte identical error messages.
// a selection of malformed inputs that exercise different token kinds,
// whitespace/structural accumulation, number overflow, and control-char
// escaping in the reconstructed "last read" token
const std::vector<std::string> inputs =
{
"[1,2,x]",
" \n @",
"{\"a\": }",
"1.18973e+4932",
"\"\t\"",
"tru",
"[1 2]",
"\xEF\xBB\xBF nul",
};
for (const auto& s : inputs)
{
CAPTURE(s);
// reference: contiguous std::string -> seekable (lazy) path
const std::string reference = parse_error_message(s);
// every input is malformed, so parsing must fail (error messages start
// with '['; the success sentinel returned above starts with '<')
CHECK(reference.front() == '[');
// const char* -> also seekable
CHECK(parse_error_message(s.c_str()) == reference);
// std::vector<char> iterators -> seekable (random-access)
{
const std::vector<char> v(s.begin(), s.end());
CHECK(parse_error_message_range(v.begin(), v.end()) == reference);
}
// std::list iterators -> non-seekable (bidirectional) eager path
{
const std::list<char> l(s.begin(), s.end());
CHECK(parse_error_message_range(l.begin(), l.end()) == reference);
}
// std::istringstream -> non-seekable streaming eager path
{
std::istringstream ss(s);
CHECK(parse_error_message(ss) == reference);
}
// wide strings -> wide_string_input_adapter eager path; only comparable
// for ASCII input, as non-ASCII bytes are transcoded to different UTF-8
const bool is_ascii = std::all_of(s.begin(), s.end(), [](char c)
{
return static_cast<unsigned char>(c) < 0x80;
});
if (is_ascii)
{
const std::u16string w16(s.begin(), s.end());
CHECK(parse_error_message(w16) == reference);
const std::u32string w32(s.begin(), s.end());
CHECK(parse_error_message(w32) == reference);
}
}
}
#endif // !defined(JSON_NOEXCEPTION)