mirror of
https://github.com/nlohmann/json.git
synced 2026-09-01 14:07:14 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d18792bd | ||
|
|
8557b661ab | ||
|
|
48face7a20 | ||
|
|
68c87ad9de | ||
|
|
fe0299545a | ||
|
|
366f3d26e5 | ||
|
|
7c9208bfb3 | ||
|
|
bb60941f0e | ||
|
|
3b0dd69928 | ||
|
|
c05c5e229b | ||
|
|
acf076a677 | ||
|
|
33edc9751c | ||
|
|
83c87cb9e0 | ||
|
|
eed1587000 | ||
|
|
c034480c22 | ||
|
|
899cf31255 | ||
|
|
c363dc3e4d |
@@ -4,28 +4,44 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
codeql-action:
|
||||
patterns:
|
||||
- "github/codeql-action/*"
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /docs/mkdocs
|
||||
schedule:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /tools/astyle
|
||||
schedule:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /tools/generate_natvis
|
||||
schedule:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /tools/serve_header
|
||||
schedule:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /cmake/requirements
|
||||
schedule:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
with:
|
||||
languages: c-cpp
|
||||
|
||||
@@ -48,4 +48,4 @@ jobs:
|
||||
uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -47,7 +47,10 @@ jobs:
|
||||
|
||||
var hasPatch = artifacts.data.artifacts.some((artifact) => artifact.name == "amalgamation-patch");
|
||||
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'
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
@@ -55,8 +58,19 @@ jobs:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
const author = fs.readFileSync('./author')
|
||||
const issue_number = Number(fs.readFileSync('./number'));
|
||||
// Both values come from a fork-triggered workflow and are therefore
|
||||
// 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({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -41,12 +41,23 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Scan code using project's configuration on https://semgrep.dev/manage
|
||||
- uses: returntocorp/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d
|
||||
with:
|
||||
publishToken: ${{ secrets.SEMGREP_APP_TOKEN }}
|
||||
publishDeployment: ${{ secrets.SEMGREP_DEPLOYMENT_ID }}
|
||||
generateSarif: "1"
|
||||
# The former returntocorp/semgrep-action is deprecated (the org was renamed
|
||||
# to semgrep/*); the maintained approach is to install the CLI and invoke
|
||||
# it directly. We use `semgrep scan` (not `semgrep ci`, which requires a
|
||||
# login token): with no SEMGREP_APP_TOKEN configured this is exactly what
|
||||
# the old action fell back to, running community rules with no token.
|
||||
# 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==1.168.0
|
||||
|
||||
# `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
|
||||
- name: Upload SARIF file
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -316,7 +316,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -339,7 +339,7 @@ jobs:
|
||||
target: [ci_test_examples, ci_test_build_documentation]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ Files: include/nlohmann/thirdparty/hedley.hpp
|
||||
Copyright: 2016-2021 Evan Nemerson <evan@nemerson.com>
|
||||
License: CC0
|
||||
|
||||
Files: include/nlohmann/detail/meta/cpp_future.hpp
|
||||
Copyright: 2013-2026 Niels Lohmann <https://nlohmann.me> and 2018 The Abseil Authors
|
||||
License: MIT AND Apache-2.0
|
||||
|
||||
Files: tests/thirdparty/doctest/*
|
||||
Copyright: 2016-2023 Viktor Kirilov
|
||||
License: MIT
|
||||
|
||||
@@ -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
|
||||
- [`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
|
||||
|
||||
|
||||
@@ -82,7 +82,13 @@ basic_json(basic_json&& other) noexcept;
|
||||
4. This is a constructor for existing `basic_json` types. It does not hijack copy/move constructors, since the parameter
|
||||
has different template arguments than the current ones.
|
||||
|
||||
The constructor tries to convert the internal `m_value` of the parameter.
|
||||
The constructor tries to convert the internal `m_value` of the parameter. Each member value (object, array, string,
|
||||
etc.) is serialized via the corresponding `to_json()` overload. For objects and strings, the conversion requires
|
||||
that the *target* `basic_json` type's `object_t::key_type` (or `string_t`) be directly constructible from the
|
||||
*source* type's corresponding member type via `is_constructible`. If this requirement is not met, the conversion
|
||||
does not fail to compile; instead, it silently falls back to the array-conversion path, which represents objects
|
||||
as arrays of `[key, value]` pairs and strings as arrays of character codes. This is a known limitation tracked in
|
||||
[issue #3425](https://github.com/nlohmann/json/issues/3425).
|
||||
|
||||
5. Creates a JSON value of type array or object from the passed initializer list `init`. In case `type_deduction` is
|
||||
`#!cpp true` (default), the type of the JSON value to be created is deducted from the initializer list `init`
|
||||
@@ -146,6 +152,11 @@ basic_json(basic_json&& other) noexcept;
|
||||
|
||||
- `BasicJsonType` is a `basic_json` type.
|
||||
- `BasicJsonType` has different template arguments than `basic_json_t`.
|
||||
|
||||
**Note:** For cross-`basic_json` conversions to produce correct results, the target `basic_json`'s
|
||||
`object_t::key_type` and `string_t` must be directly constructible from the source `basic_json`'s
|
||||
corresponding types. See the description of overload (4) above for details on what happens when
|
||||
this requirement is not met.
|
||||
|
||||
`U`:
|
||||
: `uncvref_t<CompatibleType>`
|
||||
|
||||
@@ -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
|
||||
- [operator<<](../operator_ltlt.md) serialize to stream
|
||||
- [Serialization](../../features/serialization.md) - the serialization article
|
||||
|
||||
## 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
|
||||
- [insert](insert.md) add values to an array/object
|
||||
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ Amortized constant.
|
||||
|
||||
- [operator+=](operator+=.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
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
|
||||
- [clear](clear.md) clears the contents
|
||||
- [insert](insert.md) add values to an array/object
|
||||
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ std::string format_as(const BasicJsonType& j)
|
||||
|
||||
- [dump](dump.md)
|
||||
- [std::formatter<basic_json>](std_formatter.md) - the `std::format` (C++20) equivalent
|
||||
- [Serialization](../../features/serialization.md) - the serialization article
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@ overload (3).
|
||||
- [get_ptr](get_ptr.md) get a pointer 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
|
||||
- [Converting values](../../features/conversions.md) - the type conversions article
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ Depends on the `json_serializer<ValueType>::from_json()` implementation.
|
||||
- [get](get.md) get a value (explicit conversion)
|
||||
- [get_ref](get_ref.md) get a reference 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
|
||||
|
||||
|
||||
@@ -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
|
||||
- [`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
|
||||
|
||||
|
||||
@@ -93,6 +93,15 @@ alphabetical order as `std::map` with `std::less` is used by default. Please not
|
||||
[RFC 8259](https://tools.ietf.org/html/rfc8259), because any order implements the specified "unordered" nature of JSON
|
||||
objects.
|
||||
|
||||
#### Cross-`basic_json` conversion requirements
|
||||
|
||||
When converting an object from one `basic_json` specialization to another via the
|
||||
[converting constructor](basic_json.md#overload-4), the target `object_t`'s `key_type` must be
|
||||
directly constructible from the source `basic_json`'s `string_t` type (or more generally, from the
|
||||
source object's key type). If this requirement is not met, the conversion does not fail; instead,
|
||||
the object is silently converted as an array of key-value pairs, which is incorrect. See
|
||||
[issue #3425](https://github.com/nlohmann/json/issues/3425) for details and an example.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
@@ -78,6 +78,7 @@ Linear in the size of the JSON value.
|
||||
## See also
|
||||
|
||||
- [get](get.md) get a value (explicit conversion)
|
||||
- [Converting values](../../features/conversions.md) - the type conversions article
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ invalidates all iterators and all references.
|
||||
|
||||
- [emplace_back](emplace_back.md) add a value to an array
|
||||
- [operator+=](operator+=.md) add a value to an array/object
|
||||
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ provides `<format>`, controlled by the [`JSON_HAS_STD_FORMAT`](../macros/json_ha
|
||||
- [dump](dump.md) - serialization
|
||||
- [operator<<(std::ostream&)](../operator_ltlt.md) - serialize to stream
|
||||
- [format_as](format_as.md) - customization point used by `fmt::format` (fmtlib)
|
||||
- [Serialization](../../features/serialization.md) - the serialization article
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -6,9 +6,18 @@ namespace std {
|
||||
}
|
||||
```
|
||||
|
||||
Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. Furthermore, the
|
||||
type of the JSON value is taken into account to have different hash values for `#!json null`, `#!cpp 0`, `#!cpp 0U`, and
|
||||
`#!cpp false`, etc.
|
||||
Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. To satisfy the
|
||||
`std::hash` contract, numeric JSON values that compare equal must hash to the same value. This means:
|
||||
|
||||
- `json(42)`, `json(42u)`, and `json(42.0)` all hash to the same value
|
||||
- `json(0)`, `json(0u)`, and `json(0.0)` all hash to the same value
|
||||
|
||||
Different types hash differently for non-numeric types (e.g., `#!json null`, `#!cpp false`, and strings all have distinct hashes).
|
||||
|
||||
**Edge case:** For very large integers outside the exact representable range of the floating-point type (beyond ~2^53 for
|
||||
typical `double`), the hash values for integer and floating-point values may differ, even if the floating-point value
|
||||
was obtained by casting the integer (due to precision loss). This is a documented limitation arising from how the
|
||||
comparison operator normalizes numeric types.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -45,6 +45,15 @@ This implementation is interoperable as it does compare strings code unit by cod
|
||||
String values are stored as pointers in a `basic_json` type. That is, for any access to string values, a pointer of type
|
||||
`string_t*` must be dereferenced.
|
||||
|
||||
#### Cross-`basic_json` conversion requirements
|
||||
|
||||
When converting a string value from one `basic_json` specialization to another via the
|
||||
[converting constructor](basic_json.md#overload-4), the target `string_t` must be directly
|
||||
constructible from the source `basic_json`'s `string_t` type. If this requirement is not met, the
|
||||
conversion does not fail; instead, the string is silently converted as an array of character codes,
|
||||
which is incorrect. See [issue #3425](https://github.com/nlohmann/json/issues/3425) for details
|
||||
and an example.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
@@ -59,6 +59,7 @@ std::string to_string(const BasicJsonType& j)
|
||||
## See also
|
||||
|
||||
- [dump](dump.md)
|
||||
- [Serialization](../../features/serialization.md) - the serialization article
|
||||
|
||||
## 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
|
||||
- [merge_patch](merge_patch.md) applies a JSON Merge Patch
|
||||
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -56,6 +56,10 @@ Linear.
|
||||
--8<-- "examples/operator_literal_json.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [Creating JSON values](../features/creating_values.md) - the article on creating JSON values
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
|
||||
@@ -80,6 +80,12 @@ Linear.
|
||||
```json
|
||||
--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
|
||||
|
||||
1. Added in version 1.0.0. Added support for indentation character and deprecated
|
||||
|
||||
@@ -11,6 +11,7 @@ int main()
|
||||
<< "hash(false) = " << std::hash<json> {}(json(false)) << '\n'
|
||||
<< "hash(0) = " << std::hash<json> {}(json(0)) << '\n'
|
||||
<< "hash(0U) = " << std::hash<json> {}(json(0U)) << '\n'
|
||||
<< "hash(0.0) = " << std::hash<json> {}(json(0.0)) << '\n'
|
||||
<< "hash(\"\") = " << std::hash<json> {}(json("")) << '\n'
|
||||
<< "hash({}) = " << std::hash<json> {}(json::object()) << '\n'
|
||||
<< "hash([]) = " << std::hash<json> {}(json::array()) << '\n'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
hash(null) = 2654435769
|
||||
hash(false) = 2654436030
|
||||
hash(0) = 2654436095
|
||||
hash(0U) = 2654436156
|
||||
hash("") = 6142509191626859748
|
||||
hash(0) = 2654436221
|
||||
hash(0U) = 2654436221
|
||||
hash(0.0) = 2654436221
|
||||
hash("") = 11160318156688833227
|
||||
hash({}) = 2654435832
|
||||
hash([]) = 2654435899
|
||||
hash({"hello": "world"}) = 4469488738203676328
|
||||
hash({"hello": "world"}) = 3701319991624763853
|
||||
|
||||
@@ -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.
|
||||
|
||||
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`.
|
||||
- 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
|
||||
namespace ns {
|
||||
struct person_derived : person {
|
||||
std:string email;
|
||||
std::string email;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(person_derived, person, email)
|
||||
|
||||
@@ -11,7 +11,7 @@ Runtime assertions can be switched off by defining the preprocessor macro `NDEBU
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
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"
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
| BSON type | BSON marker byte | JSON value type |
|
||||
|-----------------------|------------------|-----------------|
|
||||
| double | 0x01 | number_float |
|
||||
| string | 0x02 | string |
|
||||
| document | 0x03 | object |
|
||||
| array | 0x04 | array |
|
||||
| binary | 0x05 | binary |
|
||||
| undefined | 0x06 | *unsupported* |
|
||||
| ObjectId | 0x07 | *unsupported* |
|
||||
| boolean | 0x08 | boolean |
|
||||
| UTC Date-Time | 0x09 | *unsupported* |
|
||||
| null | 0x0A | null |
|
||||
| Regular Expr. | 0x0B | *unsupported* |
|
||||
| DB Pointer | 0x0C | *unsupported* |
|
||||
| JavaScript Code | 0x0D | *unsupported* |
|
||||
| Symbol | 0x0E | *unsupported* |
|
||||
| JavaScript Code | 0x0F | *unsupported* |
|
||||
| int32 | 0x10 | number_integer |
|
||||
| uint64(Timestamp) | 0x11 | number_unsigned |
|
||||
| 128-bit decimal float | 0x13 | *unsupported* |
|
||||
| Max Key | 0x7F | *unsupported* |
|
||||
| Min Key | 0xFF | *unsupported* |
|
||||
| BSON type | BSON marker byte | JSON value type |
|
||||
|--------------------------|------------------|-----------------|
|
||||
| double | 0x01 | number_float |
|
||||
| string | 0x02 | string |
|
||||
| document | 0x03 | object |
|
||||
| array | 0x04 | array |
|
||||
| binary | 0x05 | binary |
|
||||
| undefined | 0x06 | *unsupported* |
|
||||
| ObjectId | 0x07 | *unsupported* |
|
||||
| boolean | 0x08 | boolean |
|
||||
| UTC Date-Time | 0x09 | *unsupported* |
|
||||
| null | 0x0A | null |
|
||||
| Regular Expr. | 0x0B | *unsupported* |
|
||||
| DB Pointer | 0x0C | *unsupported* |
|
||||
| JavaScript Code | 0x0D | *unsupported* |
|
||||
| Symbol | 0x0E | *unsupported* |
|
||||
| JavaScript Code w/ scope | 0x0F | *unsupported* |
|
||||
| int32 | 0x10 | number_integer |
|
||||
| uint64(Timestamp) | 0x11 | number_unsigned |
|
||||
| int64 | 0x12 | number_integer |
|
||||
| 128-bit decimal float | 0x13 | *unsupported* |
|
||||
| Max Key | 0x7F | *unsupported* |
|
||||
| Min Key | 0xFF | *unsupported* |
|
||||
|
||||
!!! warning "Incomplete mapping"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ extremely small code sizes, fairly small message size, and extensibility without
|
||||
|
||||
!!! 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
|
||||
- [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 NOT representable by a float* | Double-Precision Float | 0xFB |
|
||||
| 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*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A |
|
||||
| string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B |
|
||||
| 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*: 65536..4294967295 | array (4 bytes follow) | 0x9A |
|
||||
| array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B |
|
||||
| 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*: 65536..4294967295 | map (4 bytes follow) | 0xBA |
|
||||
| object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB |
|
||||
| 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*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A |
|
||||
| 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 (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 % |
|
||||
| CBOR | 50.5 % | 86.3 % | 68.4 % | 88.0 % |
|
||||
| MessagePack | 50.5 % | 86.0 % | 68.5 % | 87.9 % |
|
||||
|
||||
@@ -41,7 +41,7 @@ binary.has_subtype(); // returns false
|
||||
binary_with_subtype.has_subtype(); // returns true
|
||||
|
||||
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.set_subtype(23);
|
||||
@@ -146,7 +146,7 @@ as an array of uint8 values. The library implements this translation.
|
||||
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
|
||||
0x7B // '{'
|
||||
@@ -158,10 +158,10 @@ as an array of uint8 values. The library implements this translation.
|
||||
0x7D // '}'
|
||||
```
|
||||
|
||||
The following code uses the type and size optimization for UBJSON:
|
||||
The following code uses the type and size optimization for BJData:
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
@@ -178,7 +178,7 @@ as an array of uint8 values. The library implements this translation.
|
||||
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:
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
`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
|
||||
0x16 0x00 0x00 0x00 // number of bytes in the document
|
||||
@@ -247,7 +247,7 @@ byte array.
|
||||
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
|
||||
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);
|
||||
```
|
||||
|
||||
`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
|
||||
0x81 // fixmap1
|
||||
@@ -331,7 +331,7 @@ as an array of uint8 values. The library implements this translation.
|
||||
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
|
||||
0x7B // '{'
|
||||
|
||||
@@ -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(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"
|
||||
|
||||
@@ -62,7 +62,7 @@ non-existing, an exception is thrown.
|
||||
[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
|
||||
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
|
||||
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,
|
||||
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`.
|
||||
|
||||
To address this issue, either provide a correctly typed default value or use the template parameter to specify the
|
||||
|
||||
@@ -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).
|
||||
@@ -130,7 +130,7 @@ for (auto& [key, val] : j_object.items())
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
```cüü
|
||||
```cpp
|
||||
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
|
||||
@@ -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
|
||||
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.
|
||||
|
||||
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
|
||||
behavior. You have been warned!
|
||||
|
||||
## 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**
|
||||
|
||||
@@ -37,7 +37,7 @@ The default type `nlohmann::json` uses a `std::map` to store JSON objects, and t
|
||||
|
||||
## 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
|
||||
|
||||
@@ -103,7 +103,7 @@ Assume file `input.json` contains the JSON object above:
|
||||
```json
|
||||
{
|
||||
"one": 1,
|
||||
"three": 3
|
||||
"two": 2,
|
||||
"three": 3,
|
||||
"two": 2
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,13 +1,61 @@
|
||||
# Parsing
|
||||
|
||||
!!! note
|
||||
|
||||
This page is under construction.
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -137,7 +137,7 @@ The choice of `object_t` influences the behavior of the JSON class. With the def
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ This is the same behavior as the code `#!c double x = 3.141592653589793238462643
|
||||
|
||||
!!! 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]$.
|
||||
- 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.
|
||||
@@ -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.
|
||||
|
||||
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.
|
||||
|
||||
!!! example
|
||||
@@ -182,7 +182,7 @@ This library serializes NaN values as `#!js null`. This corresponds to the beha
|
||||
|
||||
### 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.
|
||||
|
||||
!!! example "Alternative comparison functions"
|
||||
@@ -203,8 +203,8 @@ Floating-point inside JSON values numbers are compared with `#!c json::number_fl
|
||||
```cpp
|
||||
bool my_equal(const_reference lhs, const_reference rhs)
|
||||
{
|
||||
const auto lhs_type lhs.type();
|
||||
const auto rhs_type rhs.type();
|
||||
const auto lhs_type = lhs.type();
|
||||
const auto rhs_type = rhs.type();
|
||||
if (lhs_type == rhs_type)
|
||||
{
|
||||
switch(lhs_type)
|
||||
@@ -230,7 +230,7 @@ Floating-point inside JSON values numbers are compared with `#!c json::number_fl
|
||||
### Number conversion
|
||||
|
||||
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"
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://json.nlohmann.me/sitemap.xml
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Copy each documentation page's Markdown source into the built site."""
|
||||
|
||||
# Creates a `<path>.md` sibling of each HTML output (for example,
|
||||
# `features/comments/` becomes `features/comments.md`) so agents and tools can
|
||||
# fetch the raw Markdown directly instead of parsing rendered HTML.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
_pages = []
|
||||
|
||||
|
||||
def on_files(files, config):
|
||||
global _pages
|
||||
_pages = [f for f in files if f.is_documentation_page()]
|
||||
return files
|
||||
|
||||
|
||||
def on_post_build(config):
|
||||
site_dir = config["site_dir"]
|
||||
for file in _pages:
|
||||
url = file.url.rstrip("/")
|
||||
target = os.path.join(site_dir, (url or "index") + ".md")
|
||||
os.makedirs(os.path.dirname(target), exist_ok=True)
|
||||
shutil.copyfile(file.abs_src_path, target)
|
||||
+38
-3
@@ -57,6 +57,7 @@ nav:
|
||||
- home/customers.md
|
||||
- home/sponsors.md
|
||||
- Features:
|
||||
- features/index.md
|
||||
- features/arbitrary_types.md
|
||||
- Binary Formats:
|
||||
- features/binary_formats/index.md
|
||||
@@ -67,16 +68,18 @@ nav:
|
||||
- features/binary_formats/ubjson.md
|
||||
- features/binary_values.md
|
||||
- features/comments.md
|
||||
- features/trailing_commas.md
|
||||
- features/conversions.md
|
||||
- features/creating_values.md
|
||||
- Element Access:
|
||||
- features/element_access/index.md
|
||||
- features/element_access/unchecked_access.md
|
||||
- features/element_access/checked_access.md
|
||||
- features/element_access/default_value.md
|
||||
- features/iterators.md
|
||||
- features/json_pointer.md
|
||||
- features/json_patch.md
|
||||
- features/merge_patch.md
|
||||
- features/json_patch.md
|
||||
- features/json_pointer.md
|
||||
- features/modifying_values.md
|
||||
- features/modules.md
|
||||
- 'nlohmann Namespace': features/namespace.md
|
||||
- features/object_order.md
|
||||
@@ -87,8 +90,10 @@ nav:
|
||||
- features/parsing/parser_callbacks.md
|
||||
- features/parsing/sax_interface.md
|
||||
- features/assertions.md
|
||||
- features/serialization.md
|
||||
- features/enum_conversion.md
|
||||
- features/macros.md
|
||||
- features/trailing_commas.md
|
||||
- Types:
|
||||
- features/types/index.md
|
||||
- features/types/number_handling.md
|
||||
@@ -362,6 +367,9 @@ markdown_extensions:
|
||||
auto_append:
|
||||
- ../includes/glossary.md
|
||||
|
||||
hooks:
|
||||
- hooks/copy_markdown_source.py
|
||||
|
||||
plugins:
|
||||
- search:
|
||||
separator: '[\s\-\.]'
|
||||
@@ -384,6 +392,33 @@ plugins:
|
||||
- https://nlohmann.github.io/json/*
|
||||
- mailto:*
|
||||
- privacy
|
||||
- llmstxt:
|
||||
markdown_description: >
|
||||
JSON for Modern C++ is a C++11 header-only library implementing a JSON
|
||||
value type with an STL-like API, JSON Pointer/Patch, CBOR/MessagePack/
|
||||
BSON/UBJSON/BJData binary format support, and a SAX-style parser interface.
|
||||
sections:
|
||||
Home:
|
||||
- index.md
|
||||
- home/*.md
|
||||
Features:
|
||||
- features/*.md
|
||||
- features/binary_formats/*.md
|
||||
- features/element_access/*.md
|
||||
- features/parsing/*.md
|
||||
- features/types/*.md
|
||||
Integration:
|
||||
- integration/*.md
|
||||
API Documentation:
|
||||
- api/*.md
|
||||
- api/basic_json/*.md
|
||||
- api/adl_serializer/*.md
|
||||
- api/byte_container_with_subtype/*.md
|
||||
- api/json_pointer/*.md
|
||||
- api/json_sax/*.md
|
||||
- api/macros/*.md
|
||||
Community:
|
||||
- community/*.md
|
||||
|
||||
extra_css:
|
||||
- css/custom.css
|
||||
|
||||
@@ -7,5 +7,6 @@ mkdocs-material-extensions==1.3.1 # extensions
|
||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||
mkdocs-redirects==1.2.3 # plugin "redirects"
|
||||
mkdocs-htmlproofer-plugin==1.5.0 # plugin "htmlproofer"
|
||||
mkdocs-llmstxt==0.5.0 # plugin "llmstxt"
|
||||
|
||||
PyYAML==6.0.3 # linter
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <functional> // hash
|
||||
#include <limits> // numeric_limits
|
||||
#include <cmath> // isfinite
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
@@ -26,12 +28,73 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Check if a number_integer_t value is exactly representable as number_float_t
|
||||
// Returns true if static_cast<number_integer_t>(static_cast<number_float_t>(val)) == val
|
||||
template<typename BasicJsonType>
|
||||
inline bool is_exactly_representable_as_float(typename BasicJsonType::number_integer_t val) noexcept
|
||||
{
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
|
||||
// If the float type's mantissa covers the integer type's entire range, all values round-trip
|
||||
constexpr int float_digits = std::numeric_limits<number_float_t>::digits;
|
||||
constexpr int int_digits = std::numeric_limits<number_integer_t>::digits;
|
||||
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
#pragma warning(push )
|
||||
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
||||
#endif
|
||||
if (float_digits >= int_digits)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
|
||||
// For values outside float's exact range, they don't round-trip
|
||||
// The safe way to check: compute the max magnitude that round-trips
|
||||
// Using unsigned arithmetic to avoid UB with negating INT_MIN
|
||||
|
||||
// Max magnitude representable exactly: 2^(digits-1) - 1 for signed, 2^digits - 1 for unsigned range
|
||||
// But we're checking a signed value, so use 2^digits as the threshold
|
||||
constexpr auto max_exact = static_cast<number_integer_t>(1) << (float_digits - 1);
|
||||
|
||||
// Check absolute value against this threshold
|
||||
if (val >= 0)
|
||||
{
|
||||
if (val >= max_exact)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For negative values, check via unsigned wrapping arithmetic
|
||||
// -val in unsigned domain; if it wraps, the value is too negative
|
||||
auto unsigned_abs = static_cast<typename BasicJsonType::number_unsigned_t>(-val);
|
||||
if (unsigned_abs >= static_cast<typename BasicJsonType::number_unsigned_t>(max_exact))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For values within the exact range, verify the round-trip
|
||||
const auto f = static_cast<number_float_t>(val);
|
||||
return std::isfinite(f) && static_cast<number_integer_t>(f) == val;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief hash a JSON value
|
||||
|
||||
The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||
type of the JSON value is taken into account to have different hash values for
|
||||
null, 0, 0U, and false, etc.
|
||||
most types. However, numeric types (number_integer, number_unsigned, number_float)
|
||||
are hashed to satisfy the std::hash contract: if two json values compare equal,
|
||||
they must have equal hash values. This means json(42), json(42u), and json(42.0)
|
||||
all hash to the same value (since they compare equal). For large integer values
|
||||
outside the exact representable range of the float type, integer values are hashed
|
||||
in their own domain to avoid precision loss.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j JSON value to hash
|
||||
@@ -90,14 +153,36 @@ std::size_t hash(const BasicJsonType& j)
|
||||
|
||||
case BasicJsonType::value_t::number_integer:
|
||||
{
|
||||
const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
|
||||
return combine(type, h);
|
||||
const auto v = j.template get<number_integer_t>();
|
||||
// Use a shared numeric type tag so all numeric types that are equal hash the same
|
||||
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
||||
|
||||
if (is_exactly_representable_as_float<BasicJsonType>(v))
|
||||
{
|
||||
const auto h = std::hash<number_float_t> {}(static_cast<number_float_t>(v));
|
||||
return combine(numeric_type, h);
|
||||
}
|
||||
|
||||
const auto h = std::hash<number_integer_t> {}(v);
|
||||
return combine(numeric_type, h);
|
||||
}
|
||||
|
||||
case BasicJsonType::value_t::number_unsigned:
|
||||
{
|
||||
const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
|
||||
return combine(type, h);
|
||||
const auto v = j.template get<number_unsigned_t>();
|
||||
// Normalize to signed (matching operator== behavior for U-vs-I comparison)
|
||||
const auto v_as_signed = static_cast<number_integer_t>(v);
|
||||
// Use a shared numeric type tag so all numeric types that are equal hash the same
|
||||
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
||||
|
||||
if (is_exactly_representable_as_float<BasicJsonType>(v_as_signed))
|
||||
{
|
||||
const auto h = std::hash<number_float_t> {}(static_cast<number_float_t>(v_as_signed));
|
||||
return combine(numeric_type, h);
|
||||
}
|
||||
|
||||
const auto h = std::hash<number_integer_t> {}(v_as_signed);
|
||||
return combine(numeric_type, h);
|
||||
}
|
||||
|
||||
case BasicJsonType::value_t::number_float:
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <array> // array
|
||||
#include <cmath> // ldexp
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <cstring> // memcpy
|
||||
#include <iterator> // back_inserter
|
||||
@@ -2908,18 +2908,7 @@ class binary_reader
|
||||
const NumberType len,
|
||||
string_t& result)
|
||||
{
|
||||
bool success = true;
|
||||
for (NumberType i = 0; i < len; i++)
|
||||
{
|
||||
get();
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string")))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
result.push_back(static_cast<typename string_t::value_type>(current));
|
||||
}
|
||||
return success;
|
||||
return get_bytes(format, len, "string", result);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -2941,18 +2930,66 @@ class binary_reader
|
||||
const NumberType len,
|
||||
binary_t& result)
|
||||
{
|
||||
bool success = true;
|
||||
for (NumberType i = 0; i < len; i++)
|
||||
return get_bytes(format, len, "binary", result);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read @a len bytes from the input into a string or byte container
|
||||
|
||||
@tparam NumberType the type of the length
|
||||
@tparam ContainerType the destination container (string_t or binary_t)
|
||||
@param[in] format the current format (for diagnostics)
|
||||
@param[in] len number of bytes to read
|
||||
@param[in] context further context information (for diagnostics)
|
||||
@param[out] result container the bytes are appended to
|
||||
|
||||
@return whether reading completed
|
||||
|
||||
@note We cannot reserve @a len bytes for the result up front, because
|
||||
@a len may be far larger than the actual input. Instead we read in
|
||||
bounded chunks, so the peak allocation is capped regardless of the
|
||||
claimed length while the per-byte loop is replaced by block copies
|
||||
(a std::memcpy for contiguous inputs). @ref unexpect_eof() still
|
||||
detects a premature end of input.
|
||||
*/
|
||||
template<typename NumberType, typename ContainerType>
|
||||
bool get_bytes(const input_format_t format,
|
||||
NumberType len,
|
||||
const char* context,
|
||||
ContainerType& result)
|
||||
{
|
||||
// upper bound on the number of bytes read (and allocated) per chunk
|
||||
constexpr std::size_t chunk_size = 4096;
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
get();
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary")))
|
||||
// number of bytes to read this iteration: min(chunk_size, len),
|
||||
// computed without truncating chunk_size to a narrow NumberType
|
||||
const std::size_t wanted = (static_cast<std::uintmax_t>(len) < static_cast<std::uintmax_t>(chunk_size))
|
||||
? static_cast<std::size_t>(len)
|
||||
: chunk_size;
|
||||
const std::size_t old_size = result.size();
|
||||
result.resize(old_size + wanted);
|
||||
// resize() is required to make size() exactly old_size + wanted;
|
||||
// that is the room get_elements() is allowed to write into
|
||||
JSON_ASSERT(result.size() == old_size + wanted);
|
||||
const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted);
|
||||
chars_read += bytes_read;
|
||||
if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
// premature end of input: shrink to what was actually read and
|
||||
// report the failure at the first missing byte (same position
|
||||
// accounting as get_to() for partial number reads)
|
||||
result.resize(old_size + bytes_read);
|
||||
++chars_read;
|
||||
current = char_traits<char_type>::eof();
|
||||
return unexpect_eof(format, context);
|
||||
}
|
||||
result.push_back(static_cast<typename binary_t::value_type>(current));
|
||||
// a full chunk was read; get_elements() never returns more than requested
|
||||
JSON_ASSERT(bytes_read == wanted);
|
||||
len = static_cast<NumberType>(len - static_cast<NumberType>(wanted));
|
||||
}
|
||||
return success;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -161,8 +161,18 @@ class iterator_input_adapter
|
||||
public:
|
||||
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)
|
||||
: 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()
|
||||
@@ -177,9 +187,67 @@ class iterator_input_adapter
|
||||
return char_traits<char_type>::eof();
|
||||
}
|
||||
|
||||
// for general iterators, we cannot really do something better than falling back to processing the range one-by-one
|
||||
// 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
|
||||
// bytes actually read. For contiguous iterators (e.g. pointers) this is a
|
||||
// single std::memcpy; for general iterators we fall back to processing the
|
||||
// range one-by-one.
|
||||
template<class T>
|
||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||
{
|
||||
return get_elements_impl(dest, count, std::integral_constant<bool, iterator_is_contiguous> {});
|
||||
}
|
||||
|
||||
private:
|
||||
// whether IteratorType refers to a contiguous range and therefore supports
|
||||
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
|
||||
// library iterators such as those of std::vector and std::string)
|
||||
static constexpr bool iterator_is_contiguous =
|
||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
||||
std::contiguous_iterator<IteratorType> ||
|
||||
#endif
|
||||
std::is_pointer<IteratorType>::value;
|
||||
|
||||
// contiguous fast path: bulk copy the remaining range with std::memcpy
|
||||
template<class T>
|
||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
|
||||
{
|
||||
const std::size_t wanted = count * sizeof(T);
|
||||
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
|
||||
const std::size_t copied = (std::min)(wanted, available);
|
||||
if (JSON_HEDLEY_LIKELY(copied != 0))
|
||||
{
|
||||
// the copy must stay within both buffers: the caller-provided
|
||||
// destination holds `wanted` bytes and the remaining input range
|
||||
// holds `available` bytes, and `copied` is the minimum of the two
|
||||
JSON_ASSERT(copied <= wanted); // does not overrun the destination
|
||||
JSON_ASSERT(copied <= available); // does not read past the input end
|
||||
// &*current yields the raw address for both raw pointers and
|
||||
// non-pointer contiguous iterators (e.g. std::vector's iterator)
|
||||
std::memcpy(dest, &*current, copied);
|
||||
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(copied / sizeof(char_type)));
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
// general fallback: copy the range one element at a time
|
||||
template<class T>
|
||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::false_type /*contiguous*/)
|
||||
{
|
||||
auto* ptr = reinterpret_cast<char*>(dest);
|
||||
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
|
||||
@@ -197,7 +265,7 @@ class iterator_input_adapter
|
||||
return count * sizeof(T);
|
||||
}
|
||||
|
||||
private:
|
||||
IteratorType begin;
|
||||
IteratorType current;
|
||||
IteratorType end;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -118,6 +140,12 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
using char_type = typename InputAdapterType::char_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:
|
||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||
|
||||
@@ -1327,8 +1355,24 @@ scan_number_done:
|
||||
void reset() noexcept
|
||||
{
|
||||
token_buffer.clear();
|
||||
token_string.clear();
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1357,10 +1401,9 @@ scan_number_done:
|
||||
current = ia.get_character();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
||||
{
|
||||
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
||||
}
|
||||
// seekable adapters reconstruct the token lazily on error (see
|
||||
// get_token_string), so the eager per-character copy is skipped
|
||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
|
||||
if (current == '\n')
|
||||
{
|
||||
@@ -1371,6 +1414,18 @@ scan_number_done:
|
||||
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)
|
||||
|
||||
@@ -1398,6 +1453,15 @@ scan_number_done:
|
||||
--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()))
|
||||
{
|
||||
JSON_ASSERT(!token_string.empty());
|
||||
@@ -1455,14 +1519,38 @@ scan_number_done:
|
||||
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
|
||||
/// (an arbitrary value that is not a valid char value, often -1), because
|
||||
/// 255 may legitimately occur. May contain NUL, which should be escaped.
|
||||
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
|
||||
std::string result;
|
||||
for (const auto c : token_string)
|
||||
for (const auto c : chars)
|
||||
{
|
||||
if (static_cast<unsigned char>(c) <= '\x1F')
|
||||
{
|
||||
@@ -1623,9 +1711,14 @@ scan_number_done:
|
||||
/// the start position of the current token
|
||||
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 {};
|
||||
|
||||
/// 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)
|
||||
string_t token_buffer {};
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief Default base class of the @ref basic_json class.
|
||||
@@ -25,13 +23,27 @@ of @ref basic_json do not require complex case distinctions
|
||||
@ref basic_json always has a base class.
|
||||
By default, this class is used because it is empty and thus has no effect
|
||||
on the behavior of @ref basic_json.
|
||||
|
||||
@note This class intentionally lives in namespace @ref nlohmann rather than
|
||||
@ref nlohmann::detail. Every @ref basic_json specialization derives from
|
||||
it (via @ref detail::json_base_class) unless a custom base class is
|
||||
supplied, which makes its namespace an associated namespace of
|
||||
@ref basic_json for the purpose of argument-dependent lookup (ADL). If
|
||||
it lived in `nlohmann::detail`, that namespace - and with it the
|
||||
library's internal `to_json`/`from_json` overloads - would leak into
|
||||
ADL for any unqualified `to_json`/`from_json` call a user makes
|
||||
involving a @ref basic_json argument, silently shadowing the user's own
|
||||
overloads in some cases.
|
||||
*/
|
||||
struct json_default_base {};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<class T>
|
||||
using json_base_class = typename std::conditional <
|
||||
std::is_same<T, void>::value,
|
||||
json_default_base,
|
||||
::nlohmann::json_default_base,
|
||||
T
|
||||
>::type;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-FileCopyrightText: 2018 The Abseil Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: MIT AND Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -465,18 +465,12 @@ class serializer
|
||||
{
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
|
||||
static_cast<std::uint16_t>(codepoint)));
|
||||
bytes += 6;
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
|
||||
static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),
|
||||
static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));
|
||||
bytes += 12;
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -683,6 +677,32 @@ class serializer
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
|
||||
*
|
||||
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
||||
* lookup table, avoiding the format-string parsing and locale machinery of
|
||||
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
||||
*
|
||||
* @param[in] pos position in @a string_buffer to write at; there must
|
||||
* be at least 6 bytes of headroom
|
||||
* @param[in] codeunit 16-bit value to encode
|
||||
*/
|
||||
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
||||
{
|
||||
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
string_buffer[pos + 0] = '\\';
|
||||
string_buffer[pos + 1] = 'u';
|
||||
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
||||
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
||||
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
||||
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
||||
pos += 6;
|
||||
}
|
||||
|
||||
// templates to avoid warnings about useless casts
|
||||
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
||||
bool is_negative_number(NumberType x)
|
||||
|
||||
@@ -3405,7 +3405,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-FileCopyrightText: 2018 The Abseil Authors
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-License-Identifier: MIT AND Apache-2.0
|
||||
|
||||
|
||||
|
||||
@@ -6677,6 +6677,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <functional> // hash
|
||||
#include <limits> // numeric_limits
|
||||
#include <cmath> // isfinite
|
||||
|
||||
// #include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
@@ -6694,12 +6696,73 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Check if a number_integer_t value is exactly representable as number_float_t
|
||||
// Returns true if static_cast<number_integer_t>(static_cast<number_float_t>(val)) == val
|
||||
template<typename BasicJsonType>
|
||||
inline bool is_exactly_representable_as_float(typename BasicJsonType::number_integer_t val) noexcept
|
||||
{
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
|
||||
// If the float type's mantissa covers the integer type's entire range, all values round-trip
|
||||
constexpr int float_digits = std::numeric_limits<number_float_t>::digits;
|
||||
constexpr int int_digits = std::numeric_limits<number_integer_t>::digits;
|
||||
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
#pragma warning(push )
|
||||
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
||||
#endif
|
||||
if (float_digits >= int_digits)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
|
||||
// For values outside float's exact range, they don't round-trip
|
||||
// The safe way to check: compute the max magnitude that round-trips
|
||||
// Using unsigned arithmetic to avoid UB with negating INT_MIN
|
||||
|
||||
// Max magnitude representable exactly: 2^(digits-1) - 1 for signed, 2^digits - 1 for unsigned range
|
||||
// But we're checking a signed value, so use 2^digits as the threshold
|
||||
constexpr auto max_exact = static_cast<number_integer_t>(1) << (float_digits - 1);
|
||||
|
||||
// Check absolute value against this threshold
|
||||
if (val >= 0)
|
||||
{
|
||||
if (val >= max_exact)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For negative values, check via unsigned wrapping arithmetic
|
||||
// -val in unsigned domain; if it wraps, the value is too negative
|
||||
auto unsigned_abs = static_cast<typename BasicJsonType::number_unsigned_t>(-val);
|
||||
if (unsigned_abs >= static_cast<typename BasicJsonType::number_unsigned_t>(max_exact))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For values within the exact range, verify the round-trip
|
||||
const auto f = static_cast<number_float_t>(val);
|
||||
return std::isfinite(f) && static_cast<number_integer_t>(f) == val;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief hash a JSON value
|
||||
|
||||
The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||
type of the JSON value is taken into account to have different hash values for
|
||||
null, 0, 0U, and false, etc.
|
||||
most types. However, numeric types (number_integer, number_unsigned, number_float)
|
||||
are hashed to satisfy the std::hash contract: if two json values compare equal,
|
||||
they must have equal hash values. This means json(42), json(42u), and json(42.0)
|
||||
all hash to the same value (since they compare equal). For large integer values
|
||||
outside the exact representable range of the float type, integer values are hashed
|
||||
in their own domain to avoid precision loss.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j JSON value to hash
|
||||
@@ -6758,14 +6821,36 @@ std::size_t hash(const BasicJsonType& j)
|
||||
|
||||
case BasicJsonType::value_t::number_integer:
|
||||
{
|
||||
const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
|
||||
return combine(type, h);
|
||||
const auto v = j.template get<number_integer_t>();
|
||||
// Use a shared numeric type tag so all numeric types that are equal hash the same
|
||||
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
||||
|
||||
if (is_exactly_representable_as_float<BasicJsonType>(v))
|
||||
{
|
||||
const auto h = std::hash<number_float_t> {}(static_cast<number_float_t>(v));
|
||||
return combine(numeric_type, h);
|
||||
}
|
||||
|
||||
const auto h = std::hash<number_integer_t> {}(v);
|
||||
return combine(numeric_type, h);
|
||||
}
|
||||
|
||||
case BasicJsonType::value_t::number_unsigned:
|
||||
{
|
||||
const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
|
||||
return combine(type, h);
|
||||
const auto v = j.template get<number_unsigned_t>();
|
||||
// Normalize to signed (matching operator== behavior for U-vs-I comparison)
|
||||
const auto v_as_signed = static_cast<number_integer_t>(v);
|
||||
// Use a shared numeric type tag so all numeric types that are equal hash the same
|
||||
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
||||
|
||||
if (is_exactly_representable_as_float<BasicJsonType>(v_as_signed))
|
||||
{
|
||||
const auto h = std::hash<number_float_t> {}(static_cast<number_float_t>(v_as_signed));
|
||||
return combine(numeric_type, h);
|
||||
}
|
||||
|
||||
const auto h = std::hash<number_integer_t> {}(v_as_signed);
|
||||
return combine(numeric_type, h);
|
||||
}
|
||||
|
||||
case BasicJsonType::value_t::number_float:
|
||||
@@ -6811,7 +6896,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
#include <array> // array
|
||||
#include <cmath> // ldexp
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <cstring> // memcpy
|
||||
#include <iterator> // back_inserter
|
||||
@@ -6993,8 +7078,18 @@ class iterator_input_adapter
|
||||
public:
|
||||
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)
|
||||
: 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()
|
||||
@@ -7009,9 +7104,67 @@ class iterator_input_adapter
|
||||
return char_traits<char_type>::eof();
|
||||
}
|
||||
|
||||
// for general iterators, we cannot really do something better than falling back to processing the range one-by-one
|
||||
// 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
|
||||
// bytes actually read. For contiguous iterators (e.g. pointers) this is a
|
||||
// single std::memcpy; for general iterators we fall back to processing the
|
||||
// range one-by-one.
|
||||
template<class T>
|
||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||
{
|
||||
return get_elements_impl(dest, count, std::integral_constant<bool, iterator_is_contiguous> {});
|
||||
}
|
||||
|
||||
private:
|
||||
// whether IteratorType refers to a contiguous range and therefore supports
|
||||
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
|
||||
// library iterators such as those of std::vector and std::string)
|
||||
static constexpr bool iterator_is_contiguous =
|
||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
||||
std::contiguous_iterator<IteratorType> ||
|
||||
#endif
|
||||
std::is_pointer<IteratorType>::value;
|
||||
|
||||
// contiguous fast path: bulk copy the remaining range with std::memcpy
|
||||
template<class T>
|
||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
|
||||
{
|
||||
const std::size_t wanted = count * sizeof(T);
|
||||
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
|
||||
const std::size_t copied = (std::min)(wanted, available);
|
||||
if (JSON_HEDLEY_LIKELY(copied != 0))
|
||||
{
|
||||
// the copy must stay within both buffers: the caller-provided
|
||||
// destination holds `wanted` bytes and the remaining input range
|
||||
// holds `available` bytes, and `copied` is the minimum of the two
|
||||
JSON_ASSERT(copied <= wanted); // does not overrun the destination
|
||||
JSON_ASSERT(copied <= available); // does not read past the input end
|
||||
// &*current yields the raw address for both raw pointers and
|
||||
// non-pointer contiguous iterators (e.g. std::vector's iterator)
|
||||
std::memcpy(dest, &*current, copied);
|
||||
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(copied / sizeof(char_type)));
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
// general fallback: copy the range one element at a time
|
||||
template<class T>
|
||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::false_type /*contiguous*/)
|
||||
{
|
||||
auto* ptr = reinterpret_cast<char*>(dest);
|
||||
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
|
||||
@@ -7029,7 +7182,7 @@ class iterator_input_adapter
|
||||
return count * sizeof(T);
|
||||
}
|
||||
|
||||
private:
|
||||
IteratorType begin;
|
||||
IteratorType current;
|
||||
IteratorType end;
|
||||
|
||||
@@ -7510,6 +7663,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
|
||||
|
||||
@@ -7525,6 +7700,12 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
using char_type = typename InputAdapterType::char_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:
|
||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||
|
||||
@@ -8734,8 +8915,24 @@ scan_number_done:
|
||||
void reset() noexcept
|
||||
{
|
||||
token_buffer.clear();
|
||||
token_string.clear();
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -8764,10 +8961,9 @@ scan_number_done:
|
||||
current = ia.get_character();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
||||
{
|
||||
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
||||
}
|
||||
// seekable adapters reconstruct the token lazily on error (see
|
||||
// get_token_string), so the eager per-character copy is skipped
|
||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
|
||||
if (current == '\n')
|
||||
{
|
||||
@@ -8778,6 +8974,18 @@ scan_number_done:
|
||||
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)
|
||||
|
||||
@@ -8805,6 +9013,15 @@ scan_number_done:
|
||||
--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()))
|
||||
{
|
||||
JSON_ASSERT(!token_string.empty());
|
||||
@@ -8862,14 +9079,38 @@ scan_number_done:
|
||||
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
|
||||
/// (an arbitrary value that is not a valid char value, often -1), because
|
||||
/// 255 may legitimately occur. May contain NUL, which should be escaped.
|
||||
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
|
||||
std::string result;
|
||||
for (const auto c : token_string)
|
||||
for (const auto c : chars)
|
||||
{
|
||||
if (static_cast<unsigned char>(c) <= '\x1F')
|
||||
{
|
||||
@@ -9030,9 +9271,14 @@ scan_number_done:
|
||||
/// the start position of the current token
|
||||
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 {};
|
||||
|
||||
/// 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)
|
||||
string_t token_buffer {};
|
||||
|
||||
@@ -13073,18 +13319,7 @@ class binary_reader
|
||||
const NumberType len,
|
||||
string_t& result)
|
||||
{
|
||||
bool success = true;
|
||||
for (NumberType i = 0; i < len; i++)
|
||||
{
|
||||
get();
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string")))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
result.push_back(static_cast<typename string_t::value_type>(current));
|
||||
}
|
||||
return success;
|
||||
return get_bytes(format, len, "string", result);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -13106,18 +13341,66 @@ class binary_reader
|
||||
const NumberType len,
|
||||
binary_t& result)
|
||||
{
|
||||
bool success = true;
|
||||
for (NumberType i = 0; i < len; i++)
|
||||
return get_bytes(format, len, "binary", result);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read @a len bytes from the input into a string or byte container
|
||||
|
||||
@tparam NumberType the type of the length
|
||||
@tparam ContainerType the destination container (string_t or binary_t)
|
||||
@param[in] format the current format (for diagnostics)
|
||||
@param[in] len number of bytes to read
|
||||
@param[in] context further context information (for diagnostics)
|
||||
@param[out] result container the bytes are appended to
|
||||
|
||||
@return whether reading completed
|
||||
|
||||
@note We cannot reserve @a len bytes for the result up front, because
|
||||
@a len may be far larger than the actual input. Instead we read in
|
||||
bounded chunks, so the peak allocation is capped regardless of the
|
||||
claimed length while the per-byte loop is replaced by block copies
|
||||
(a std::memcpy for contiguous inputs). @ref unexpect_eof() still
|
||||
detects a premature end of input.
|
||||
*/
|
||||
template<typename NumberType, typename ContainerType>
|
||||
bool get_bytes(const input_format_t format,
|
||||
NumberType len,
|
||||
const char* context,
|
||||
ContainerType& result)
|
||||
{
|
||||
// upper bound on the number of bytes read (and allocated) per chunk
|
||||
constexpr std::size_t chunk_size = 4096;
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
get();
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary")))
|
||||
// number of bytes to read this iteration: min(chunk_size, len),
|
||||
// computed without truncating chunk_size to a narrow NumberType
|
||||
const std::size_t wanted = (static_cast<std::uintmax_t>(len) < static_cast<std::uintmax_t>(chunk_size))
|
||||
? static_cast<std::size_t>(len)
|
||||
: chunk_size;
|
||||
const std::size_t old_size = result.size();
|
||||
result.resize(old_size + wanted);
|
||||
// resize() is required to make size() exactly old_size + wanted;
|
||||
// that is the room get_elements() is allowed to write into
|
||||
JSON_ASSERT(result.size() == old_size + wanted);
|
||||
const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted);
|
||||
chars_read += bytes_read;
|
||||
if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
// premature end of input: shrink to what was actually read and
|
||||
// report the failure at the first missing byte (same position
|
||||
// accounting as get_to() for partial number reads)
|
||||
result.resize(old_size + bytes_read);
|
||||
++chars_read;
|
||||
current = char_traits<char_type>::eof();
|
||||
return unexpect_eof(format, context);
|
||||
}
|
||||
result.push_back(static_cast<typename binary_t::value_type>(current));
|
||||
// a full chunk was read; get_elements() never returns more than requested
|
||||
JSON_ASSERT(bytes_read == wanted);
|
||||
len = static_cast<NumberType>(len - static_cast<NumberType>(wanted));
|
||||
}
|
||||
return success;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -14896,8 +15179,6 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief Default base class of the @ref basic_json class.
|
||||
@@ -14908,13 +15189,27 @@ of @ref basic_json do not require complex case distinctions
|
||||
@ref basic_json always has a base class.
|
||||
By default, this class is used because it is empty and thus has no effect
|
||||
on the behavior of @ref basic_json.
|
||||
|
||||
@note This class intentionally lives in namespace @ref nlohmann rather than
|
||||
@ref nlohmann::detail. Every @ref basic_json specialization derives from
|
||||
it (via @ref detail::json_base_class) unless a custom base class is
|
||||
supplied, which makes its namespace an associated namespace of
|
||||
@ref basic_json for the purpose of argument-dependent lookup (ADL). If
|
||||
it lived in `nlohmann::detail`, that namespace - and with it the
|
||||
library's internal `to_json`/`from_json` overloads - would leak into
|
||||
ADL for any unqualified `to_json`/`from_json` call a user makes
|
||||
involving a @ref basic_json argument, silently shadowing the user's own
|
||||
overloads in some cases.
|
||||
*/
|
||||
struct json_default_base {};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<class T>
|
||||
using json_base_class = typename std::conditional <
|
||||
std::is_same<T, void>::value,
|
||||
json_default_base,
|
||||
::nlohmann::json_default_base,
|
||||
T
|
||||
>::type;
|
||||
|
||||
@@ -19752,18 +20047,12 @@ class serializer
|
||||
{
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
|
||||
static_cast<std::uint16_t>(codepoint)));
|
||||
bytes += 6;
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
|
||||
static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),
|
||||
static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));
|
||||
bytes += 12;
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -19970,6 +20259,32 @@ class serializer
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
|
||||
*
|
||||
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
||||
* lookup table, avoiding the format-string parsing and locale machinery of
|
||||
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
||||
*
|
||||
* @param[in] pos position in @a string_buffer to write at; there must
|
||||
* be at least 6 bytes of headroom
|
||||
* @param[in] codeunit 16-bit value to encode
|
||||
*/
|
||||
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
||||
{
|
||||
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
string_buffer[pos + 0] = '\\';
|
||||
string_buffer[pos + 1] = 'u';
|
||||
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
||||
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
||||
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
||||
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
||||
pos += 6;
|
||||
}
|
||||
|
||||
// templates to avoid warnings about useless casts
|
||||
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
||||
bool is_negative_number(NumberType x)
|
||||
|
||||
@@ -229,7 +229,13 @@ TEST_CASE("algorithms")
|
||||
{
|
||||
json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
|
||||
std::partial_sort(j.begin(), j.begin() + 4, j.end());
|
||||
CHECK(j == json({nullptr, false, true, 3, {{"one", 1}, {"two", 2}}, 29, {1, 2, 3}, "foo", "baz", 13}));
|
||||
// only the first four elements are expected to be sorted, the rest are
|
||||
// unspecified by the standard
|
||||
const json expected({nullptr, false, true, 3});
|
||||
// std::equal below only bounds-checks the first range; assert the
|
||||
// second range is at least as long to rule out an over-read (CWE-126)
|
||||
CHECK(std::distance(begin(expected), end(expected)) >= 4);
|
||||
CHECK(std::equal(j.begin(), j.begin() + 4, begin(expected)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -322,14 +322,12 @@ TEST_CASE("alternative string type")
|
||||
|
||||
SECTION("JSON pointer")
|
||||
{
|
||||
// conversion from json to alt_json fails to compile (see #3425);
|
||||
// attempted fix(*) produces: [[['b','a','r'],['b','a','z']]] (with each char being an integer)
|
||||
// (*) disable implicit conversion for json_refs of any basic_json type
|
||||
// alt_json j = R"(
|
||||
// {
|
||||
// "foo": ["bar", "baz"]
|
||||
// }
|
||||
// )"_json;
|
||||
// Direct conversion from a json literal to alt_json is not supported due to issue #3425:
|
||||
// alt_json's string_t (alt_string) is not directly constructible from std::string, so the
|
||||
// cross-basic_json conversion falls back to the array-conversion path, incorrectly representing
|
||||
// objects as arrays of [key, value] pairs and strings as arrays of character codes.
|
||||
// See https://github.com/nlohmann/json/issues/3425 for details.
|
||||
// Workaround: use alt_json::parse() instead of implicit conversion.
|
||||
auto j = alt_json::parse(R"({"foo": ["bar", "baz"]})");
|
||||
|
||||
CHECK(j.at(alt_json::json_pointer("/foo/0")) == j["foo"][0]);
|
||||
|
||||
@@ -2778,3 +2778,43 @@ TEST_CASE("Tagged values")
|
||||
CHECK(!jb["binary"].get_binary().has_subtype());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CBOR large strings and binaries (chunked reader)")
|
||||
{
|
||||
// The binary reader reads strings and byte arrays in bounded chunks; make
|
||||
// sure roundtripping is correct for lengths around and beyond the internal
|
||||
// chunk size (4096 bytes), for both vector (iterator) and pointer inputs.
|
||||
for (const std::size_t len :
|
||||
{
|
||||
std::size_t{0}, std::size_t{1}, std::size_t{4095}, std::size_t{4096},
|
||||
std::size_t{4097}, std::size_t{8192}, std::size_t{100000}
|
||||
})
|
||||
{
|
||||
CAPTURE(len);
|
||||
|
||||
// text string
|
||||
const json j_string = std::string(len, 'x');
|
||||
const std::vector<std::uint8_t> v_string = json::to_cbor(j_string);
|
||||
CHECK(json::from_cbor(v_string) == j_string);
|
||||
// pointer input exercises the std::memcpy fast path
|
||||
CHECK(json::from_cbor(reinterpret_cast<const char*>(v_string.data()),
|
||||
reinterpret_cast<const char*>(v_string.data()) + v_string.size()) == j_string);
|
||||
|
||||
// byte string
|
||||
const json j_binary = json::binary(std::vector<std::uint8_t>(len, 0xCD));
|
||||
const std::vector<std::uint8_t> v_binary = json::to_cbor(j_binary);
|
||||
CHECK(json::from_cbor(v_binary) == j_binary);
|
||||
CHECK(json::from_cbor(reinterpret_cast<const char*>(v_binary.data()),
|
||||
reinterpret_cast<const char*>(v_binary.data()) + v_binary.size()) == j_binary);
|
||||
|
||||
// a truncated payload must still be reported as an error, never crash
|
||||
// or loop, regardless of the (large) announced length
|
||||
if (len > 16)
|
||||
{
|
||||
std::vector<std::uint8_t> truncated = v_string;
|
||||
truncated.resize(truncated.size() - 8);
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_cbor(truncated), json::parse_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@ using nlohmann::json;
|
||||
#endif
|
||||
|
||||
#include <valarray>
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -168,6 +168,32 @@ TEST_CASE("convenience functions")
|
||||
CHECK_THROWS_WITH_AS(check_escaped("\xC2"), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("string escape with ensure_ascii")
|
||||
{
|
||||
// control characters are escaped regardless of ensure_ascii
|
||||
check_escaped("\x01", "\\u0001", true);
|
||||
check_escaped("\x1f", "\\u001f", true);
|
||||
|
||||
// non-ASCII code points in the Basic Multilingual Plane are emitted as
|
||||
// a single lowercase \uXXXX escape (exercises every nibble position)
|
||||
check_escaped("\xC2\x80", "\\u0080", true); // U+0080
|
||||
check_escaped("\xC3\xBF", "\\u00ff", true); // U+00FF (ÿ)
|
||||
check_escaped("\xDF\xBF", "\\u07ff", true); // U+07FF
|
||||
check_escaped("\xE4\xBD\xA0", "\\u4f60", true); // U+4F60 (你)
|
||||
check_escaped("\xEA\xAF\x8D", "\\uabcd", true); // U+ABCD
|
||||
check_escaped("\xEF\xBF\xBD", "\\ufffd", true); // U+FFFD (replacement char, all-f nibbles)
|
||||
|
||||
// code points outside the BMP are emitted as a UTF-16 surrogate pair
|
||||
// of two lowercase \uXXXX escapes
|
||||
check_escaped("\xF0\x90\x80\x80", "\\ud800\\udc00", true); // U+10000 (lowest astral)
|
||||
check_escaped("\xF0\x9F\x98\x80", "\\ud83d\\ude00", true); // U+1F600 (😀)
|
||||
check_escaped("\xF4\x8F\xBF\xBF", "\\udbff\\udfff", true); // U+10FFFF (highest code point)
|
||||
|
||||
// with ensure_ascii disabled, non-ASCII input is passed through verbatim
|
||||
check_escaped("\xE4\xBD\xA0", "\xE4\xBD\xA0", false);
|
||||
check_escaped("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", false);
|
||||
}
|
||||
|
||||
SECTION("string concat")
|
||||
{
|
||||
using nlohmann::detail::concat;
|
||||
|
||||
+22
-6
@@ -35,10 +35,10 @@ TEST_CASE("hash<nlohmann::json>")
|
||||
|
||||
// number
|
||||
hashes.insert(std::hash<json> {}(json(0)));
|
||||
hashes.insert(std::hash<json> {}(json(static_cast<unsigned>(0))));
|
||||
hashes.insert(std::hash<json> {}(json(static_cast<unsigned>(0)))); // now same hash as json(0)
|
||||
hashes.insert(std::hash<json> {}(json(0.0))); // now same hash as json(0)
|
||||
|
||||
hashes.insert(std::hash<json> {}(json(-1)));
|
||||
hashes.insert(std::hash<json> {}(json(0.0)));
|
||||
hashes.insert(std::hash<json> {}(json(42.23)));
|
||||
|
||||
// array
|
||||
@@ -60,7 +60,16 @@ TEST_CASE("hash<nlohmann::json>")
|
||||
// discarded
|
||||
hashes.insert(std::hash<json> {}(json(json::value_t::discarded)));
|
||||
|
||||
CHECK(hashes.size() == 21);
|
||||
// Note: json(0), json(0U), and json(0.0) now hash to the same value
|
||||
// (to satisfy the std::hash contract: equal values must hash equally)
|
||||
// So we expect 19 distinct hashes instead of 21
|
||||
CHECK(hashes.size() == 19);
|
||||
|
||||
// Verify the std::hash contract: equal values must hash equally
|
||||
CHECK(std::hash<json> {}(json(0)) == std::hash<json> {}(json(static_cast<unsigned>(0))));
|
||||
CHECK(std::hash<json> {}(json(0)) == std::hash<json> {}(json(0.0)));
|
||||
CHECK(std::hash<json> {}(json(42)) == std::hash<json> {}(json(42u)));
|
||||
CHECK(std::hash<json> {}(json(42)) == std::hash<json> {}(json(42.0)));
|
||||
}
|
||||
|
||||
TEST_CASE("hash<nlohmann::ordered_json>")
|
||||
@@ -84,10 +93,10 @@ TEST_CASE("hash<nlohmann::ordered_json>")
|
||||
|
||||
// number
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(0)));
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0)))); // now same hash as ordered_json(0)
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(0.0))); // now same hash as ordered_json(0)
|
||||
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(-1)));
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(0.0)));
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(42.23)));
|
||||
|
||||
// array
|
||||
@@ -109,5 +118,12 @@ TEST_CASE("hash<nlohmann::ordered_json>")
|
||||
// discarded
|
||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(ordered_json::value_t::discarded)));
|
||||
|
||||
CHECK(hashes.size() == 21);
|
||||
// Note: ordered_json(0), ordered_json(0U), and ordered_json(0.0) now hash to the same value
|
||||
CHECK(hashes.size() == 19);
|
||||
|
||||
// Verify the std::hash contract for ordered_json as well
|
||||
CHECK(std::hash<ordered_json> {}(ordered_json(0)) == std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
|
||||
CHECK(std::hash<ordered_json> {}(ordered_json(0)) == std::hash<ordered_json> {}(ordered_json(0.0)));
|
||||
CHECK(std::hash<ordered_json> {}(ordered_json(42)) == std::hash<ordered_json> {}(ordered_json(42u)));
|
||||
CHECK(std::hash<ordered_json> {}(ordered_json(42)) == std::hash<ordered_json> {}(ordered_json(42.0)));
|
||||
}
|
||||
|
||||
@@ -1358,4 +1358,100 @@ TEST_CASE("regression test #5122 - nlohmann::ordered_map move-assignment transfe
|
||||
CHECK(src.begin()->first == "after-move");
|
||||
}
|
||||
|
||||
// Stand-in for a third-party library (e.g., Eigen as of 3.4, which added
|
||||
// STL-compatible begin()/end() to its vector types), living in its own
|
||||
// namespace with its own to_json overload for its vector type.
|
||||
namespace issue_4320_eigen
|
||||
{
|
||||
// "array-compatible" from the library's point of view (it has begin()/end()),
|
||||
// but for which this (fake) third-party namespace provides its own to_json.
|
||||
struct vector3
|
||||
{
|
||||
double v[3]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-use-default-member-init,modernize-use-default-member-init)
|
||||
vector3(double x, double y, double z) : v{x, y, z} {} // NOLINT(hicpp-member-init,cppcoreguidelines-pro-type-member-init)
|
||||
double x() const
|
||||
{
|
||||
return v[0];
|
||||
}
|
||||
double y() const
|
||||
{
|
||||
return v[1];
|
||||
}
|
||||
double z() const
|
||||
{
|
||||
return v[2];
|
||||
}
|
||||
double* begin()
|
||||
{
|
||||
return v;
|
||||
}
|
||||
double* end()
|
||||
{
|
||||
return v + 3;
|
||||
}
|
||||
const double* begin() const
|
||||
{
|
||||
return v;
|
||||
}
|
||||
const double* end() const
|
||||
{
|
||||
return v + 3;
|
||||
}
|
||||
};
|
||||
|
||||
inline void to_json(json& j, const vector3& v) // NOLINT(misc-use-internal-linkage)
|
||||
{
|
||||
j = {{"x", v.x()}, {"y", v.y()}, {"z", v.z()}};
|
||||
}
|
||||
} // namespace issue_4320_eigen
|
||||
|
||||
// The user's own namespace, using the (fake) Eigen type as an implementation
|
||||
// detail behind a payload type that has nothing to do with vectors/arrays.
|
||||
namespace issue_4320
|
||||
{
|
||||
// Publicly derives from issue_4320_eigen::vector3 but does *not* define its
|
||||
// own to_json - it is only ever used as a temporary to reach the base
|
||||
// class's to_json via ADL.
|
||||
struct vector3_wrapper : issue_4320_eigen::vector3
|
||||
{
|
||||
using issue_4320_eigen::vector3::vector3;
|
||||
};
|
||||
|
||||
struct payload
|
||||
{
|
||||
double x, y, z;
|
||||
};
|
||||
|
||||
inline vector3_wrapper to_eigen(const payload& p) // NOLINT(misc-use-internal-linkage)
|
||||
{
|
||||
return {p.x, p.y, p.z};
|
||||
}
|
||||
|
||||
inline void to_json(json& j, const payload& p) // NOLINT(misc-use-internal-linkage)
|
||||
{
|
||||
// Unqualified call, passing a *derived* vector3_wrapper: relies on ADL
|
||||
// finding issue_4320_eigen::to_json(json&, const vector3&) through the
|
||||
// vector3 base class, via a derived-to-base conversion. Must NOT resolve
|
||||
// to the library's own generic array-compatible to_json (an exact-match
|
||||
// template for vector3_wrapper, since it also has begin()/end()), which
|
||||
// would serialize this as [x, y, z] instead of {"x":x, "y":y, "z":z}.
|
||||
to_json(j, to_eigen(p));
|
||||
}
|
||||
} // namespace issue_4320
|
||||
|
||||
TEST_CASE("issue #4320 - custom base class must not leak nlohmann::detail into ADL")
|
||||
{
|
||||
// Before the fix, basic_json unconditionally derived from a type living in
|
||||
// nlohmann::detail (json_default_base), which made nlohmann::detail an
|
||||
// associated namespace of every basic_json for ADL purposes. That leaked
|
||||
// the library's internal generic-array to_json overload into unqualified
|
||||
// to_json() calls made from user code, silently bypassing user-defined
|
||||
// to_json overloads reached via a derived-to-base conversion.
|
||||
const issue_4320::payload p{1.0, 2.0, 3.0};
|
||||
|
||||
json j;
|
||||
to_json(j, p);
|
||||
CHECK(j == json({{"x", 1.0}, {"y", 2.0}, {"z", 3.0}}));
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
|
||||
Reference in New Issue
Block a user