mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 19:50:34 +00:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eede67ca92 | ||
|
|
82b31f31b8 | ||
|
|
798f4c888a | ||
|
|
98258aa3af | ||
|
|
904c8c6710 | ||
|
|
1f0c3be6f3 | ||
|
|
c3b51addbf | ||
|
|
a91f6f489f | ||
|
|
9371f2b6c7 | ||
|
|
9deb721adc | ||
|
|
ecf9df7c45 | ||
|
|
b992e1f76a | ||
|
|
a7fa8d04e8 | ||
|
|
677507137b | ||
|
|
21d90fac1c | ||
|
|
e842f3a68f | ||
|
|
bc56ac6d9a | ||
|
|
bfb2b0cb48 | ||
|
|
8cccae029b | ||
|
|
c7e534d41e | ||
|
|
83d72fd7a7 |
@@ -52,6 +52,7 @@ labels:
|
|||||||
- "single_include/nlohmann/json_view\\.hpp"
|
- "single_include/nlohmann/json_view\\.hpp"
|
||||||
- "tests/src/unit-json_view.*"
|
- "tests/src/unit-json_view.*"
|
||||||
- "tests/src/fuzzer-parse_json_view\\.cpp"
|
- "tests/src/fuzzer-parse_json_view\\.cpp"
|
||||||
|
- "tests/benchmarks/json_view/.*"
|
||||||
- "tools/amalgamate/config_json_view\\.json"
|
- "tools/amalgamate/config_json_view\\.json"
|
||||||
- "docs/mkdocs/docs/features/json_view\\.md"
|
- "docs/mkdocs/docs/features/json_view\\.md"
|
||||||
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
name: "json_view benchmarks"
|
||||||
|
|
||||||
|
# On demand only: runs the comparison of json_view with yyjson, simdjson, and
|
||||||
|
# Boost.JSON (tests/benchmarks/json_view/compare.py) on GitHub-hosted runners,
|
||||||
|
# for numbers from x86-64 and AArch64 Linux. It runs when started by hand, or
|
||||||
|
# when a pull request gets the label "benchmark" (on both architectures, with
|
||||||
|
# GCC and the default settings). Shared runners are noisy: the results show
|
||||||
|
# where json_view stands, but published numbers need a quiet machine (see
|
||||||
|
# tests/benchmarks/json_view/README.md).
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
runner:
|
||||||
|
description: "Runner image"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- ubuntu-24.04
|
||||||
|
- ubuntu-24.04-arm
|
||||||
|
default: ubuntu-24.04
|
||||||
|
compiler:
|
||||||
|
description: "Compiler"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- g++
|
||||||
|
- clang++
|
||||||
|
default: g++
|
||||||
|
native:
|
||||||
|
description: "Compile for the runner's CPU (-march=native)"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
rounds:
|
||||||
|
description: "Rounds of bench_view"
|
||||||
|
type: number
|
||||||
|
default: 30
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compare:
|
||||||
|
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'benchmark'
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
runner: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', inputs.runner) || '["ubuntu-24.04", "ubuntu-24.04-arm"]') }}
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Download test data
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build -DJSON_BuildTests=On
|
||||||
|
cmake --build build --target download_test_data
|
||||||
|
|
||||||
|
- name: Run the comparison
|
||||||
|
env:
|
||||||
|
CXX: ${{ inputs.compiler || 'g++' }}
|
||||||
|
CC: ${{ inputs.compiler == 'clang++' && 'clang' || 'gcc' }}
|
||||||
|
ROUNDS: ${{ inputs.rounds || 30 }}
|
||||||
|
NATIVE: ${{ inputs.native && '--native' || '' }}
|
||||||
|
run: python3 tests/benchmarks/json_view/compare.py --data build/test_files --download --rounds "$ROUNDS" $NATIVE
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
||||||
|
path: tests/benchmarks/json_view/results/
|
||||||
@@ -67,6 +67,7 @@ cc_library(
|
|||||||
"include/nlohmann/detail/string_utils.hpp",
|
"include/nlohmann/detail/string_utils.hpp",
|
||||||
"include/nlohmann/detail/value_t.hpp",
|
"include/nlohmann/detail/value_t.hpp",
|
||||||
"include/nlohmann/detail/view/builder.hpp",
|
"include/nlohmann/detail/view/builder.hpp",
|
||||||
|
"include/nlohmann/detail/view/compare.hpp",
|
||||||
"include/nlohmann/detail/view/document_data.hpp",
|
"include/nlohmann/detail/view/document_data.hpp",
|
||||||
"include/nlohmann/detail/view/errors.hpp",
|
"include/nlohmann/detail/view/errors.hpp",
|
||||||
"include/nlohmann/detail/view/input.hpp",
|
"include/nlohmann/detail/view/input.hpp",
|
||||||
@@ -77,8 +78,11 @@ cc_library(
|
|||||||
"include/nlohmann/detail/view/materialize.hpp",
|
"include/nlohmann/detail/view/materialize.hpp",
|
||||||
"include/nlohmann/detail/view/node.hpp",
|
"include/nlohmann/detail/view/node.hpp",
|
||||||
"include/nlohmann/detail/view/number.hpp",
|
"include/nlohmann/detail/view/number.hpp",
|
||||||
|
"include/nlohmann/detail/view/object_index.hpp",
|
||||||
"include/nlohmann/detail/view/pointer.hpp",
|
"include/nlohmann/detail/view/pointer.hpp",
|
||||||
"include/nlohmann/detail/view/scan.hpp",
|
"include/nlohmann/detail/view/scan.hpp",
|
||||||
|
"include/nlohmann/detail/view/serializer.hpp",
|
||||||
|
"include/nlohmann/detail/view/simd.hpp",
|
||||||
"include/nlohmann/detail/view/string_ref.hpp",
|
"include/nlohmann/detail/view/string_ref.hpp",
|
||||||
"include/nlohmann/detail/view/value.hpp",
|
"include/nlohmann/detail/view/value.hpp",
|
||||||
"include/nlohmann/json.hpp",
|
"include/nlohmann/json.hpp",
|
||||||
|
|||||||
@@ -1405,6 +1405,7 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
|
|||||||
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
|
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
|
||||||
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||||
- The view's parser (`<nlohmann/json_view.hpp>`) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks.
|
- The view's parser (`<nlohmann/json_view.hpp>`) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks.
|
||||||
|
- The view's parser (`<nlohmann/json_view.hpp>`) validates non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||||
|
|
||||||
<img align="right" src="https://git.fsfe.org/reuse/reuse-ci/raw/branch/master/reuse-horizontal.png" alt="REUSE Software">
|
<img align="right" src="https://git.fsfe.org/reuse/reuse-ci/raw/branch/master/reuse-horizontal.png" alt="REUSE Software">
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::cbegin', 'Me
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::cend', 'Method', 'api/basic_json_view/cend/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::cend', 'Method', 'api/basic_json_view/cend/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::contains', 'Method', 'api/basic_json_view/contains/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::contains', 'Method', 'api/basic_json_view/contains/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::count', 'Method', 'api/basic_json_view/count/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::count', 'Method', 'api/basic_json_view/count/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::dump', 'Method', 'api/basic_json_view/dump/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::empty', 'Method', 'api/basic_json_view/empty/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::empty', 'Method', 'api/basic_json_view/empty/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::end', 'Method', 'api/basic_json_view/end/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::end', 'Method', 'api/basic_json_view/end/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::find', 'Method', 'api/basic_json_view/find/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::find', 'Method', 'api/basic_json_view/find/index.html');
|
||||||
@@ -172,9 +173,13 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_string',
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_structured', 'Method', 'api/basic_json_view/is_structured/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_structured', 'Method', 'api/basic_json_view/is_structured/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::items', 'Method', 'api/basic_json_view/items/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::items', 'Method', 'api/basic_json_view/items/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::materialize', 'Method', 'api/basic_json_view/materialize/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::materialize', 'Method', 'api/basic_json_view/materialize/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_format', 'Enum', 'api/basic_json_view/number_format/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_token', 'Method', 'api/basic_json_view/number_token/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_token', 'Method', 'api/basic_json_view/number_token/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator bool', 'Method', 'api/basic_json_view/operator_bool/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator bool', 'Method', 'api/basic_json_view/operator_bool/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator<<', 'Operator', 'api/basic_json_view/operator_ltlt/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator[]', 'Operator', 'api/basic_json_view/operator[]/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator[]', 'Operator', 'api/basic_json_view/operator[]/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator==', 'Operator', 'api/basic_json_view/operator_eq/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator!=', 'Operator', 'api/basic_json_view/operator_ne/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::size', 'Method', 'api/basic_json_view/size/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::size', 'Method', 'api/basic_json_view/size/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::source_offset', 'Method', 'api/basic_json_view/source_offset/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::source_offset', 'Method', 'api/basic_json_view/source_offset/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type', 'Method', 'api/basic_json_view/type/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type', 'Method', 'api/basic_json_view/type/index.html');
|
||||||
@@ -302,3 +307,5 @@ INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_SERIALIZE_ENUM'
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MAJOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MAJOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MINOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MINOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_PATCH', 'Macro', 'api/macros/nlohmann_json_version_major/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_PATCH', 'Macro', 'api/macros/nlohmann_json_version_major/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_VIEW_NO_SIMD', 'Macro', 'api/macros/json_view_no_simd/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_VIEW_USE_SSSE3', 'Macro', 'api/macros/json_view_use_ssse3/index.html');
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ Binary values are serialized as an object containing two keys:
|
|||||||
|
|
||||||
- [to_string](to_string.md) returns a string representation of a JSON value
|
- [to_string](to_string.md) returns a string representation of a JSON value
|
||||||
- [operator<<](../operator_ltlt.md) serialize to stream
|
- [operator<<](../operator_ltlt.md) serialize to stream
|
||||||
|
- [`basic_json_view::dump`](../basic_json_view/dump.md) the corresponding function of `basic_json_view`, serializing
|
||||||
|
directly from a flat index without building a `basic_json` value
|
||||||
- [Serialization](../../features/serialization.md) - the serialization article
|
- [Serialization](../../features/serialization.md) - the serialization article
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ Linear.
|
|||||||
|
|
||||||
- [operator!=](operator_ne.md) compare for inequality
|
- [operator!=](operator_ne.md) compare for inequality
|
||||||
- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20)
|
- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20)
|
||||||
|
- [basic_json_view::operator==](../basic_json_view/operator_eq.md) - the same comparison on a zero-copy view, without
|
||||||
|
building a `basic_json` value for it
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ Linear.
|
|||||||
--8<-- "examples/operator__notequal__nullptr_t.output"
|
--8<-- "examples/operator__notequal__nullptr_t.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [operator==](operator_eq.md) compare for equality
|
||||||
|
- [basic_json_view::operator!=](../basic_json_view/operator_ne.md) - the same comparison on a zero-copy view, without
|
||||||
|
building a `basic_json` value for it
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.13.0 to remove
|
1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.13.0 to remove
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||||
another, in document order, stopping at the first match. Each comparison first checks the key's length --
|
another, in document order, stopping at the first match. Each comparison first checks the key's length --
|
||||||
already known from the index, without reading the key bytes -- before comparing its content.
|
already known from the index, without reading the key bytes -- before comparing its content.
|
||||||
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||||
|
on average.
|
||||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||||
index (unlike `BasicJsonType`'s array, which is random-access).
|
index (unlike `BasicJsonType`'s array, which is random-access).
|
||||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ No-throw guarantee: this function never throws exceptions.
|
|||||||
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||||
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already
|
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already
|
||||||
known from the index, without reading the key bytes -- before comparing its content.
|
known from the index, without reading the key bytes -- before comparing its content.
|
||||||
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||||
|
on average.
|
||||||
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||||
that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and
|
that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and
|
||||||
[`at`](at.md#complexity) with a JSON pointer.
|
[`at`](at.md#complexity) with a JSON pointer.
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ No-throw guarantee: this function never throws exceptions.
|
|||||||
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||||
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already
|
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already
|
||||||
known from the index, without reading the key bytes -- before comparing its content.
|
known from the index, without reading the key bytes -- before comparing its content.
|
||||||
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
||||||
|
average.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# <small>nlohmann::basic_json_view::</small>dump
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
string_t dump(const int indent = -1,
|
||||||
|
const char indent_char = ' ',
|
||||||
|
const bool ensure_ascii = false,
|
||||||
|
const number_format numbers = number_format::shortest) const;
|
||||||
|
```
|
||||||
|
|
||||||
|
Serializes this value (and its subtree) directly from the flat index, without ever building a `BasicJsonType` value
|
||||||
|
first. With the default `#!cpp numbers == number_format::shortest`, the result is the same string
|
||||||
|
[`BasicJsonType::dump`](../basic_json/dump.md) would produce for the value
|
||||||
|
[`BasicJsonType::parse()`](../basic_json/parse.md) builds from the same source text, called with the same `indent`,
|
||||||
|
`indent_char`, and `ensure_ascii` -- except that members of an object appear in document order rather than sorted by
|
||||||
|
key, and *every* occurrence of a repeated key is written rather than only the last one (see
|
||||||
|
[Notes on duplicate keys](operator[].md#notes)). For a `json_view` (whose `BasicJsonType` is not ordered), this means
|
||||||
|
`dump()` can print an object's members in a different order than [`materialize()`](materialize.md)`.dump()` of the
|
||||||
|
same subtree.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
`indent` (in)
|
||||||
|
: If `indent` is nonnegative, array elements and object members are pretty-printed with that indent level. An
|
||||||
|
indent level of `0` only inserts newlines. `-1` (the default) selects the most compact representation.
|
||||||
|
|
||||||
|
`indent_char` (in)
|
||||||
|
: The character used for indentation if `indent` is greater than `0`. The default is ` ` (space).
|
||||||
|
|
||||||
|
`ensure_ascii` (in)
|
||||||
|
: If `ensure_ascii` is `#!cpp true`, all non-ASCII characters in the output are escaped with `\uXXXX` sequences, and
|
||||||
|
the result consists of ASCII characters only.
|
||||||
|
|
||||||
|
`numbers` (in)
|
||||||
|
: how to write numbers, see [`number_format`](number_format.md): `shortest` (the default) writes them the way
|
||||||
|
[`BasicJsonType::dump`](../basic_json/dump.md) would; `source` copies every number exactly as it appears in the
|
||||||
|
source text.
|
||||||
|
|
||||||
|
## Return value
|
||||||
|
|
||||||
|
string containing the serialization of this value, or `#!cpp "<discarded>"` if the view is
|
||||||
|
[discarded](is_discarded.md).
|
||||||
|
|
||||||
|
## Exception safety
|
||||||
|
|
||||||
|
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
May throw `#!cpp std::bad_alloc` if allocating the output string fails. Unlike
|
||||||
|
[`BasicJsonType::dump`](../basic_json/dump.md), there is no `error_handler` parameter and no
|
||||||
|
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316): the view only ever holds text the parser
|
||||||
|
already validated as UTF-8, so there is nothing to replace or ignore.
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
|
||||||
|
Linear in the size of the output text.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
The walk over the subtree is iterative, so the nesting depth it can write is limited by available memory only, not by
|
||||||
|
the call stack -- as for [`materialize()`](materialize.md).
|
||||||
|
|
||||||
|
Strings are escaped by the same rules as [`BasicJsonType::dump`](../basic_json/dump.md). With
|
||||||
|
`#!cpp numbers == number_format::shortest`, floats are written with the library's shortest round-trip conversion,
|
||||||
|
exactly as [`BasicJsonType::dump`](../basic_json/dump.md) would (e.g. `#!cpp 1.5`, `#!cpp 100.0`, `#!cpp 1e+100`), and
|
||||||
|
integers are copied from the source text -- already canonical in JSON, so this matches their shortest form too --
|
||||||
|
except that `#!cpp -0` is written as `#!cpp 0`, the way [`BasicJsonType::parse()`](../basic_json/parse.md) reads it.
|
||||||
|
`#!cpp number_format::source` copies every number exactly as written in the source text instead, with no exception
|
||||||
|
for `#!cpp -0` -- `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more
|
||||||
|
digits than any number type holds (such a literal is itself classified as a float, see
|
||||||
|
[What is different](../../features/json_view.md#what-is-different)) -- something `BasicJsonType` cannot do, since
|
||||||
|
parsing already reduces every number to its parsed value.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The example below forwards a single record out of a larger batch, and re-serializes a configuration file, both
|
||||||
|
without ever building a `BasicJsonType` value for the surrounding array or for the parts of it that were not
|
||||||
|
needed. It also shows that [`materialize()`](materialize.md)`.dump()` of the configuration sorts its keys, where
|
||||||
|
`dump()` on the view keeps the order they appear in the source text.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/basic_json_view__dump.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/basic_json_view__dump.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [`number_format`](number_format.md) - how `dump()` writes numbers
|
||||||
|
- [operator<<](operator_ltlt.md) - serialize this value to a stream
|
||||||
|
- [materialize](materialize.md) - build a `BasicJsonType` value, e.g. to use `BasicJsonType::dump`'s `error_handler`
|
||||||
|
- [`BasicJsonType::dump`](../basic_json/dump.md) - the corresponding function of `basic_json`
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -28,6 +28,8 @@ No-throw guarantee: this function never throws exceptions.
|
|||||||
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||||
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already
|
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already
|
||||||
known from the index, without reading the key bytes -- before comparing its content.
|
known from the index, without reading the key bytes -- before comparing its content.
|
||||||
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
||||||
|
average.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ Moving the document itself does not invalidate its views: the index is heap-allo
|
|||||||
`basic_json_document` object.
|
`basic_json_document` object.
|
||||||
|
|
||||||
`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element
|
`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element
|
||||||
access, lookup, iteration, and conversion -- [`get<T>()`](get.md), [`get_string()`](get_string.md),
|
access, lookup, iteration, conversion, and comparison -- [`get<T>()`](get.md), [`get_string()`](get_string.md),
|
||||||
[`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a
|
[`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a
|
||||||
subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and
|
subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and
|
||||||
[`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). It does not (yet) provide `dump()` or
|
[`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). [`operator==`](operator_eq.md) and
|
||||||
comparison.
|
[`operator!=`](operator_ne.md) compare two views, or a view and a `BasicJsonType` value, without ever building a
|
||||||
|
`BasicJsonType` value for a view; no ordering comparison (`#!cpp operator<`) is provided.
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ comparison.
|
|||||||
- **iterator**, **const_iterator** - a forward iterator over the elements of an array or the member values of an
|
- **iterator**, **const_iterator** - a forward iterator over the elements of an array or the member values of an
|
||||||
object, in document order; both names refer to the same type, since a view is always read-only
|
object, in document order; both names refer to the same type, since a view is always read-only
|
||||||
- **item** - a (key, value) pair produced by [`items()`](items.md)
|
- **item** - a (key, value) pair produced by [`items()`](items.md)
|
||||||
|
- [**number_format**](number_format.md) - how [`dump()`](dump.md) writes numbers
|
||||||
|
|
||||||
## Member functions
|
## Member functions
|
||||||
|
|
||||||
@@ -106,6 +108,16 @@ comparison.
|
|||||||
- [**number_token**](number_token.md) - get a number's token text without a copy
|
- [**number_token**](number_token.md) - get a number's token text without a copy
|
||||||
- [**materialize**](materialize.md) - build the `BasicJsonType` value of this subtree
|
- [**materialize**](materialize.md) - build the `BasicJsonType` value of this subtree
|
||||||
|
|
||||||
|
### Comparison
|
||||||
|
|
||||||
|
- [**operator==**](operator_eq.md) - comparison: equal
|
||||||
|
- [**operator!=**](operator_ne.md) - comparison: not equal
|
||||||
|
|
||||||
|
### Serialization
|
||||||
|
|
||||||
|
- [**dump**](dump.md) - serialize to a JSON-formatted string
|
||||||
|
- [**operator<<**](operator_ltlt.md) - serialize to stream
|
||||||
|
|
||||||
### Source access
|
### Source access
|
||||||
|
|
||||||
- [**source_offset**](source_offset.md) - byte offset of this value in the document's source text
|
- [**source_offset**](source_offset.md) - byte offset of this value in the document's source text
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# <small>nlohmann::basic_json_view::</small>number_format
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class number_format {
|
||||||
|
shortest,
|
||||||
|
source
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This enumeration is used in [`dump`](dump.md) to choose how numbers are written. Two values are differentiated:
|
||||||
|
|
||||||
|
shortest
|
||||||
|
: integers are copied from the source text -- already canonical in JSON -- except that `#!cpp -0` becomes
|
||||||
|
`#!cpp 0`, the way [`BasicJsonType::parse()`](../basic_json/parse.md) reads it; floats are written with the
|
||||||
|
library's shortest round-trip conversion, exactly as [`BasicJsonType::dump()`](../basic_json/dump.md) would (e.g.
|
||||||
|
`#!cpp 1.5`, `#!cpp 100.0`, `#!cpp 1e+100`)
|
||||||
|
|
||||||
|
source
|
||||||
|
: every number is copied exactly as it appears in the source text -- `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0`, all
|
||||||
|
digits of an integer literal with more digits than any number type holds -- something `BasicJsonType` cannot do,
|
||||||
|
since parsing already reduces every number to its parsed value
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The example below writes back a price list received from a supplier: with `number_format::shortest` (the
|
||||||
|
default), a trailing zero and scientific notation are normalized away and a long account number that overflows
|
||||||
|
every number type is rounded, the same way `#!cpp materialize().dump()` (or `basic_json::dump()`) would;
|
||||||
|
`number_format::source` keeps every number exactly as it was written in the source text instead.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/basic_json_view__number_format.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/basic_json_view__number_format.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [dump](dump.md) - serialize to a JSON-formatted string
|
||||||
|
- [number_token](number_token.md) - get a single number's token text without dumping the whole value
|
||||||
|
- [`BasicJsonType::error_handler_t`](../basic_json/error_handler_t.md) - the analogous enumeration for
|
||||||
|
`BasicJsonType::dump`'s decoding-error behavior
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -76,6 +76,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
another, in document order, stopping at the first match. Each comparison first checks the key's length --
|
another, in document order, stopping at the first match. Each comparison first checks the key's length --
|
||||||
already known from the index, without reading the key bytes -- before comparing its content, so a key of a
|
already known from the index, without reading the key bytes -- before comparing its content, so a key of a
|
||||||
different length than `key` is rejected without touching the source text.
|
different length than `key` is rejected without touching the source text.
|
||||||
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||||
|
on average.
|
||||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||||
index (unlike `BasicJsonType`'s array, which is random-access).
|
index (unlike `BasicJsonType`'s array, which is random-access).
|
||||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# <small>nlohmann::basic_json_view::</small>operator==
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// (1)
|
||||||
|
bool operator==(const basic_json_view& lhs, const basic_json_view& rhs);
|
||||||
|
|
||||||
|
// (2)
|
||||||
|
bool operator==(const basic_json_view& lhs, const BasicJsonType& rhs);
|
||||||
|
bool operator==(const BasicJsonType& lhs, const basic_json_view& rhs);
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Compares two views for equality: whether the values [`BasicJsonType::parse()`](../basic_json/parse.md) would
|
||||||
|
produce for `lhs` and `rhs` are equal, according to `BasicJsonType`'s [`operator==`](../basic_json/operator_eq.md).
|
||||||
|
2. Compares a view and a `BasicJsonType` value for equality, in either order: whether the value `parse()` would
|
||||||
|
produce for the view and the other operand are equal, according to `BasicJsonType`'s
|
||||||
|
[`operator==`](../basic_json/operator_eq.md).
|
||||||
|
|
||||||
|
Neither overload builds a `BasicJsonType` value for a view to do the comparison (see [Notes](#notes) below). Numbers
|
||||||
|
compare by value across their types (`#!cpp 1 == 1.0`), and an object compares by its members, with duplicate keys
|
||||||
|
resolved exactly as `parse()` resolves them -- the last value, at the position of the first occurrence of the key.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
`lhs` (in)
|
||||||
|
: first value to consider
|
||||||
|
|
||||||
|
`rhs` (in)
|
||||||
|
: second value to consider
|
||||||
|
|
||||||
|
## Return value
|
||||||
|
|
||||||
|
whether the values `lhs` and `rhs` are equal
|
||||||
|
|
||||||
|
## Exception safety
|
||||||
|
|
||||||
|
Strong exception safety: if an exception is thrown, there are no changes to either operand, or to the document(s) a
|
||||||
|
view refers to.
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
May throw `#!cpp std::bad_alloc`. Unlike the other comparison and most other `basic_json_view` functions,
|
||||||
|
`operator==` is not `#!cpp noexcept`: resolving an object's members needs a temporary array to sort them by key (see
|
||||||
|
[Complexity](#complexity) below), and that allocation can fail.
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
|
||||||
|
Linear in the size of the compared values: every number, string, array element, and object member is visited at most
|
||||||
|
once, and the walk is iterative, so the nesting depth it can compare is limited by available memory only, not by the
|
||||||
|
call stack (as for [`materialize()`](materialize.md)). Resolving an object's members takes an additional O(n log n)
|
||||||
|
in the number of members at that level, since they are sorted by key to detect and resolve duplicates before being
|
||||||
|
compared. Two arrays of different [`size()`](size.md) are rejected without visiting either one's elements.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Only a single number, boolean, or `#!cpp null` value is ever materialized into a `BasicJsonType`, to reuse its
|
||||||
|
`operator==` -- for numbers, so that values written differently in the source text but equal in value (e.g. an
|
||||||
|
integer and a floating-point literal) still compare equal, following the same rules `BasicJsonType` does for special
|
||||||
|
values such as `#!cpp NaN`. Constructing one of these scalars never allocates. Strings are compared directly, without
|
||||||
|
allocating, either from the source text on both sides or, for overload 2, against `BasicJsonType`'s own string.
|
||||||
|
Arrays and objects are never materialized at all; only their elements or members are visited, one pair at a time.
|
||||||
|
|
||||||
|
!!! info "How objects are compared"
|
||||||
|
|
||||||
|
For a [`json_view`](../json_view.md) (`BasicJsonType::object_t` is `#!cpp std::map`), members are compared by
|
||||||
|
key, regardless of the order they appear in the source text. For an
|
||||||
|
[`ordered_json_view`](../ordered_json_view.md) (`object_t` is `ordered_map`), they are compared in the order
|
||||||
|
they occur, so the very same two objects with their members reordered can compare equal as `json_view`s but not
|
||||||
|
as `ordered_json_view`s. This is exactly how [`json`](../json.md) and [`ordered_json`](../ordered_json.md)
|
||||||
|
compare, see ["Comparing different `basic_json` specializations"](../basic_json/operator_eq.md#notes).
|
||||||
|
|
||||||
|
!!! info "Discarded views"
|
||||||
|
|
||||||
|
A [discarded](is_discarded.md) view compares the same way a discarded `BasicJsonType` value does, which is
|
||||||
|
governed by
|
||||||
|
[`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../macros/json_use_legacy_discarded_value_comparison.md): by
|
||||||
|
default, a discarded view is never equal to anything, not even another discarded view.
|
||||||
|
|
||||||
|
No ordering comparison (`#!cpp operator<`) is provided for `basic_json_view`; [`materialize()`](materialize.md) is
|
||||||
|
the way to get a `BasicJsonType` value that supports it.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The example below checks whether a newly received configuration differs from the previous one, and whether a
|
||||||
|
received document matches what a test expects -- directly on views, without ever materializing a `BasicJsonType`
|
||||||
|
value for either side.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/basic_json_view__operator_eq.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/basic_json_view__operator_eq.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [operator!=](operator_ne.md) - compare for inequality
|
||||||
|
- [materialize](materialize.md) - build a `BasicJsonType` value, e.g. to keep comparing after the document is gone
|
||||||
|
- [`BasicJsonType::operator==`](../basic_json/operator_eq.md) - the corresponding function of `basic_json`
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# <small>nlohmann::basic_json_view::</small>operator<<
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::ostream& operator<<(std::ostream& o, const basic_json_view& v);
|
||||||
|
```
|
||||||
|
|
||||||
|
Not available when [`JSON_NO_IO`](../macros/json_no_io.md) is defined.
|
||||||
|
|
||||||
|
Serializes the given view `v` to the output stream `o`, using [`dump`](dump.md) -- exactly as
|
||||||
|
`#!cpp operator<<(std::ostream&, const basic_json&)` does for a `basic_json` value.
|
||||||
|
|
||||||
|
- The indentation of the output can be controlled with the member variable `width` of the output stream `o`. For
|
||||||
|
instance, using the manipulator `std::setw(4)` on `o` sets the indentation level to `4`, and the serialization
|
||||||
|
result is the same as calling `#!cpp v.dump(4)`. A `width` of `0` or less (the default) selects the most compact
|
||||||
|
representation, as `#!cpp v.dump(-1)` does.
|
||||||
|
- The indentation character can be controlled with the member variable `fill` of the output stream `o`. For instance,
|
||||||
|
the manipulator `std::setfill('\t')` sets indentation to use a tab character rather than the default space
|
||||||
|
character.
|
||||||
|
- As for `basic_json`, `o`'s `width` is reset to `0` after this call, whether or not it was greater than `0` before.
|
||||||
|
|
||||||
|
Numbers are always written as `#!cpp v.dump()` writes them by default, i.e. as with
|
||||||
|
[`number_format::shortest`](number_format.md); there is no way to select `#!cpp number_format::source` through the
|
||||||
|
stream.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
`o` (in, out)
|
||||||
|
: stream to write to
|
||||||
|
|
||||||
|
`v` (in)
|
||||||
|
: view to serialize
|
||||||
|
|
||||||
|
## Return value
|
||||||
|
|
||||||
|
the stream `o`
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
May throw `#!cpp std::bad_alloc`, propagated from [`dump`](dump.md#exceptions). Unlike
|
||||||
|
`#!cpp operator<<(std::ostream&, const basic_json&)`, there is no UTF-8 decoding step that could throw
|
||||||
|
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316), and no `error_handler` to choose between --
|
||||||
|
see the [Exceptions](dump.md#exceptions) of `dump`.
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
|
||||||
|
Linear, as [`dump`](dump.md#complexity).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The example below writes one record out of a larger batch straight to a log stream -- compact for a one-line
|
||||||
|
entry, and pretty-printed with `std::setw`/`std::setfill` for a readable dump -- without ever building a
|
||||||
|
`BasicJsonType` value for the record, or for the rest of the batch.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/basic_json_view__operator_ltlt.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/basic_json_view__operator_ltlt.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [dump](dump.md) - serialize to a JSON-formatted string
|
||||||
|
- [`operator<<(std::ostream&)`](../operator_ltlt.md) - the corresponding operator for `basic_json`
|
||||||
|
- [`JSON_NO_IO`](../macros/json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# <small>nlohmann::basic_json_view::</small>operator!=
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// (1)
|
||||||
|
bool operator!=(const basic_json_view& lhs, const basic_json_view& rhs);
|
||||||
|
|
||||||
|
// (2)
|
||||||
|
bool operator!=(const basic_json_view& lhs, const BasicJsonType& rhs);
|
||||||
|
bool operator!=(const BasicJsonType& lhs, const basic_json_view& rhs);
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Compares two views for inequality. Returns `#!cpp !(lhs == rhs)`, see [operator==](operator_eq.md).
|
||||||
|
2. Compares a view and a `BasicJsonType` value for inequality, in either order. Returns `#!cpp !(lhs == rhs)` (or,
|
||||||
|
for the reversed order, `#!cpp !(rhs == lhs)`), see [operator==](operator_eq.md).
|
||||||
|
|
||||||
|
Since `operator!=` is defined as the negation of [`operator==`](operator_eq.md), it follows the same rules for
|
||||||
|
special cases: for instance, since a [discarded](is_discarded.md) view is never equal to anything by default (see
|
||||||
|
[operator=='s Notes](operator_eq.md#notes)), it is never *unequal* to anything either -- `#!cpp discarded != discarded`
|
||||||
|
is also `#!cpp false`, exactly as for a discarded `BasicJsonType` value.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
`lhs` (in)
|
||||||
|
: first value to consider
|
||||||
|
|
||||||
|
`rhs` (in)
|
||||||
|
: second value to consider
|
||||||
|
|
||||||
|
## Return value
|
||||||
|
|
||||||
|
whether the values `lhs` and `rhs` are not equal
|
||||||
|
|
||||||
|
## Exception safety
|
||||||
|
|
||||||
|
Strong exception safety: if an exception is thrown, there are no changes to either operand, or to the document(s) a
|
||||||
|
view refers to.
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
May throw `#!cpp std::bad_alloc`, propagated from [`operator==`](operator_eq.md#exceptions). Unlike most other
|
||||||
|
`basic_json_view` functions, `operator!=` is not `#!cpp noexcept`.
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
|
||||||
|
Linear, as [`operator==`](operator_eq.md#complexity).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
See the [Notes](operator_eq.md#notes) of `operator==` -- in particular for how an object's members are compared
|
||||||
|
(order matters for [`ordered_json_view`](../ordered_json_view.md) but not for [`json_view`](../json_view.md)) and
|
||||||
|
for how discarded views compare.
|
||||||
|
|
||||||
|
No ordering comparison (`#!cpp operator<`) is provided for `basic_json_view`; [`materialize()`](materialize.md) is
|
||||||
|
the way to get a `BasicJsonType` value that supports it.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The example below asserts, as a test would, that a received document differs from an unwanted value, and shows
|
||||||
|
that -- as for [`json`](../json.md)/[`ordered_json`](../ordered_json.md) -- reordering an object's members is
|
||||||
|
detected as a difference for an `ordered_json_view` but not for a `json_view`.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/basic_json_view__operator_ne.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/basic_json_view__operator_ne.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [operator==](operator_eq.md) - compare for equality
|
||||||
|
- [materialize](materialize.md) - build a `BasicJsonType` value, e.g. to keep comparing after the document is gone
|
||||||
|
- [`BasicJsonType::operator!=`](../basic_json/operator_ne.md) - the corresponding function of `basic_json`
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -70,6 +70,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after
|
1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after
|
||||||
another, in document order, stopping at the first match. Plus the complexity of converting the found member to
|
another, in document order, stopping at the first match. Plus the complexity of converting the found member to
|
||||||
`T` (see [`get`](get.md)).
|
`T` (see [`get`](get.md)).
|
||||||
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||||
|
on average.
|
||||||
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||||
that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and
|
that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and
|
||||||
[`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value
|
[`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
||||||
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
||||||
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
||||||
|
- [**JSON_VIEW_NO_SIMD**](json_view_no_simd.md) - use only portable code in the parser of `json_view.hpp`
|
||||||
|
- [**JSON_VIEW_USE_SSSE3**](json_view_use_ssse3.md) - validate non-ASCII strings with SSSE3 in the parser of `json_view.hpp`
|
||||||
|
|
||||||
## Library version
|
## Library version
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# JSON_VIEW_NO_SIMD
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_VIEW_NO_SIMD
|
||||||
|
```
|
||||||
|
|
||||||
|
When defined, the parser of [`basic_json_document`](../basic_json_document/index.md) (`<nlohmann/json_view.hpp>`)
|
||||||
|
uses only portable C++ to scan strings. By default, it scans long runs of string bytes 16 at a time with NEON on
|
||||||
|
AArch64 (with GCC and Clang) and SSE2 on x86-64, which are part of the baseline instruction sets of these
|
||||||
|
architectures, and validates non-ASCII text with NEON (or SSSE3, see
|
||||||
|
[`JSON_VIEW_USE_SSSE3`](json_view_use_ssse3.md)).
|
||||||
|
|
||||||
|
The same input is accepted or rejected either way, with the same values, and errors are reported the same way; only
|
||||||
|
the speed differs. The macro exists for platforms whose compilers lack the intrinsics headers, and to test the portable
|
||||||
|
code.
|
||||||
|
|
||||||
|
!!! warning "Define consistently"
|
||||||
|
|
||||||
|
The macro selects between two definitions of the same inline functions. It must therefore be defined identically for
|
||||||
|
**every** translation unit that includes `<nlohmann/json_view.hpp>`; prefer a compile definition on the target.
|
||||||
|
|
||||||
|
## Default definition
|
||||||
|
|
||||||
|
By default, `#!cpp JSON_VIEW_NO_SIMD` is not defined, and the vector code is used where available.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#undef JSON_VIEW_NO_SIMD
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The code below uses the portable string scanning of the view.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_VIEW_NO_SIMD
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [JSON_VIEW_USE_SSSE3](json_view_use_ssse3.md) - validate non-ASCII strings with SSSE3 on x86-64
|
||||||
|
- [json_view](../../features/json_view.md) - the zero-copy view
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# JSON_VIEW_USE_SSSE3
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_VIEW_USE_SSSE3
|
||||||
|
```
|
||||||
|
|
||||||
|
When defined on x86-64, the parser of [`basic_json_document`](../basic_json_document/index.md)
|
||||||
|
(`<nlohmann/json_view.hpp>`) validates non-ASCII text in strings with SSSE3, 16 bytes at a time, using the "lookup4"
|
||||||
|
algorithm of [simdjson](https://github.com/simdjson/simdjson). Without it, non-ASCII text is validated one UTF-8
|
||||||
|
sequence at a time on x86-64; on AArch64, the vector check uses NEON and is always on.
|
||||||
|
|
||||||
|
SSSE3 is not part of the x86-64 baseline, so the code must be compiled for it: define the macro only together with a
|
||||||
|
compiler option that enables SSSE3 (e.g. `-mssse3`, or `-march=` with a CPU that has it), and only for programs that
|
||||||
|
run on such CPUs. The same input is accepted or rejected either way; only the speed of non-ASCII text differs.
|
||||||
|
|
||||||
|
!!! warning "Define consistently"
|
||||||
|
|
||||||
|
The macro selects between two definitions of the same inline functions. It must therefore be defined identically,
|
||||||
|
with the same compiler options, for **every** translation unit that includes `<nlohmann/json_view.hpp>`; mixing
|
||||||
|
translation units that define it with ones that do not is an ODR violation. Prefer a compile definition on the
|
||||||
|
target.
|
||||||
|
|
||||||
|
## Default definition
|
||||||
|
|
||||||
|
By default, `#!cpp JSON_VIEW_USE_SSSE3` is not defined.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#undef JSON_VIEW_USE_SSSE3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
With CMake, for a program that only runs on CPUs with SSSE3:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
target_compile_definitions(your_target PRIVATE JSON_VIEW_USE_SSSE3)
|
||||||
|
target_compile_options(your_target PRIVATE -mssse3)
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [JSON_VIEW_NO_SIMD](json_view_no_simd.md) - use only portable code in the view's parser
|
||||||
|
- [JSON_USE_SIMDUTF](json_use_simdutf.md) - validate UTF-8 with simdutf in `basic_json`'s parser
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -84,6 +84,8 @@ Linear.
|
|||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [dump](basic_json/dump.md) - serialize to a JSON-formatted string
|
- [dump](basic_json/dump.md) - serialize to a JSON-formatted string
|
||||||
|
- [`basic_json_view::operator<<`](basic_json_view/operator_ltlt.md) - the corresponding operator for
|
||||||
|
`basic_json_view`
|
||||||
- [Serialization](../features/serialization.md) - the serialization article
|
- [Serialization](../features/serialization.md) - the serialization article
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
using json_document = nlohmann::json_document;
|
||||||
|
using json_view = nlohmann::json_view;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// a large batch of sensor readings -- forward just the one that changed,
|
||||||
|
// without ever building a basic_json value for the batch or for the
|
||||||
|
// readings that are not needed
|
||||||
|
const json_document batch = json_document::parse(R"(
|
||||||
|
[{"id": 1, "temp": 21.5}, {"id": 2, "temp": 87.3}, {"id": 3, "temp": 21.7}]
|
||||||
|
)");
|
||||||
|
const json_view readings = batch.root();
|
||||||
|
std::cout << readings[1].dump() << '\n';
|
||||||
|
|
||||||
|
// a configuration file -- dump() on the view keeps the member order of
|
||||||
|
// the source text; a json value's object_t is std::map, so
|
||||||
|
// materialize().dump() of the very same view sorts the keys instead
|
||||||
|
const json_document config = json_document::parse(
|
||||||
|
R"({"name": "cache", "host": "db1", "port": 6379, "timeout": 30})");
|
||||||
|
std::cout << config.root().dump(2) << "\n\n";
|
||||||
|
std::cout << config.root().materialize().dump(2) << '\n';
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{"id":2,"temp":87.3}
|
||||||
|
{
|
||||||
|
"name": "cache",
|
||||||
|
"host": "db1",
|
||||||
|
"port": 6379,
|
||||||
|
"timeout": 30
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"host": "db1",
|
||||||
|
"name": "cache",
|
||||||
|
"port": 6379,
|
||||||
|
"timeout": 30
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
using json_document = nlohmann::json_document;
|
||||||
|
using json_view = nlohmann::json_view;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// a price list received from a supplier feed -- prices and account
|
||||||
|
// numbers must be forwarded exactly, e.g. into an invoice
|
||||||
|
const json_document doc = json_document::parse(R"(
|
||||||
|
[{"sku": "A1", "price": 19.90, "account_id": 12345678901234567890123456},
|
||||||
|
{"sku": "A2", "price": 1E2, "account_id": 98765432109876543210987654}]
|
||||||
|
)");
|
||||||
|
const json_view list = doc.root();
|
||||||
|
|
||||||
|
// number_format::shortest (the default) writes numbers the way
|
||||||
|
// basic_json::dump() would: "19.90" becomes "19.9", "1E2" becomes
|
||||||
|
// "100.0", and each account number -- far beyond any 64-bit integer --
|
||||||
|
// is rounded to the nearest double, exactly as materialize().dump()
|
||||||
|
// (or a plain nlohmann::json) would round it
|
||||||
|
std::cout << list.dump() << '\n';
|
||||||
|
|
||||||
|
// number_format::source copies every number exactly as it was written
|
||||||
|
// in the source text instead -- something basic_json cannot do at all,
|
||||||
|
// since parsing already reduces every number to its parsed value
|
||||||
|
std::cout << list.dump(-1, ' ', false, json_view::number_format::source) << '\n';
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[{"sku":"A1","price":19.9,"account_id":1.2345678901234568e+25},{"sku":"A2","price":100.0,"account_id":9.876543210987655e+25}]
|
||||||
|
[{"sku":"A1","price":19.90,"account_id":12345678901234567890123456},{"sku":"A2","price":1E2,"account_id":98765432109876543210987654}]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
using json_document = nlohmann::json_document;
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// two snapshots of a polled configuration endpoint -- compare them
|
||||||
|
// directly as views, without ever building a nlohmann::json value for
|
||||||
|
// either one
|
||||||
|
const json_document previous = json_document::parse(
|
||||||
|
R"({"name": "cache", "port": 6379, "timeout": 30})");
|
||||||
|
const json_document current = json_document::parse(
|
||||||
|
R"({"port": 6379.0, "timeout": 30, "name": "cache"})");
|
||||||
|
|
||||||
|
// same members, reordered, and 6379 written as a float -- operator==
|
||||||
|
// treats them the same way BasicJsonType::operator== would
|
||||||
|
std::cout << std::boolalpha << (previous.root() == current.root()) << '\n';
|
||||||
|
|
||||||
|
// an actually changed value is detected the same way
|
||||||
|
const json_document changed = json_document::parse(
|
||||||
|
R"({"name": "cache", "port": 6380, "timeout": 30})");
|
||||||
|
std::cout << (previous.root() == changed.root()) << '\n';
|
||||||
|
|
||||||
|
// comparing a view directly against an expected json value -- handy in a
|
||||||
|
// test, without materializing the received document at all
|
||||||
|
const json expected = {{"name", "cache"}, {"port", 6379}, {"timeout", 30}};
|
||||||
|
std::cout << (previous.root() == expected) << '\n';
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
true
|
||||||
|
false
|
||||||
|
true
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
using json_document = nlohmann::json_document;
|
||||||
|
using json_view = nlohmann::json_view;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// one order out of a large incoming batch -- write it straight to a log
|
||||||
|
// stream without ever building a basic_json value for it, or for the
|
||||||
|
// rest of the batch
|
||||||
|
const json_document doc = json_document::parse(R"(
|
||||||
|
[{"id": 1, "item": "cable"}, {"id": 2, "item": "adapter"}]
|
||||||
|
)");
|
||||||
|
const json_view orders = doc.root();
|
||||||
|
|
||||||
|
// compact, for a one-line log entry
|
||||||
|
std::cout << orders[1] << '\n';
|
||||||
|
|
||||||
|
// std::setw sets the indentation level, exactly as for basic_json
|
||||||
|
std::cout << std::setw(2) << orders[1] << "\n\n";
|
||||||
|
|
||||||
|
// std::setfill changes the indentation character
|
||||||
|
std::cout << std::setw(1) << std::setfill('\t') << orders[1] << '\n';
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{"id":2,"item":"adapter"}
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"item": "adapter"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"item": "adapter"
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
using json_document = nlohmann::json_document;
|
||||||
|
using ordered_json_document = nlohmann::ordered_json_document;
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// assert, as a test would, that a received document differs from an
|
||||||
|
// unwanted shape -- without ever materializing it into a json value just
|
||||||
|
// to compare
|
||||||
|
const json_document received = json_document::parse(
|
||||||
|
R"({"status": "ok", "code": 200})");
|
||||||
|
const json unwanted = {{"status", "error"}, {"code", 500}};
|
||||||
|
std::cout << std::boolalpha << (received.root() != unwanted) << '\n';
|
||||||
|
|
||||||
|
// json (std::map) compares object members regardless of order ...
|
||||||
|
const json_document a = json_document::parse(R"({"a": 1, "b": 2})");
|
||||||
|
const json_document b = json_document::parse(R"({"b": 2, "a": 1})");
|
||||||
|
std::cout << (a.root() != b.root()) << '\n';
|
||||||
|
|
||||||
|
// ... but ordered_json (ordered_map) compares them in the order they
|
||||||
|
// appear, so the very same reordering is detected as a difference
|
||||||
|
const ordered_json_document oa = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
||||||
|
const ordered_json_document ob = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
||||||
|
std::cout << (oa.root() != ob.root()) << '\n';
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
true
|
||||||
|
false
|
||||||
|
true
|
||||||
@@ -139,8 +139,12 @@ whenever any of the other conditions above was not met.
|
|||||||
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
|
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
|
||||||
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
||||||
`BasicJsonType` was built.
|
`BasicJsonType` was built.
|
||||||
- **`dump()` and comparison are not (yet) provided** by `basic_json_view`. For now,
|
- **Ordering comparisons are not provided** by `basic_json_view` -- there is no `#!cpp operator<`.
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can do those things with.
|
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
|
||||||
|
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
|
||||||
|
[`materialize()`](../api/basic_json_view/materialize.md) or [`parse()`](../api/basic_json/parse.md) would produce
|
||||||
|
equal values for them, without ever building a tree to do it. For ordering, too,
|
||||||
|
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can compare.
|
||||||
|
|
||||||
## Getting values out without copying
|
## Getting values out without copying
|
||||||
|
|
||||||
@@ -166,6 +170,23 @@ Two conversions never copy at all:
|
|||||||
|
|
||||||
Both results are only valid as long as the view -- and, for a string with no escapes, the borrowed source text -- is.
|
Both results are only valid as long as the view -- and, for a string with no escapes, the borrowed source text -- is.
|
||||||
|
|
||||||
|
## Writing a view back
|
||||||
|
|
||||||
|
[`dump()`](../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a
|
||||||
|
`basic_json` value. An object's members are written in document order, not sorted by key, and *every* occurrence of a
|
||||||
|
repeated key is written, not only the last one -- the same two ways [iteration](#what-is-different) already differs
|
||||||
|
from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see above. `#!cpp materialize().dump()` gives
|
||||||
|
a different result in both respects for a `json_view`.
|
||||||
|
|
||||||
|
By default, numbers are written the way [`basic_json::dump()`](../api/basic_json/dump.md) would.
|
||||||
|
[`number_format::source`](../api/basic_json_view/number_format.md) instead copies every number exactly as it was
|
||||||
|
written in the source text -- a price like `#!cpp 19.90`, a long order or account ID with more digits than any number
|
||||||
|
type holds, or a high-precision coordinate -- something `basic_json` cannot do at all, since parsing already reduces
|
||||||
|
a number to its parsed `#!cpp double`/`#!cpp int64_t` value.
|
||||||
|
|
||||||
|
[`operator<<`](../api/basic_json_view/operator_ltlt.md) writes a view to a stream the way `basic_json`'s does, using
|
||||||
|
the stream's `width`/`fill` for indentation.
|
||||||
|
|
||||||
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
|
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
|
||||||
|
|
||||||
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) |
|
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) |
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ packet-beta
|
|||||||
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
|
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float |
|
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float |
|
||||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes are (0: the source text, 1: the buffer of decoded strings, for strings with escapes); bit 2: the value of a boolean |
|
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes are (0: the source text, 1: the buffer of decoded strings, for strings with escapes); bit 2: the value of a boolean |
|
||||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; otherwise 0 |
|
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their hash index (1-based), or 0; otherwise 0 |
|
||||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||||
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself |
|
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself |
|
||||||
@@ -211,6 +211,11 @@ packet-beta
|
|||||||
subtree is `next` nodes further for an array or object, and the next node otherwise (`document_data::after`). Views
|
subtree is `next` nodes further for an array or object, and the next node otherwise (`document_data::after`). Views
|
||||||
step from element to element this way and skip whole subtrees in constant time.
|
step from element to element this way and skip whole subtrees in constant time.
|
||||||
- **Offsets** are 32 bits wide, so a document is limited to 4 GiB (`out_of_range.416`).
|
- **Offsets** are 32 bits wide, so a document is limited to 4 GiB (`out_of_range.416`).
|
||||||
|
- **Large objects** (128 members or more) get a hash index after parsing
|
||||||
|
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
|
||||||
|
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
|
||||||
|
not compare every key. The object's `extra` holds the number of its table. Only 65,535 tables fit into `extra`;
|
||||||
|
objects beyond them are searched linearly.
|
||||||
|
|
||||||
For example, `#!json {"a": [1, 2.5]}` becomes five nodes. Each node's elements follow it, and `next` leads from an
|
For example, `#!json {"a": [1, 2.5]}` becomes five nodes. Each node's elements follow it, and `next` leads from an
|
||||||
array or object past its subtree:
|
array or object past its subtree:
|
||||||
|
|||||||
@@ -23,3 +23,5 @@ The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Eva
|
|||||||
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||||
|
|
||||||
The view's parser (`<nlohmann/json_view.hpp>`) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks.
|
The view's parser (`<nlohmann/json_view.hpp>`) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks.
|
||||||
|
|
||||||
|
The view's parser (`<nlohmann/json_view.hpp>`) validates non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ nav:
|
|||||||
- 'cend': api/basic_json_view/cend.md
|
- 'cend': api/basic_json_view/cend.md
|
||||||
- 'contains': api/basic_json_view/contains.md
|
- 'contains': api/basic_json_view/contains.md
|
||||||
- 'count': api/basic_json_view/count.md
|
- 'count': api/basic_json_view/count.md
|
||||||
|
- 'dump': api/basic_json_view/dump.md
|
||||||
- 'empty': api/basic_json_view/empty.md
|
- 'empty': api/basic_json_view/empty.md
|
||||||
- 'end': api/basic_json_view/end.md
|
- 'end': api/basic_json_view/end.md
|
||||||
- 'find': api/basic_json_view/find.md
|
- 'find': api/basic_json_view/find.md
|
||||||
@@ -275,9 +276,13 @@ nav:
|
|||||||
- 'is_structured': api/basic_json_view/is_structured.md
|
- 'is_structured': api/basic_json_view/is_structured.md
|
||||||
- 'items': api/basic_json_view/items.md
|
- 'items': api/basic_json_view/items.md
|
||||||
- 'materialize': api/basic_json_view/materialize.md
|
- 'materialize': api/basic_json_view/materialize.md
|
||||||
|
- 'number_format': api/basic_json_view/number_format.md
|
||||||
- 'number_token': api/basic_json_view/number_token.md
|
- 'number_token': api/basic_json_view/number_token.md
|
||||||
- 'operator bool': api/basic_json_view/operator_bool.md
|
- 'operator bool': api/basic_json_view/operator_bool.md
|
||||||
|
- 'operator<<': api/basic_json_view/operator_ltlt.md
|
||||||
- 'operator[]': api/basic_json_view/operator[].md
|
- 'operator[]': api/basic_json_view/operator[].md
|
||||||
|
- 'operator==': api/basic_json_view/operator_eq.md
|
||||||
|
- 'operator!=': api/basic_json_view/operator_ne.md
|
||||||
- 'size': api/basic_json_view/size.md
|
- 'size': api/basic_json_view/size.md
|
||||||
- 'source_offset': api/basic_json_view/source_offset.md
|
- 'source_offset': api/basic_json_view/source_offset.md
|
||||||
- 'type': api/basic_json_view/type.md
|
- 'type': api/basic_json_view/type.md
|
||||||
@@ -364,6 +369,8 @@ nav:
|
|||||||
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
||||||
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
||||||
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
|
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
|
||||||
|
- 'JSON_VIEW_NO_SIMD': api/macros/json_view_no_simd.md
|
||||||
|
- 'JSON_VIEW_USE_SSSE3': api/macros/json_view_use_ssse3.md
|
||||||
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
|
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
|
||||||
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
|
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
|
||||||
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_non_intrusive.md
|
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_non_intrusive.md
|
||||||
|
|||||||
@@ -116,6 +116,13 @@ class builder
|
|||||||
frame shallow[64]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): not initialized on purpose; filled as containers open
|
frame shallow[64]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): not initialized on purpose; filled as containers open
|
||||||
std::vector<frame> deep{};
|
std::vector<frame> deep{};
|
||||||
|
|
||||||
|
/// remember an object to index after parsing (out of line, so that the
|
||||||
|
/// parse loop only has a call for it)
|
||||||
|
NLOHMANN_VIEW_NOINLINE void note_large_object(std::uint32_t idx)
|
||||||
|
{
|
||||||
|
doc.large_objects.push_back(idx);
|
||||||
|
}
|
||||||
|
|
||||||
NLOHMANN_VIEW_NOINLINE bool fail(error_code c, const unsigned char* at) noexcept
|
NLOHMANN_VIEW_NOINLINE bool fail(error_code c, const unsigned char* at) noexcept
|
||||||
{
|
{
|
||||||
m_failure.code = c;
|
m_failure.code = c;
|
||||||
@@ -503,7 +510,7 @@ class builder
|
|||||||
switch (cur()) \
|
switch (cur()) \
|
||||||
{ \
|
{ \
|
||||||
case '"': \
|
case '"': \
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!string())) { return false; } \
|
if (NLOHMANN_VIEW_UNLIKELY(!string<true>())) { return false; } \
|
||||||
goto NEXT; \
|
goto NEXT; \
|
||||||
case '{': \
|
case '{': \
|
||||||
open(value_t::object); \
|
open(value_t::object); \
|
||||||
@@ -587,7 +594,7 @@ obj_key:
|
|||||||
{
|
{
|
||||||
return fail(error_code::expected_key);
|
return fail(error_code::expected_key);
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!string()))
|
if (NLOHMANN_VIEW_UNLIKELY(!string<false>()))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -628,19 +635,27 @@ obj_next:
|
|||||||
if (enabled(TrailingCommas) && cur() == '}')
|
if (enabled(TrailingCommas) && cur() == '}')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
goto close_container;
|
goto close_object;
|
||||||
}
|
}
|
||||||
goto obj_key;
|
goto obj_key;
|
||||||
}
|
}
|
||||||
if (cur() == '}')
|
if (cur() == '}')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
goto close_container;
|
goto close_object;
|
||||||
}
|
}
|
||||||
return fail(error_code::expected_object_end);
|
return fail(error_code::expected_object_end);
|
||||||
|
|
||||||
#undef NLOHMANN_VIEW_VALUE
|
#undef NLOHMANN_VIEW_VALUE
|
||||||
|
|
||||||
|
close_object:
|
||||||
|
// a large object gets a hash index (objects only, so that closing
|
||||||
|
// an array pays nothing for this)
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
|
||||||
|
{
|
||||||
|
cold.note_large_object(cur_idx);
|
||||||
|
}
|
||||||
|
|
||||||
close_container:
|
close_container:
|
||||||
close();
|
close();
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(depth == 0))
|
if (NLOHMANN_VIEW_UNLIKELY(depth == 0))
|
||||||
@@ -690,7 +705,7 @@ root_done:
|
|||||||
switch (cur())
|
switch (cur())
|
||||||
{
|
{
|
||||||
case '"':
|
case '"':
|
||||||
return string();
|
return string<true>();
|
||||||
case 't':
|
case 't':
|
||||||
return literal("true", 4, value_t::boolean, node_flags::is_true);
|
return literal("true", 4, value_t::boolean, node_flags::is_true);
|
||||||
case 'f':
|
case 'f':
|
||||||
@@ -977,12 +992,13 @@ indent_done:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a string (value or key) at p
|
/// a string at p: a value (Value) or a key
|
||||||
|
template<bool Value>
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool string()
|
NLOHMANN_VIEW_ALWAYS_INLINE bool string()
|
||||||
{
|
{
|
||||||
++p; // opening quote
|
++p; // opening quote
|
||||||
const unsigned char* const s = p;
|
const unsigned char* const s = p;
|
||||||
p = scan_string_run(p, e);
|
p = scan_string_run<Value>(p, e);
|
||||||
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
||||||
{
|
{
|
||||||
emit(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
emit(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||||
|
|||||||
@@ -0,0 +1,317 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm> // sort, stable_sort
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <string> // string
|
||||||
|
#include <utility> // move, pair
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
namespace view
|
||||||
|
{
|
||||||
|
|
||||||
|
// Equality of views, and of views with basic_json values, with the semantics
|
||||||
|
// of basic_json's operator== applied to the values parse() would produce:
|
||||||
|
// numbers compare by value across their types, an object is compared by its
|
||||||
|
// members with duplicate keys resolved as parse() resolves them (the last
|
||||||
|
// value, at the position of the first occurrence), and in document order if
|
||||||
|
// the object type keeps an order (ordered_json), by key otherwise.
|
||||||
|
|
||||||
|
/// one side of a comparison: a view
|
||||||
|
template<typename BasicJsonType, typename View>
|
||||||
|
class view_side
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using string_view_t = typename View::string_view_t;
|
||||||
|
|
||||||
|
explicit view_side(const View& v) noexcept
|
||||||
|
: m_view(v)
|
||||||
|
{}
|
||||||
|
|
||||||
|
value_t type() const noexcept
|
||||||
|
{
|
||||||
|
return m_view.type();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const noexcept
|
||||||
|
{
|
||||||
|
return m_view.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
string_view_t string() const
|
||||||
|
{
|
||||||
|
return m_view.get_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a number, boolean, or null as a basic_json value (no allocation)
|
||||||
|
BasicJsonType scalar() const
|
||||||
|
{
|
||||||
|
switch (m_view.type())
|
||||||
|
{
|
||||||
|
case value_t::number_integer:
|
||||||
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
|
||||||
|
case value_t::number_float:
|
||||||
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
|
||||||
|
case value_t::boolean:
|
||||||
|
return BasicJsonType(m_view.template get<bool>());
|
||||||
|
case value_t::null:
|
||||||
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
case value_t::string:
|
||||||
|
case value_t::binary:
|
||||||
|
case value_t::discarded:
|
||||||
|
default:
|
||||||
|
return BasicJsonType(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void elements(std::vector<view_side>& out) const
|
||||||
|
{
|
||||||
|
out.reserve(m_view.size());
|
||||||
|
for (const View e : m_view)
|
||||||
|
{
|
||||||
|
out.emplace_back(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the members as parse() keeps them: one per key, the last value at the
|
||||||
|
/// position of the first occurrence; in that order, or sorted by key
|
||||||
|
void members(std::vector<std::pair<string_view_t, view_side>>& out, bool ordered) const
|
||||||
|
{
|
||||||
|
struct member
|
||||||
|
{
|
||||||
|
string_view_t key;
|
||||||
|
View value;
|
||||||
|
std::size_t position;
|
||||||
|
};
|
||||||
|
std::vector<member> all;
|
||||||
|
all.reserve(m_view.size());
|
||||||
|
std::size_t position = 0;
|
||||||
|
for (auto it = m_view.begin(); it != m_view.end(); ++it)
|
||||||
|
{
|
||||||
|
all.push_back(member{it.key(), it.value(), position++});
|
||||||
|
}
|
||||||
|
std::stable_sort(all.begin(), all.end(), [](const member & a, const member & b)
|
||||||
|
{
|
||||||
|
return a.key < b.key;
|
||||||
|
});
|
||||||
|
std::vector<member> unique;
|
||||||
|
unique.reserve(all.size());
|
||||||
|
for (std::size_t i = 0; i < all.size();)
|
||||||
|
{
|
||||||
|
std::size_t last = i;
|
||||||
|
while (last + 1 < all.size() && all[last + 1].key == all[i].key)
|
||||||
|
{
|
||||||
|
++last;
|
||||||
|
}
|
||||||
|
unique.push_back(member{all[i].key, all[last].value, all[i].position});
|
||||||
|
i = last + 1;
|
||||||
|
}
|
||||||
|
if (ordered)
|
||||||
|
{
|
||||||
|
std::sort(unique.begin(), unique.end(), [](const member & a, const member & b)
|
||||||
|
{
|
||||||
|
return a.position < b.position;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out.reserve(unique.size());
|
||||||
|
for (const member& m : unique)
|
||||||
|
{
|
||||||
|
out.emplace_back(m.key, view_side(m.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
View m_view;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// the other side of a comparison: a basic_json value
|
||||||
|
template<typename BasicJsonType, typename StringView>
|
||||||
|
class json_side
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using string_view_t = StringView;
|
||||||
|
|
||||||
|
explicit json_side(const BasicJsonType& j) noexcept
|
||||||
|
: m_json(&j)
|
||||||
|
{}
|
||||||
|
|
||||||
|
value_t type() const noexcept
|
||||||
|
{
|
||||||
|
return m_json->type();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const noexcept
|
||||||
|
{
|
||||||
|
return m_json->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
string_view_t string() const
|
||||||
|
{
|
||||||
|
const auto& s = m_json->template get_ref<const typename BasicJsonType::string_t&>();
|
||||||
|
return string_view_t(s.data(), s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicJsonType scalar() const
|
||||||
|
{
|
||||||
|
return *m_json;
|
||||||
|
}
|
||||||
|
|
||||||
|
void elements(std::vector<json_side>& out) const
|
||||||
|
{
|
||||||
|
out.reserve(m_json->size());
|
||||||
|
for (const auto& e : *m_json)
|
||||||
|
{
|
||||||
|
out.emplace_back(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void members(std::vector<std::pair<string_view_t, json_side>>& out, bool ordered) const
|
||||||
|
{
|
||||||
|
out.reserve(m_json->size());
|
||||||
|
for (auto it = m_json->cbegin(); it != m_json->cend(); ++it)
|
||||||
|
{
|
||||||
|
out.emplace_back(string_view_t(it.key().data(), it.key().size()), json_side(it.value()));
|
||||||
|
}
|
||||||
|
if (!ordered)
|
||||||
|
{
|
||||||
|
std::sort(out.begin(), out.end(), [](const std::pair<string_view_t, json_side>& a, const std::pair<string_view_t, json_side>& b)
|
||||||
|
{
|
||||||
|
return a.first < b.first;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const BasicJsonType* m_json;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// whether two sides are equal; iterative, so that the nesting depth is
|
||||||
|
/// limited by memory only
|
||||||
|
template<typename BasicJsonType, typename A, typename B>
|
||||||
|
bool equal(const A& a0, const B& b0)
|
||||||
|
{
|
||||||
|
using string_view_t = typename A::string_view_t;
|
||||||
|
const bool ordered = is_ordered_map<typename BasicJsonType::object_t>::value;
|
||||||
|
|
||||||
|
struct frame
|
||||||
|
{
|
||||||
|
std::vector<A> elements_a{};
|
||||||
|
std::vector<B> elements_b{};
|
||||||
|
std::vector<std::pair<string_view_t, A>> members_a{};
|
||||||
|
std::vector<std::pair<string_view_t, B>> members_b{};
|
||||||
|
bool object = false;
|
||||||
|
std::size_t next = 0;
|
||||||
|
};
|
||||||
|
std::vector<frame> stack;
|
||||||
|
A a = a0;
|
||||||
|
B b = b0;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const value_t ta = a.type();
|
||||||
|
const value_t tb = b.type();
|
||||||
|
const bool numbers = (ta == value_t::number_integer || ta == value_t::number_unsigned || ta == value_t::number_float)
|
||||||
|
&& (tb == value_t::number_integer || tb == value_t::number_unsigned || tb == value_t::number_float);
|
||||||
|
if (ta == value_t::discarded || tb == value_t::discarded)
|
||||||
|
{
|
||||||
|
// basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)
|
||||||
|
if (ta != tb || !(BasicJsonType(value_t::discarded) == BasicJsonType(value_t::discarded)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!numbers && ta != tb)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ta == value_t::string)
|
||||||
|
{
|
||||||
|
if (!(a.string() == b.string()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ta == value_t::array || ta == value_t::object)
|
||||||
|
{
|
||||||
|
if (a.size() != b.size() && ta == value_t::array)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
frame f;
|
||||||
|
f.object = ta == value_t::object;
|
||||||
|
if (f.object)
|
||||||
|
{
|
||||||
|
a.members(f.members_a, ordered);
|
||||||
|
b.members(f.members_b, ordered);
|
||||||
|
if (f.members_a.size() != f.members_b.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a.elements(f.elements_a);
|
||||||
|
b.elements(f.elements_b);
|
||||||
|
}
|
||||||
|
stack.push_back(std::move(f));
|
||||||
|
}
|
||||||
|
else if (!(a.scalar() == b.scalar())) // numbers (also of different types), null, boolean
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the next pair of values
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (stack.empty())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
frame& f = stack.back();
|
||||||
|
const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size();
|
||||||
|
if (f.next == count)
|
||||||
|
{
|
||||||
|
stack.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (f.object)
|
||||||
|
{
|
||||||
|
if (!(f.members_a[f.next].first == f.members_b[f.next].first))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
a = f.members_a[f.next].second;
|
||||||
|
b = f.members_b[f.next].second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a = f.elements_a[f.next];
|
||||||
|
b = f.elements_b[f.next];
|
||||||
|
}
|
||||||
|
++f.next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace view
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -10,9 +10,11 @@
|
|||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint32_t
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <new> // operator new, placement new
|
#include <new> // operator new, placement new
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
@@ -37,6 +39,17 @@ struct document_data
|
|||||||
std::size_t inline_cap = 0;
|
std::size_t inline_cap = 0;
|
||||||
std::string arena{}; ///< decoded strings that contained escapes // NOLINT(readability-redundant-member-init)
|
std::string arena{}; ///< decoded strings that contained escapes // NOLINT(readability-redundant-member-init)
|
||||||
std::string owned{}; ///< owned copy of the input, if any // NOLINT(readability-redundant-member-init)
|
std::string owned{}; ///< owned copy of the input, if any // NOLINT(readability-redundant-member-init)
|
||||||
|
|
||||||
|
// hash indexes of large objects (see object_index.hpp)
|
||||||
|
static constexpr std::uint32_t index_min_members = 128;
|
||||||
|
struct object_index
|
||||||
|
{
|
||||||
|
std::size_t start; ///< first slot in index_slots
|
||||||
|
std::uint32_t mask; ///< slot count - 1 (a power of two minus one)
|
||||||
|
};
|
||||||
|
std::vector<object_index> indexes{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
std::vector<std::uint32_t> index_slots{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
std::vector<std::uint32_t> large_objects{}; ///< positions of the objects to index (noted while parsing) // NOLINT(readability-redundant-member-init)
|
||||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena (indexed by flags & node_flags::storage)
|
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena (indexed by flags & node_flags::storage)
|
||||||
bool discarded = true;
|
bool discarded = true;
|
||||||
|
|
||||||
@@ -64,7 +77,7 @@ struct document_data
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document_data() noexcept = default;
|
document_data() = default;
|
||||||
document_data(const document_data&) = delete;
|
document_data(const document_data&) = delete;
|
||||||
document_data(document_data&&) = delete;
|
document_data(document_data&&) = delete;
|
||||||
document_data& operator=(const document_data&) = delete;
|
document_data& operator=(const document_data&) = delete;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <nlohmann/detail/view/document_data.hpp>
|
#include <nlohmann/detail/view/document_data.hpp>
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
|
#include <nlohmann/detail/view/object_index.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -85,6 +86,10 @@ class short_key
|
|||||||
/// nullptr; most keys are rejected by their length, from the index alone
|
/// nullptr; most keys are rejected by their length, from the index alone
|
||||||
inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(object->extra != 0))
|
||||||
|
{
|
||||||
|
return find_indexed(d, object, key, n); // a large object
|
||||||
|
}
|
||||||
const node* const end = document_data::child_end(object);
|
const node* const end = document_data::child_end(object);
|
||||||
const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
if (NLOHMANN_VIEW_LIKELY(n <= 16))
|
if (NLOHMANN_VIEW_LIKELY(n <= 16))
|
||||||
|
|||||||
@@ -19,3 +19,8 @@
|
|||||||
#undef NLOHMANN_VIEW_THROW
|
#undef NLOHMANN_VIEW_THROW
|
||||||
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
|
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
#undef NLOHMANN_VIEW_REPEAT16
|
#undef NLOHMANN_VIEW_REPEAT16
|
||||||
|
#undef NLOHMANN_VIEW_NEON
|
||||||
|
#undef NLOHMANN_VIEW_SSE2
|
||||||
|
#undef NLOHMANN_VIEW_SSSE3
|
||||||
|
#undef NLOHMANN_VIEW_VECTOR
|
||||||
|
#undef NLOHMANN_VIEW_VECTOR_UTF8
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
++n;
|
++n;
|
||||||
break;
|
break;
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
sax.number_float(float_value<typename BasicJsonType::number_float_t>(d.str(*n), *n), no_token);
|
sax.number_float(float_value<typename BasicJsonType::number_float_t>(d, *n), no_token);
|
||||||
++n;
|
++n;
|
||||||
break;
|
break;
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
|
|||||||
@@ -9,11 +9,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // int64_t, uint64_t
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
|
#include <type_traits> // integral_constant
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <nlohmann/detail/view/document_data.hpp>
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
|
#include <nlohmann/detail/view/scan.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -62,6 +66,96 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
|||||||
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the digits of a float token with at most 19 digits, from its layout
|
||||||
|
|
||||||
|
The digit layout recorded while parsing says where the integer digits, the
|
||||||
|
fraction digits, and the exponent are, so the digits are read eight at a
|
||||||
|
time without scanning.
|
||||||
|
|
||||||
|
@param[in] p first character of the token
|
||||||
|
@param[in] e end of the token
|
||||||
|
@param[in] limit end of the readable memory (the source text)
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
|
||||||
|
{
|
||||||
|
const bool negative = *p == '-';
|
||||||
|
p += negative ? 1 : 0;
|
||||||
|
std::uint64_t w = parse_upto19(p, int_digits, limit);
|
||||||
|
p += int_digits;
|
||||||
|
std::int64_t q = 0;
|
||||||
|
if (frac_digits != 0)
|
||||||
|
{
|
||||||
|
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
|
||||||
|
p += 1 + frac_digits;
|
||||||
|
q = -static_cast<std::int64_t>(frac_digits);
|
||||||
|
}
|
||||||
|
if (p != e)
|
||||||
|
{
|
||||||
|
// [eE][+-]digits; huge exponents saturate (the parser rejected overflow)
|
||||||
|
++p;
|
||||||
|
const bool exp_negative = *p == '-';
|
||||||
|
p += (*p == '-' || *p == '+') ? 1 : 0;
|
||||||
|
std::int64_t exp_value = 0;
|
||||||
|
for (; p != e; ++p)
|
||||||
|
{
|
||||||
|
if (exp_value < 0x10000000)
|
||||||
|
{
|
||||||
|
exp_value = (exp_value * 10) + (*p - '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q += exp_negative ? -exp_value : exp_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float_significand d;
|
||||||
|
d.w = w;
|
||||||
|
d.exponent = q;
|
||||||
|
d.negative = negative;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the value of a float token with at most 19 digits, from its layout
|
||||||
|
|
||||||
|
The result is correctly rounded by the lexer's conversion
|
||||||
|
(detail::decimal_to_float(): Clinger's fast path where both operands are
|
||||||
|
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
|
||||||
|
digits), so it is the value parse() produces.
|
||||||
|
*/
|
||||||
|
template<typename FloatType>
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
|
||||||
|
{
|
||||||
|
return decimal_to_float<FloatType>(layout_decimal(p, e, int_digits, frac_digits, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of the float token of a node, as parse() converts it; floats and
|
||||||
|
/// doubles with at most 19 digits are converted from the digit layout
|
||||||
|
template<typename FloatType>
|
||||||
|
FloatType float_value(const document_data& d, const node& n)
|
||||||
|
{
|
||||||
|
return float_value<FloatType>(d, n, std::integral_constant<bool, has_native_float_format<FloatType>::value> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FloatType>
|
||||||
|
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
|
||||||
|
{
|
||||||
|
const unsigned int_digits = n.extra & 0xFFu;
|
||||||
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
|
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
|
||||||
|
{
|
||||||
|
// (a float token not written by an edit is in the text)
|
||||||
|
const auto* const first = reinterpret_cast<const unsigned char*>(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
return layout_float<FloatType>(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(d.src + d.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
}
|
||||||
|
return float_value<FloatType>(d.str(n), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FloatType>
|
||||||
|
FloatType float_value(const document_data& d, const node& n, std::false_type /*other*/)
|
||||||
|
{
|
||||||
|
return float_value<FloatType>(d.str(n), n);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace view
|
} // namespace view
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
|
#include <cstring> // memcmp
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <nlohmann/detail/view/document_data.hpp>
|
||||||
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
|
|
||||||
|
// Hash indexes of large objects, so that a lookup does not compare thousands
|
||||||
|
// of keys (as Boost.JSON switches from a linear search to a hash table for
|
||||||
|
// large objects). An object with document_data::index_min_members members or
|
||||||
|
// more gets an open-addressing table after parsing; its node stores the
|
||||||
|
// number of the table (1-based) in `extra`. A slot holds the offset of a key
|
||||||
|
// node from its object node (0: empty). Of duplicate keys, the first is kept,
|
||||||
|
// as for the linear search.
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
namespace view
|
||||||
|
{
|
||||||
|
|
||||||
|
/// hash of a key: its bytes, eight at a time, in a fixed byte order
|
||||||
|
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
||||||
|
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
while (n >= 8)
|
||||||
|
{
|
||||||
|
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||||
|
h ^= h >> 29u;
|
||||||
|
p += 8;
|
||||||
|
n -= 8;
|
||||||
|
}
|
||||||
|
std::uint64_t w = 0;
|
||||||
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
|
||||||
|
}
|
||||||
|
h = (h ^ w) * 0x94D049BB133111EBu;
|
||||||
|
return h ^ (h >> 31u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build the table of a large object
|
||||||
|
inline void build_object_index(document_data& d, node* obj)
|
||||||
|
{
|
||||||
|
if (d.indexes.size() >= 0xFFFFu)
|
||||||
|
{
|
||||||
|
return; // LCOV_EXCL_LINE (the number must fit `extra`; more large objects are searched linearly)
|
||||||
|
}
|
||||||
|
std::size_t cap = 16;
|
||||||
|
while (cap < 2 * static_cast<std::size_t>(obj->len))
|
||||||
|
{
|
||||||
|
cap *= 2;
|
||||||
|
}
|
||||||
|
const std::size_t start = d.index_slots.size();
|
||||||
|
d.index_slots.resize(start + cap, 0);
|
||||||
|
std::uint32_t* const slots = d.index_slots.data() + start;
|
||||||
|
const std::size_t mask = cap - 1;
|
||||||
|
for (const node* k = document_data::first_child(obj), *end = document_data::child_end(obj); k != end; k = document_data::after(k + 1))
|
||||||
|
{
|
||||||
|
const char* const key = d.str(*k);
|
||||||
|
const std::uint64_t hash = key_hash(key, k->len); // (a cast of the call would be useless where std::uint64_t is std::size_t)
|
||||||
|
std::size_t i = static_cast<std::size_t>(hash) & mask;
|
||||||
|
bool duplicate = false;
|
||||||
|
while (slots[i] != 0)
|
||||||
|
{
|
||||||
|
const node* const other = obj + slots[i];
|
||||||
|
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0))
|
||||||
|
{
|
||||||
|
duplicate = true; // keep the first
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i = (i + 1) & mask;
|
||||||
|
}
|
||||||
|
if (!duplicate)
|
||||||
|
{
|
||||||
|
slots[i] = static_cast<std::uint32_t>(k - obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.indexes.push_back(document_data::object_index{start, static_cast<std::uint32_t>(mask)});
|
||||||
|
obj->extra = static_cast<std::uint16_t>(d.indexes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build the tables of the large objects the parser noted
|
||||||
|
inline void build_object_indexes(document_data& d)
|
||||||
|
{
|
||||||
|
for (const std::uint32_t i : d.large_objects)
|
||||||
|
{
|
||||||
|
build_object_index(d, d.tape + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the key node of the first member with this key of an indexed object, or
|
||||||
|
/// nullptr
|
||||||
|
inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
||||||
|
const std::uint32_t* const slots = d.index_slots.data() + ix.start;
|
||||||
|
const std::uint64_t hash = key_hash(key, n); // (a cast of the call would be useless where std::uint64_t is std::size_t)
|
||||||
|
std::size_t i = static_cast<std::size_t>(hash) & ix.mask;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const std::uint32_t s = slots[i];
|
||||||
|
if (s == 0)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const node* const k = obj + s;
|
||||||
|
if (k->len == n && (n == 0 || std::memcmp(d.str(*k), key, n) == 0))
|
||||||
|
{
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
i = (i + 1) & ix.mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace view
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
|
#include <nlohmann/detail/view/simd.hpp>
|
||||||
|
|
||||||
// Scanning primitives of the view's parser. The unrolled checks at fixed
|
// Scanning primitives of the view's parser. The unrolled checks at fixed
|
||||||
// offsets follow yyjson (https://github.com/ibireme/yyjson, MIT license): the
|
// offsets follow yyjson (https://github.com/ibireme/yyjson, MIT license): the
|
||||||
@@ -62,9 +63,12 @@ NLOHMANN_VIEW_ALWAYS_INLINE std::uint16_t load16(const unsigned char* p) noexcep
|
|||||||
|
|
||||||
/// Advance over plain string bytes and well-formed UTF-8. Stops at a quote,
|
/// Advance over plain string bytes and well-formed UTF-8. Stops at a quote,
|
||||||
/// a backslash, a control character, ill-formed UTF-8, or the end. The first
|
/// a backslash, a control character, ill-formed UTF-8, or the end. The first
|
||||||
/// 16 bytes are checked one by one, so that the position advances by
|
/// bytes are checked one by one, so that the position advances by constants
|
||||||
/// constants in predicted branches (most strings are short); longer runs
|
/// in predicted branches: 16 for keys, whose lengths repeat from record to
|
||||||
/// continue eight bytes at a time.
|
/// record, and 8 for string values (Value) where a vector loop follows, as
|
||||||
|
/// their lengths vary more. Longer runs continue 16 bytes at a time with NEON
|
||||||
|
/// or SSE2, else eight bytes at a time.
|
||||||
|
template<bool Value = false>
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned char* p, const unsigned char* e) noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
{
|
{
|
||||||
const std::uint8_t* plain = string_plain();
|
const std::uint8_t* plain = string_plain();
|
||||||
@@ -73,9 +77,23 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned
|
|||||||
if (e - p >= 16)
|
if (e - p >= 16)
|
||||||
{
|
{
|
||||||
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; }
|
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; }
|
||||||
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
NLOHMANN_VIEW_STEP(0) NLOHMANN_VIEW_STEP(1) NLOHMANN_VIEW_STEP(2) NLOHMANN_VIEW_STEP(3)
|
||||||
|
NLOHMANN_VIEW_STEP(4) NLOHMANN_VIEW_STEP(5) NLOHMANN_VIEW_STEP(6) NLOHMANN_VIEW_STEP(7)
|
||||||
|
if (!Value || !NLOHMANN_VIEW_VECTOR)
|
||||||
|
{
|
||||||
|
NLOHMANN_VIEW_STEP(8) NLOHMANN_VIEW_STEP(9) NLOHMANN_VIEW_STEP(10) NLOHMANN_VIEW_STEP(11)
|
||||||
|
NLOHMANN_VIEW_STEP(12) NLOHMANN_VIEW_STEP(13) NLOHMANN_VIEW_STEP(14) NLOHMANN_VIEW_STEP(15)
|
||||||
|
p += 8;
|
||||||
|
}
|
||||||
#undef NLOHMANN_VIEW_STEP
|
#undef NLOHMANN_VIEW_STEP
|
||||||
p += 16;
|
p += 8;
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
p = vector_plain_run(p, e);
|
||||||
|
if (p != e && plain[*p] == 0)
|
||||||
|
{
|
||||||
|
goto stop;
|
||||||
|
}
|
||||||
|
#else
|
||||||
while (e - p >= 8)
|
while (e - p >= 8)
|
||||||
{
|
{
|
||||||
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
||||||
@@ -86,6 +104,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned
|
|||||||
}
|
}
|
||||||
p += 8;
|
p += 8;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
while (p != e && plain[*p] != 0)
|
while (p != e && plain[*p] != 0)
|
||||||
@@ -101,6 +120,10 @@ stop:
|
|||||||
{
|
{
|
||||||
return p; // quote, backslash, or control character
|
return p; // quote, backslash, or control character
|
||||||
}
|
}
|
||||||
|
#if NLOHMANN_VIEW_VECTOR_UTF8
|
||||||
|
// non-ASCII: the vector check, out of line
|
||||||
|
return scan_string_vector(p, e, plain);
|
||||||
|
#else
|
||||||
// non-ASCII: a run of well-formed sequences (the library's check, so
|
// non-ASCII: a run of well-formed sequences (the library's check, so
|
||||||
// that exactly what json::parse accepts is accepted)
|
// that exactly what json::parse accepts is accepted)
|
||||||
do
|
do
|
||||||
@@ -113,6 +136,7 @@ stop:
|
|||||||
p += n;
|
p += n;
|
||||||
}
|
}
|
||||||
while (p != e && *p >= 0x80);
|
while (p != e && *p >= 0x80);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,419 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm> // max
|
||||||
|
#include <array> // array
|
||||||
|
#include <cmath> // isfinite
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
|
#include <cstring> // memcpy, memset
|
||||||
|
#include <limits> // numeric_limits
|
||||||
|
#include <type_traits> // integral_constant
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <nlohmann/detail/view/document_data.hpp>
|
||||||
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
|
#include <nlohmann/detail/view/number.hpp>
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
namespace view
|
||||||
|
{
|
||||||
|
|
||||||
|
/// append-only output buffer: writes through a raw pointer into a string that
|
||||||
|
/// is resized ahead, and trimmed by finish()
|
||||||
|
template<typename StringType>
|
||||||
|
class output_buffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
output_buffer(StringType& out, std::size_t estimate)
|
||||||
|
: m_out(sized(out, estimate))
|
||||||
|
, m_pos(&m_out[0])
|
||||||
|
, m_end(m_pos + m_out.size())
|
||||||
|
{}
|
||||||
|
|
||||||
|
void finish()
|
||||||
|
{
|
||||||
|
m_out.resize(static_cast<std::size_t>(m_pos - m_out.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void reserve(std::size_t n)
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_end - m_pos) < n))
|
||||||
|
{
|
||||||
|
grow(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void put(char c)
|
||||||
|
{
|
||||||
|
reserve(1);
|
||||||
|
*m_pos++ = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void put(const char* s, std::size_t n)
|
||||||
|
{
|
||||||
|
reserve(n);
|
||||||
|
std::memcpy(m_pos, s, n);
|
||||||
|
m_pos += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void put_repeated(char c, std::size_t n)
|
||||||
|
{
|
||||||
|
reserve(n);
|
||||||
|
std::memset(m_pos, c, n);
|
||||||
|
m_pos += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static StringType& sized(StringType& out, std::size_t estimate)
|
||||||
|
{
|
||||||
|
out.resize((std::max)(estimate, static_cast<std::size_t>(64)));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
|
||||||
|
{
|
||||||
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
|
m_out.resize((std::max)(m_out.size() * 2, used + n + 256));
|
||||||
|
m_pos = &m_out[0] + used;
|
||||||
|
m_end = &m_out[0] + m_out.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringType& m_out;
|
||||||
|
char* m_pos;
|
||||||
|
char* m_end;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// how the view's dump() writes a value
|
||||||
|
struct dump_style
|
||||||
|
{
|
||||||
|
bool pretty = false; ///< indent >= 0
|
||||||
|
std::size_t indent = 0; ///< characters per level
|
||||||
|
char indent_char = ' ';
|
||||||
|
bool ensure_ascii = false;
|
||||||
|
bool source_numbers = false; ///< copy number tokens from the source
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a view's subtree as basic_json::dump() writes the value
|
||||||
|
|
||||||
|
The output of a subtree equals ordered_json::parse(text).dump() of it for
|
||||||
|
the same arguments (members in document order): strings are escaped by the
|
||||||
|
same rules, with the library's scanning kernels; floats are written with
|
||||||
|
the library's conversion; integers are copied from the source, where they
|
||||||
|
are canonical (except "-0", which parse() reads as 0). The walk is
|
||||||
|
iterative, so the nesting depth is limited by memory only.
|
||||||
|
*/
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
class view_serializer
|
||||||
|
{
|
||||||
|
using string_t = typename BasicJsonType::string_t;
|
||||||
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
|
|
||||||
|
public:
|
||||||
|
view_serializer(const document_data& d, string_t& out, std::size_t estimate, const dump_style& style)
|
||||||
|
: m_doc(d), m_out(out, estimate), m_style(style)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void dump(const node* root)
|
||||||
|
{
|
||||||
|
struct frame
|
||||||
|
{
|
||||||
|
const node* pos; ///< next element, or key of the next member
|
||||||
|
const node* end;
|
||||||
|
bool object;
|
||||||
|
bool first; ///< nothing written yet
|
||||||
|
};
|
||||||
|
std::vector<frame> stack;
|
||||||
|
const node* n = root;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// write the value at n
|
||||||
|
if (is_container(*n))
|
||||||
|
{
|
||||||
|
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||||
|
if (n->len == 0)
|
||||||
|
{
|
||||||
|
m_out.put(object ? "{}" : "[]", 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_out.put(object ? '{' : '[');
|
||||||
|
stack.push_back(frame{document_data::first_child(n), document_data::child_end(n), object, true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_scalar(*n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to the next value: close finished containers, then separate
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (stack.empty())
|
||||||
|
{
|
||||||
|
m_out.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frame& f = stack.back();
|
||||||
|
if (f.pos == f.end)
|
||||||
|
{
|
||||||
|
const bool object = f.object;
|
||||||
|
stack.pop_back();
|
||||||
|
newline(stack.size());
|
||||||
|
m_out.put(object ? '}' : ']');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!f.first)
|
||||||
|
{
|
||||||
|
m_out.put(',');
|
||||||
|
}
|
||||||
|
f.first = false;
|
||||||
|
newline(stack.size());
|
||||||
|
if (f.object)
|
||||||
|
{
|
||||||
|
write_string(*f.pos);
|
||||||
|
if (m_style.pretty)
|
||||||
|
{
|
||||||
|
m_out.put(": ", 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_out.put(':');
|
||||||
|
}
|
||||||
|
n = f.pos + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
n = f.pos;
|
||||||
|
}
|
||||||
|
f.pos = document_data::after(n);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void newline(std::size_t level)
|
||||||
|
{
|
||||||
|
if (m_style.pretty)
|
||||||
|
{
|
||||||
|
m_out.put('\n');
|
||||||
|
m_out.put_repeated(m_style.indent_char, level * m_style.indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_scalar(const node& n)
|
||||||
|
{
|
||||||
|
switch (static_cast<value_t>(n.kind))
|
||||||
|
{
|
||||||
|
case value_t::null:
|
||||||
|
m_out.put("null", 4);
|
||||||
|
break;
|
||||||
|
case value_t::boolean:
|
||||||
|
if ((n.flags & node_flags::is_true) != 0)
|
||||||
|
{
|
||||||
|
m_out.put("true", 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_out.put("false", 5);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_t::string:
|
||||||
|
write_string(n);
|
||||||
|
break;
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
{
|
||||||
|
const char* const token = m_doc.str(n);
|
||||||
|
const std::uint32_t len = number_length(n);
|
||||||
|
if (!m_style.source_numbers && len == 2 && token[0] == '-' && token[1] == '0')
|
||||||
|
{
|
||||||
|
m_out.put('0'); // parse() reads -0 as the integer 0
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_out.put(token, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case value_t::number_float:
|
||||||
|
if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
m_out.put(m_doc.str(n), n.len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_float(float_value<number_float_t>(m_doc, n));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
||||||
|
case value_t::array: // LCOV_EXCL_LINE
|
||||||
|
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
|
||||||
|
case value_t::discarded: // LCOV_EXCL_LINE
|
||||||
|
default: // LCOV_EXCL_LINE
|
||||||
|
break; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// as serializer::dump_float()
|
||||||
|
void write_float(number_float_t x)
|
||||||
|
{
|
||||||
|
if (!std::isfinite(x))
|
||||||
|
{
|
||||||
|
m_out.put("null", 4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
write_float(x, std::integral_constant < bool,
|
||||||
|
(std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|
||||||
|
|| (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024) > {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
||||||
|
{
|
||||||
|
std::array<char, 64> buf{};
|
||||||
|
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
||||||
|
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
||||||
|
{
|
||||||
|
// other types (e.g. long double) are rare: the library writes them
|
||||||
|
const string_t s = BasicJsonType(x).dump();
|
||||||
|
m_out.put(s.data(), s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_string(const node& n)
|
||||||
|
{
|
||||||
|
const char* const s = m_doc.str(n);
|
||||||
|
m_out.put('"');
|
||||||
|
if ((n.flags & node_flags::escaped) == 0 && !m_style.ensure_ascii)
|
||||||
|
{
|
||||||
|
// a string without escape sequences has nothing to escape
|
||||||
|
m_out.put(s, n.len);
|
||||||
|
}
|
||||||
|
else if (m_style.ensure_ascii)
|
||||||
|
{
|
||||||
|
write_escaped<true>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_escaped<false>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
}
|
||||||
|
m_out.put('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// as serializer::dump_escaped() for valid UTF-8 (the view has no other)
|
||||||
|
template<bool EnsureAscii>
|
||||||
|
void write_escaped(const unsigned char* s, std::size_t n)
|
||||||
|
{
|
||||||
|
std::size_t i = 0;
|
||||||
|
while (i < n)
|
||||||
|
{
|
||||||
|
std::size_t run = 0;
|
||||||
|
if (!EnsureAscii)
|
||||||
|
{
|
||||||
|
run = string_bulk_run(s + i, n - i);
|
||||||
|
}
|
||||||
|
else if (is_ascii_copyable(s[i]))
|
||||||
|
{
|
||||||
|
run = find_ascii_copyable_run(s + i, n - i);
|
||||||
|
}
|
||||||
|
if (run != 0)
|
||||||
|
{
|
||||||
|
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
i += run;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::uint32_t codepoint = s[i];
|
||||||
|
std::size_t len = 1;
|
||||||
|
if (codepoint >= 0xC0)
|
||||||
|
{
|
||||||
|
len = 2;
|
||||||
|
if (codepoint >= 0xE0)
|
||||||
|
{
|
||||||
|
len = codepoint >= 0xF0 ? 4 : 3;
|
||||||
|
}
|
||||||
|
codepoint &= 0xFFu >> (len + 1);
|
||||||
|
for (std::size_t k = 1; k < len; ++k)
|
||||||
|
{
|
||||||
|
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_codepoint<EnsureAscii>(codepoint, s + i, len);
|
||||||
|
i += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool EnsureAscii>
|
||||||
|
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
|
||||||
|
{
|
||||||
|
switch (codepoint)
|
||||||
|
{
|
||||||
|
case 0x08:
|
||||||
|
m_out.put("\\b", 2);
|
||||||
|
return;
|
||||||
|
case 0x09:
|
||||||
|
m_out.put("\\t", 2);
|
||||||
|
return;
|
||||||
|
case 0x0A:
|
||||||
|
m_out.put("\\n", 2);
|
||||||
|
return;
|
||||||
|
case 0x0C:
|
||||||
|
m_out.put("\\f", 2);
|
||||||
|
return;
|
||||||
|
case 0x0D:
|
||||||
|
m_out.put("\\r", 2);
|
||||||
|
return;
|
||||||
|
case 0x22:
|
||||||
|
m_out.put("\\\"", 2);
|
||||||
|
return;
|
||||||
|
case 0x5C:
|
||||||
|
m_out.put("\\\\", 2);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F))
|
||||||
|
{
|
||||||
|
if (codepoint <= 0xFFFF)
|
||||||
|
{
|
||||||
|
write_u_escape(codepoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_u_escape(0xD7C0u + (codepoint >> 10u));
|
||||||
|
write_u_escape(0xDC00u + (codepoint & 0x3FFu));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_out.put(reinterpret_cast<const char*>(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs)
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_u_escape(std::uint32_t u)
|
||||||
|
{
|
||||||
|
static constexpr const char* hex = "0123456789abcdef";
|
||||||
|
const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
|
||||||
|
m_out.put(e.data(), e.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
const document_data& m_doc;
|
||||||
|
output_buffer<string_t> m_out;
|
||||||
|
const dump_style m_style;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace view
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-FileCopyrightText: 2018-2025 The simdjson authors <https://github.com/simdjson/simdjson>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array> // array
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint8_t, uint64_t
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
|
|
||||||
|
// Vector code for long runs of string bytes. NEON (AArch64) and SSE2 (x86-64)
|
||||||
|
// belong to the baseline instruction sets and are used by default. The vector
|
||||||
|
// UTF-8 check needs NEON, or SSSE3 if JSON_VIEW_USE_SSSE3 is defined: SSSE3 is
|
||||||
|
// not part of x86-64, so it must not depend on the flags of a translation unit
|
||||||
|
// (two translation units with different flags would have different
|
||||||
|
// definitions of the same inline functions). JSON_VIEW_NO_SIMD selects the
|
||||||
|
// portable code.
|
||||||
|
#if !defined(JSON_VIEW_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
|
#include <arm_neon.h>
|
||||||
|
#define NLOHMANN_VIEW_NEON 1
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_VIEW_NEON 0
|
||||||
|
#endif
|
||||||
|
#if !defined(JSON_VIEW_NO_SIMD) && !NLOHMANN_VIEW_NEON && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#define NLOHMANN_VIEW_SSE2 1
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_VIEW_SSE2 0
|
||||||
|
#endif
|
||||||
|
#if NLOHMANN_VIEW_SSE2 && defined(JSON_VIEW_USE_SSSE3)
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
#define NLOHMANN_VIEW_SSSE3 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_VIEW_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#endif
|
||||||
|
#define NLOHMANN_VIEW_VECTOR (NLOHMANN_VIEW_NEON || NLOHMANN_VIEW_SSE2)
|
||||||
|
#define NLOHMANN_VIEW_VECTOR_UTF8 (NLOHMANN_VIEW_NEON || NLOHMANN_VIEW_SSSE3)
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
namespace view
|
||||||
|
{
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
/*!
|
||||||
|
@brief the first byte of a string run that is a quote, a backslash, a control
|
||||||
|
character, or not ASCII, 16 bytes per step
|
||||||
|
|
||||||
|
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
|
||||||
|
the two apart). A signed compare with 0x20 finds control characters and
|
||||||
|
non-ASCII bytes at once.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
while (e - p >= 16)
|
||||||
|
{
|
||||||
|
#if NLOHMANN_VIEW_NEON
|
||||||
|
const uint8x16_t in = vld1q_u8(p);
|
||||||
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||||
|
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||||
|
// one nibble per byte (the usual NEON replacement of x86's movemask, see
|
||||||
|
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
|
||||||
|
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||||
|
if (bits != 0)
|
||||||
|
{
|
||||||
|
return p + (count_trailing_zeros(bits) >> 2u);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p)));
|
||||||
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||||
|
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||||
|
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||||
|
if (bits != 0)
|
||||||
|
{
|
||||||
|
return p + count_trailing_zeros(bits);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
p += 16;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR_UTF8
|
||||||
|
/// Tables of the UTF-8 check of J. Keiser and D. Lemire, "Validating UTF-8 In
|
||||||
|
/// Less Than One Instruction Per Byte" (2021), as in simdjson ("lookup4"): each
|
||||||
|
/// maps a nibble (high and low nibble of the previous byte, high nibble of the
|
||||||
|
/// current byte) to the errors it allows; a byte pair is ill-formed if all
|
||||||
|
/// three have an error bit in common.
|
||||||
|
template<typename Dummy = void>
|
||||||
|
struct utf8_lookup4
|
||||||
|
{
|
||||||
|
static constexpr std::uint8_t too_short = 1u << 0u, too_long = 1u << 1u, overlong_3 = 1u << 2u, too_large = 1u << 3u;
|
||||||
|
static constexpr std::uint8_t surrogate = 1u << 4u, overlong_2 = 1u << 5u, too_large_1000 = 1u << 6u, overlong_4 = 1u << 6u;
|
||||||
|
static constexpr std::uint8_t two_conts = 1u << 7u, carry = too_short | too_long | two_conts;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_1_high;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_1_low;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_2_high;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_high =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
too_long, too_long, too_long, too_long, too_long, too_long, too_long, too_long,
|
||||||
|
two_conts, two_conts, two_conts, two_conts,
|
||||||
|
too_short | overlong_2, too_short, too_short | overlong_3 | surrogate, too_short | too_large | too_large_1000 | overlong_4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_low =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
carry | overlong_3 | overlong_2 | overlong_4, carry | overlong_2, carry, carry,
|
||||||
|
carry | too_large, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
|
||||||
|
carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
|
||||||
|
carry | too_large | too_large_1000, carry | too_large | too_large_1000 | surrogate, carry | too_large | too_large_1000, carry | too_large | too_large_1000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_2_high =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
too_short, too_short, too_short, too_short, too_short, too_short, too_short, too_short,
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large_1000 | overlong_4),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
|
||||||
|
too_short, too_short, too_short, too_short
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// the end of scan_string_vector from block, where the vector loop stopped
|
||||||
|
/// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a
|
||||||
|
/// time, from the start of a sequence that crosses into the block
|
||||||
|
inline const unsigned char* scan_string_finish(const unsigned char* p, const unsigned char* block, const unsigned char* e, const std::uint8_t* plain) noexcept
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= 3 && block - i >= p; ++i)
|
||||||
|
{
|
||||||
|
const unsigned char c = block[-i];
|
||||||
|
if (c < 0x80)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c >= 0xC0)
|
||||||
|
{
|
||||||
|
const int len = 2 + static_cast<int>(c >= 0xE0) + static_cast<int>(c >= 0xF0);
|
||||||
|
if (len > i)
|
||||||
|
{
|
||||||
|
block -= i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (p = block; p != e;)
|
||||||
|
{
|
||||||
|
if (*p < 0x80)
|
||||||
|
{
|
||||||
|
if (plain[*p] == 0)
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
++p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::size_t n = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
p += n;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the rest of a string from p (a character boundary), 16 bytes per step
|
||||||
|
|
||||||
|
The first quote, backslash, or control character is found with vector
|
||||||
|
compares, and the UTF-8 check covers the bytes up to it. Returns where the
|
||||||
|
string scan stops, like scan_string_run: before ill-formed UTF-8 and for the
|
||||||
|
last bytes of the input, the bytes are checked one sequence at a time. Out of
|
||||||
|
line, so that no constants of the check occupy registers in the parse loop.
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_NOINLINE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept
|
||||||
|
{
|
||||||
|
using lookup = utf8_lookup4<>;
|
||||||
|
const unsigned char* block = p;
|
||||||
|
#if NLOHMANN_VIEW_NEON
|
||||||
|
const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data());
|
||||||
|
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
|
||||||
|
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
|
||||||
|
uint8x16_t prev = vdupq_n_u8(0);
|
||||||
|
while (e - block >= 16)
|
||||||
|
{
|
||||||
|
const uint8x16_t in = vld1q_u8(block);
|
||||||
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
|
||||||
|
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||||
|
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||||
|
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||||
|
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
|
||||||
|
const std::uint64_t special_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||||
|
const std::uint64_t err_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vtstq_u8(err, err)), 4)), 0);
|
||||||
|
if (special_bits != 0)
|
||||||
|
{
|
||||||
|
// errors up to the special byte count (an incomplete sequence
|
||||||
|
// before a quote shows at the quote); the bytes after it do not
|
||||||
|
const unsigned k = static_cast<unsigned>(count_trailing_zeros(special_bits)) >> 2u;
|
||||||
|
const std::uint64_t upto = k == 15 ? ~std::uint64_t{0} :
|
||||||
|
(std::uint64_t{1} << (4u * (k + 1u))) - 1u;
|
||||||
|
if ((err_bits & upto) == 0)
|
||||||
|
{
|
||||||
|
return block + k;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err_bits != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = in;
|
||||||
|
block += 16;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// the same with SSSE3 (pshufb for the table lookups; nibbles from 16-bit
|
||||||
|
// shifts, as there are no byte shifts)
|
||||||
|
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data())));
|
||||||
|
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data())));
|
||||||
|
const __m128i t2h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_2_high.data())));
|
||||||
|
const __m128i nibble = _mm_set1_epi8(0x0F);
|
||||||
|
const __m128i zero = _mm_setzero_si128();
|
||||||
|
__m128i prev = zero;
|
||||||
|
while (e - block >= 16)
|
||||||
|
{
|
||||||
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block)));
|
||||||
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||||
|
_mm_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
|
||||||
|
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||||
|
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||||
|
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||||
|
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||||
|
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||||
|
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||||
|
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
|
||||||
|
const auto special_bits = static_cast<unsigned>(_mm_movemask_epi8(special));
|
||||||
|
const auto err_bits = ~static_cast<unsigned>(_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero))) & 0xFFFFu;
|
||||||
|
if (special_bits != 0)
|
||||||
|
{
|
||||||
|
const unsigned k = static_cast<unsigned>(count_trailing_zeros(static_cast<std::uint64_t>(special_bits)));
|
||||||
|
if ((err_bits & ((2u << k) - 1u)) == 0)
|
||||||
|
{
|
||||||
|
return block + k;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err_bits != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = in;
|
||||||
|
block += 16;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return scan_string_finish(p, block, e, plain);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace view
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -49,7 +49,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
|||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n))));
|
return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n))));
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d.str(n), n));
|
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n));
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
return static_cast<T>((n.flags & node_flags::is_true) != 0);
|
return static_cast<T>((n.flags & node_flags::is_true) != 0);
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
|
|||||||
@@ -25,10 +25,14 @@
|
|||||||
#define INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
#define INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint32_t
|
||||||
#include <cstring> // memcpy, strlen
|
#include <cstring> // memcpy, strlen
|
||||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||||
#include <map> // map
|
#include <map> // map
|
||||||
#include <memory> // unique_ptr
|
#include <memory> // unique_ptr
|
||||||
|
#ifndef JSON_NO_IO
|
||||||
|
#include <ostream> // ostream
|
||||||
|
#endif
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <tuple> // tuple_element, tuple_size
|
#include <tuple> // tuple_element, tuple_size
|
||||||
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
||||||
@@ -44,6 +48,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/detail/view/builder.hpp>
|
#include <nlohmann/detail/view/builder.hpp>
|
||||||
|
#include <nlohmann/detail/view/compare.hpp>
|
||||||
#include <nlohmann/detail/view/document_data.hpp>
|
#include <nlohmann/detail/view/document_data.hpp>
|
||||||
#include <nlohmann/detail/view/errors.hpp>
|
#include <nlohmann/detail/view/errors.hpp>
|
||||||
#include <nlohmann/detail/view/input.hpp>
|
#include <nlohmann/detail/view/input.hpp>
|
||||||
@@ -52,7 +57,9 @@
|
|||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/materialize.hpp>
|
#include <nlohmann/detail/view/materialize.hpp>
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
|
#include <nlohmann/detail/view/object_index.hpp>
|
||||||
#include <nlohmann/detail/view/pointer.hpp>
|
#include <nlohmann/detail/view/pointer.hpp>
|
||||||
|
#include <nlohmann/detail/view/serializer.hpp>
|
||||||
#include <nlohmann/detail/view/string_ref.hpp>
|
#include <nlohmann/detail/view/string_ref.hpp>
|
||||||
#include <nlohmann/detail/view/value.hpp>
|
#include <nlohmann/detail/view/value.hpp>
|
||||||
|
|
||||||
@@ -547,6 +554,96 @@ class basic_json_view
|
|||||||
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// serialization //
|
||||||
|
///////////////////
|
||||||
|
|
||||||
|
/// how dump() writes numbers
|
||||||
|
enum class number_format
|
||||||
|
{
|
||||||
|
/// as basic_json::dump(): integers canonically, floats with the
|
||||||
|
/// library's shortest round-trip digits ("1.5", "100.0", "1e+100")
|
||||||
|
shortest,
|
||||||
|
/// the number text of the source as it is ("1.50", "1E2", "-0", all
|
||||||
|
/// digits of a long integer)
|
||||||
|
source,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// the text of this value; with number_format::shortest, the output of
|
||||||
|
/// ordered_json::parse(text).dump() with the same arguments (members in
|
||||||
|
/// document order, all of them should a key occur more than once)
|
||||||
|
string_t dump(const int indent = -1, const char indent_char = ' ', const bool ensure_ascii = false,
|
||||||
|
const number_format numbers = number_format::shortest) const
|
||||||
|
{
|
||||||
|
string_t out;
|
||||||
|
if (m_node == nullptr)
|
||||||
|
{
|
||||||
|
out = "<discarded>"; // as basic_json::dump() of a discarded value
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
detail::view::dump_style style;
|
||||||
|
style.pretty = indent >= 0;
|
||||||
|
style.indent = indent >= 0 ? static_cast<std::size_t>(indent) : 0;
|
||||||
|
style.indent_char = indent_char;
|
||||||
|
style.ensure_ascii = ensure_ascii;
|
||||||
|
style.source_numbers = numbers == number_format::source;
|
||||||
|
// the compact text is about as long as the source text of the value
|
||||||
|
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 64;
|
||||||
|
detail::view::view_serializer<BasicJsonType>(*m_doc, out, estimate, style).dump(m_node);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef JSON_NO_IO
|
||||||
|
/// as operator<< of basic_json: a stream width > 0 is the indentation,
|
||||||
|
/// the fill character the indentation character
|
||||||
|
friend std::ostream& operator<<(std::ostream& o, const basic_json_view& v)
|
||||||
|
{
|
||||||
|
const bool pretty = o.width() > 0;
|
||||||
|
const auto indentation = pretty ? o.width() : 0;
|
||||||
|
o.width(0);
|
||||||
|
const string_t s = v.dump(pretty ? static_cast<int>(indentation) : -1, o.fill());
|
||||||
|
return o.write(s.data(), static_cast<std::streamsize>(s.size()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// comparison //
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
/// whether the values parse() would produce for two views are equal, as
|
||||||
|
/// by BasicJsonType's operator== (numbers by value, objects by their
|
||||||
|
/// members with duplicate keys resolved as parse() resolves them)
|
||||||
|
friend bool operator==(const basic_json_view& a, const basic_json_view& b)
|
||||||
|
{
|
||||||
|
return detail::view::equal<BasicJsonType>(side(a), side(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator!=(const basic_json_view& a, const basic_json_view& b)
|
||||||
|
{
|
||||||
|
return !(a == b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether the value parse() would produce for a view equals a value
|
||||||
|
friend bool operator==(const basic_json_view& a, const BasicJsonType& j)
|
||||||
|
{
|
||||||
|
return detail::view::equal<BasicJsonType>(side(a), json_side_t(j));
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator==(const BasicJsonType& j, const basic_json_view& a)
|
||||||
|
{
|
||||||
|
return a == j;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator!=(const basic_json_view& a, const BasicJsonType& j)
|
||||||
|
{
|
||||||
|
return !(a == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator!=(const BasicJsonType& j, const basic_json_view& a)
|
||||||
|
{
|
||||||
|
return !(a == j);
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
// materialize //
|
// materialize //
|
||||||
/////////////////
|
/////////////////
|
||||||
@@ -579,6 +676,30 @@ class basic_json_view
|
|||||||
: m_doc(d), m_node(n)
|
: m_doc(d), m_node(n)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
using json_side_t = detail::view::json_side<BasicJsonType, string_view_t>;
|
||||||
|
|
||||||
|
static detail::view::view_side<BasicJsonType, basic_json_view> side(const basic_json_view& v) noexcept
|
||||||
|
{
|
||||||
|
return detail::view::view_side<BasicJsonType, basic_json_view>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the number of source bytes of this value (estimated for values with
|
||||||
|
/// decoded strings)
|
||||||
|
std::size_t source_extent() const noexcept
|
||||||
|
{
|
||||||
|
const node* const next = document_data::after(m_node);
|
||||||
|
const bool in_source = (m_node->flags & detail::view::node_flags::storage) == 0;
|
||||||
|
if (!in_source)
|
||||||
|
{
|
||||||
|
return m_node->len;
|
||||||
|
}
|
||||||
|
if (next != m_doc->tape + m_doc->tape_size && (next->flags & detail::view::node_flags::storage) == 0 && next->off >= m_node->off)
|
||||||
|
{
|
||||||
|
return next->off - m_node->off;
|
||||||
|
}
|
||||||
|
return m_doc->size - m_node->off;
|
||||||
|
}
|
||||||
|
|
||||||
/// the value of the first member with this key, or a discarded view
|
/// the value of the first member with this key, or a discarded view
|
||||||
/// (object required)
|
/// (object required)
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
||||||
@@ -806,7 +927,9 @@ class basic_json_document
|
|||||||
}
|
}
|
||||||
return sizeof(document_data) + (m_data->inline_cap * sizeof(detail::view::node))
|
return sizeof(document_data) + (m_data->inline_cap * sizeof(detail::view::node))
|
||||||
+ (m_data->tape != m_data->inline_tape ? m_data->tape_cap * sizeof(detail::view::node) : 0)
|
+ (m_data->tape != m_data->inline_tape ? m_data->tape_cap * sizeof(detail::view::node) : 0)
|
||||||
+ m_data->arena.capacity() + m_data->owned.capacity();
|
+ m_data->arena.capacity() + m_data->owned.capacity()
|
||||||
|
+ (m_data->indexes.capacity() * sizeof(document_data::object_index)) + (m_data->index_slots.capacity() * sizeof(std::uint32_t))
|
||||||
|
+ (m_data->large_objects.capacity() * sizeof(std::uint32_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// release unused capacity of the index and the decoded strings; like
|
/// release unused capacity of the index and the decoded strings; like
|
||||||
@@ -876,6 +999,9 @@ class basic_json_document
|
|||||||
d.size = size;
|
d.size = size;
|
||||||
d.tape_size = 0;
|
d.tape_size = 0;
|
||||||
d.arena.clear();
|
d.arena.clear();
|
||||||
|
d.indexes.clear();
|
||||||
|
d.index_slots.clear();
|
||||||
|
d.large_objects.clear();
|
||||||
d.discarded = true;
|
d.discarded = true;
|
||||||
detail::view::parse_failure failure;
|
detail::view::parse_failure failure;
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
@@ -891,6 +1017,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
d.base[0] = d.src;
|
d.base[0] = d.src;
|
||||||
d.base[1] = d.arena.data();
|
d.base[1] = d.arena.data();
|
||||||
|
detail::view::build_object_indexes(d);
|
||||||
d.discarded = false;
|
d.discarded = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -324,6 +324,26 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
|||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# the json_view parser again with the portable string scanning instead of
|
||||||
|
# NEON/SSE2, and on x86-64 with the SSSE3 UTF-8 check (JSON_VIEW_USE_SSSE3)
|
||||||
|
json_test_set_test_options(test-json_view_builder_portable
|
||||||
|
COMPILE_DEFINITIONS JSON_VIEW_NO_SIMD
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-json_view_builder.cpp
|
||||||
|
NAME test-json_view_builder_portable
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC)
|
||||||
|
json_test_set_test_options(test-json_view_builder_ssse3
|
||||||
|
COMPILE_DEFINITIONS JSON_VIEW_USE_SSSE3
|
||||||
|
COMPILE_OPTIONS -mssse3
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-json_view_builder.cpp
|
||||||
|
NAME test-json_view_builder_ssse3
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# *DO NOT* use json_test_set_test_options() below this line
|
# *DO NOT* use json_test_set_test_options() below this line
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
|
|||||||
| `ViewParseIndented` | as `ParseIndented`, with a reused `json_document` |
|
| `ViewParseIndented` | as `ParseIndented`, with a reused `json_document` |
|
||||||
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
|
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
|
||||||
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
|
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
|
||||||
|
| `ViewDump` | serialize a parsed `json_document`; compare with `Dump` |
|
||||||
|
|
||||||
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
||||||
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# json_view compared with other libraries
|
||||||
|
|
||||||
|
The in-tree benchmarks in [`tests/benchmarks`](../README.md) measure `json_document` against `json::parse` only. The
|
||||||
|
programs here compare it with [yyjson](https://github.com/ibireme/yyjson),
|
||||||
|
[simdjson](https://github.com/simdjson/simdjson), and [Boost.JSON](https://github.com/boostorg/json): the question
|
||||||
|
users ask when they pick a library. They are not built by CMake or run by CI.
|
||||||
|
|
||||||
|
## Reproducing the numbers
|
||||||
|
|
||||||
|
`compare.py` builds both programs against `include/` of this checkout, runs them, and writes the results together with
|
||||||
|
everything needed to reproduce them to `results/<date>-<host>.md` (and `.csv`): the date, the commit, the CPU, the
|
||||||
|
OS, the compiler, the flags, and the versions of all libraries.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tests/benchmarks/json_view/compare.py --data <json_test_data directory> [--native] [--rounds 30]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--data` is the downloaded [test data](https://github.com/nlohmann/json_test_data), e.g. the `test_files` directory
|
||||||
|
of a CMake build directory. It needs `nativejson-benchmark/{twitter,citm_catalog,canada}.json` and
|
||||||
|
`jeopardy/jeopardy.json`.
|
||||||
|
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
|
||||||
|
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
|
||||||
|
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
|
||||||
|
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
|
||||||
|
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
|
||||||
|
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
|
||||||
|
- Only the Python 3 standard library is used; a C++17 compiler is needed (`CXX` and `CC` are honored).
|
||||||
|
|
||||||
|
For numbers worth publishing, use a quiet machine (see [Getting stable numbers](../README.md#getting-stable-numbers)),
|
||||||
|
the default 30 rounds or more, and `--native` only if the other libraries were built for the same CPU.
|
||||||
|
|
||||||
|
### On GitHub-hosted runners
|
||||||
|
|
||||||
|
The workflow [json_view benchmarks](../../../.github/workflows/json_view_benchmarks.yml) runs `compare.py --download`
|
||||||
|
on demand: by hand (Actions → "json_view benchmarks" → "Run workflow"), on an x86-64 or AArch64 Ubuntu runner with GCC
|
||||||
|
or Clang, or when a pull request gets the label `benchmark`, on both architectures with GCC. The results appear as the
|
||||||
|
job summary and as an artifact. Shared runners are noisy, so these numbers show
|
||||||
|
where `json_view` stands on another architecture; they are not meant for publication.
|
||||||
|
|
||||||
|
## What is measured
|
||||||
|
|
||||||
|
`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||||
|
JSON-RPC request (`rpc`):
|
||||||
|
|
||||||
|
| workload | what it does |
|
||||||
|
|---|---|
|
||||||
|
| parse | build and free a document |
|
||||||
|
| traverse | parse, then visit every value, convert every number, touch every string and key |
|
||||||
|
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
|
||||||
|
| dump | serialize a parsed document (compact) |
|
||||||
|
|
||||||
|
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
|
||||||
|
documents.
|
||||||
|
|
||||||
|
Before anything is timed, all engines must accept each document and agree on the traversal: the number of values, the
|
||||||
|
bytes of all strings and keys, and the sum of all numbers. All engines run interleaved in every round, and the best
|
||||||
|
round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`).
|
||||||
|
|
||||||
|
The engines do not all offer the same features, which the numbers should be read with:
|
||||||
|
|
||||||
|
| engine | document | random access | editable | notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
|
||||||
|
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
|
||||||
|
| simdjson DOM | immutable, parser reused | yes | no | |
|
||||||
|
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
|
||||||
|
| Boost.JSON | owning, mutable DOM | yes | yes | monotonic resource |
|
||||||
|
| `json::parse` | owning, mutable DOM | yes | yes | |
|
||||||
|
|
||||||
|
## Published results
|
||||||
|
|
||||||
|
Results are only published with the file `compare.py` wrote, which names the machine and the versions; see
|
||||||
|
`results/`. Numbers from one machine and compiler do not carry over to another: rerun the script.
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Corpus benchmark: the read-only workloads of bench_view.cpp on any list of
|
||||||
|
// JSON files (for example the benchmark sets of simdjson and yyjson).
|
||||||
|
//
|
||||||
|
// ./bench_corpus [--rounds N] file...
|
||||||
|
//
|
||||||
|
// For every file, all engines must accept it and agree on a traversal (value
|
||||||
|
// count, string bytes, sum of numbers) before anything is timed. Workloads:
|
||||||
|
// parse (build and free a document), traverse (visit every value, convert
|
||||||
|
// every number), dump (compact), and for json_view also dump with the source
|
||||||
|
// number text. Results go to bench_corpus.csv.
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
|
||||||
|
static volatile double g_sink;
|
||||||
|
|
||||||
|
struct stats
|
||||||
|
{
|
||||||
|
double num = 0;
|
||||||
|
std::size_t str = 0, nodes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
{
|
||||||
|
st.str += it.key().size();
|
||||||
|
walk(*it, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const json_view e : v)
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += v.get<double>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(yyjson_val* v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (yyjson_get_type(v))
|
||||||
|
{
|
||||||
|
case YYJSON_TYPE_OBJ:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* k, * val;
|
||||||
|
yyjson_obj_foreach(v, idx, max, k, val)
|
||||||
|
{
|
||||||
|
st.str += yyjson_get_len(k);
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_ARR:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* val;
|
||||||
|
yyjson_arr_foreach(v, idx, max, val)
|
||||||
|
{
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_STR:
|
||||||
|
st.str += yyjson_get_len(v);
|
||||||
|
break;
|
||||||
|
case YYJSON_TYPE_NUM:
|
||||||
|
st.num += yyjson_is_sint(v) ? static_cast<double>(yyjson_get_sint(v)) : yyjson_is_uint(v) ? static_cast<double>(yyjson_get_uint(v)) : yyjson_get_real(v);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(simdjson::dom::element e, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (e.type())
|
||||||
|
{
|
||||||
|
case simdjson::dom::element_type::OBJECT:
|
||||||
|
for (auto f : simdjson::dom::object(e))
|
||||||
|
{
|
||||||
|
st.str += f.key.size();
|
||||||
|
walk(f.value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::ARRAY:
|
||||||
|
for (auto c : simdjson::dom::array(e))
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::STRING:
|
||||||
|
st.str += std::string_view(e).size();
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::INT64:
|
||||||
|
st.num += static_cast<double>(int64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::UINT64:
|
||||||
|
st.num += static_cast<double>(uint64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::DOUBLE:
|
||||||
|
st.num += double(e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
static void walk(const boost::json::value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.kind())
|
||||||
|
{
|
||||||
|
case boost::json::kind::object:
|
||||||
|
for (const auto& kv : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += kv.key().size();
|
||||||
|
walk(kv.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::array:
|
||||||
|
for (const auto& c : v.get_array())
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case boost::json::kind::int64:
|
||||||
|
st.num += static_cast<double>(v.get_int64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::uint64:
|
||||||
|
st.num += static_cast<double>(v.get_uint64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::double_:
|
||||||
|
st.num += v.get_double();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool same(const stats& a, const stats& b)
|
||||||
|
{
|
||||||
|
return a.nodes == b.nodes && a.str == b.str && (a.num == b.num || std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int rounds = 0; // 0: by size
|
||||||
|
std::vector<std::string> files;
|
||||||
|
for (int i = 1; i < argc; ++i)
|
||||||
|
{
|
||||||
|
if (std::strcmp(argv[i], "--rounds") == 0 && i + 1 < argc)
|
||||||
|
{
|
||||||
|
rounds = std::atoi(argv[++i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
files.push_back(argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::FILE* csv = std::fopen("bench_corpus.csv", "w");
|
||||||
|
std::fprintf(csv, "file,bytes,workload,engine,ns\n");
|
||||||
|
simdjson::dom::parser sj;
|
||||||
|
for (const auto& path : files)
|
||||||
|
{
|
||||||
|
const std::string s = slurp(path);
|
||||||
|
const std::string name = path.substr(path.rfind('/') + 1);
|
||||||
|
const simdjson::padded_string ps(s);
|
||||||
|
|
||||||
|
// all engines must agree before timing
|
||||||
|
stats a, b, c, d;
|
||||||
|
const json_document doc = json_document::parse(s);
|
||||||
|
walk(doc.root(), a);
|
||||||
|
yyjson_doc* y = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
auto sjr = sj.parse(ps);
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
boost::json::parse_options opt;
|
||||||
|
opt.numbers = boost::json::number_precision::precise;
|
||||||
|
boost::json::monotonic_resource mr0;
|
||||||
|
const boost::json::value bv = boost::json::parse(s, &mr0, opt);
|
||||||
|
#endif
|
||||||
|
if (y == nullptr || sjr.error())
|
||||||
|
{
|
||||||
|
std::printf("%-34s skipped (an engine rejects it)\n", name.c_str());
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
walk(yyjson_doc_get_root(y), b);
|
||||||
|
walk(sjr.value_unsafe(), c);
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
walk(bv, d);
|
||||||
|
#else
|
||||||
|
d = a;
|
||||||
|
#endif
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
const bool ok = same(a, b) && same(a, c) && same(a, d);
|
||||||
|
|
||||||
|
const int r = rounds > 0 ? rounds : static_cast<int>(std::max<std::size_t>(3, std::min<std::size_t>(60, 400000000 / (s.size() + 1))));
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
json_document vd = json_document::parse(s);
|
||||||
|
yyjson_doc* yd = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
simdjson::dom::parser sjd;
|
||||||
|
const simdjson::dom::element se = sjd.parse(ps).value_unsafe();
|
||||||
|
const std::vector<std::pair<std::string, std::vector<engine>>> workloads =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"parse", {
|
||||||
|
{"json_view", [&] { auto x = json_document::parse(s); g_sink = static_cast<double>(x.node_count()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(x)); yyjson_doc_free(x); }},
|
||||||
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"traverse", {
|
||||||
|
{"json_view", [&] { auto x = json_document::parse(s); stats st; walk(x.root(), st); g_sink = st.num; }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(x), st); g_sink = st.num; yyjson_doc_free(x); }},
|
||||||
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dump", {
|
||||||
|
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json_view (source numbers)", [&] { std::string o = vd.root().dump(-1, ' ', false, json_view::number_format::source); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
std::printf("%-34s %9zu B%s\n", name.c_str(), s.size(), ok ? "" : " [ENGINES DISAGREE]");
|
||||||
|
for (const auto& wl : workloads)
|
||||||
|
{
|
||||||
|
std::vector<double> best(wl.second.size(), 1e300);
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
wl.second[k].fn();
|
||||||
|
best[k] = std::min(best[k], std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf(" %-9s", wl.first.c_str());
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
std::printf(" %s %.2f GB/s (%.2fx)", wl.second[k].name.c_str(), static_cast<double>(s.size()) / best[k], best[k] / best[0]);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
yyjson_doc_free(yd);
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,735 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Same-feature-set benchmark: read-only JSON documents with random access.
|
||||||
|
//
|
||||||
|
// json_view nlohmann/json_view.hpp (fresh document per parse / reused)
|
||||||
|
// yyjson yyjson_read(): immutable document, random access
|
||||||
|
// simdjson DOM dom::parser (reused, as recommended): immutable, random access
|
||||||
|
// references (different feature sets):
|
||||||
|
// simdjson OD On-Demand: forward-only, lazy
|
||||||
|
// Boost.JSON owning, mutable DOM (monotonic resource)
|
||||||
|
// json::parse owning, mutable DOM (nlohmann today)
|
||||||
|
//
|
||||||
|
// Workloads: parse (build + free), traverse (visit everything, convert every
|
||||||
|
// number, touch every string and key), select (a few fields per document),
|
||||||
|
// dump (compact serialization of the parsed document).
|
||||||
|
// All engines run interleaved in every round; the best round is reported.
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
|
||||||
|
static volatile double g_sink;
|
||||||
|
|
||||||
|
struct stats
|
||||||
|
{
|
||||||
|
double num = 0;
|
||||||
|
std::size_t str = 0, nodes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------- traversal ----------------
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
{
|
||||||
|
st.str += it.key().size();
|
||||||
|
walk(*it, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const json_view e : v)
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += v.get<double>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(const json& j, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (j.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (const auto& kv : j.get_ref<const json::object_t&>())
|
||||||
|
{
|
||||||
|
st.str += kv.first.size();
|
||||||
|
walk(kv.second, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const auto& e : j.get_ref<const json::array_t&>())
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += j.get_ref<const std::string&>().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(*j.get_ptr<const json::number_integer_t*>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(*j.get_ptr<const json::number_unsigned_t*>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += *j.get_ptr<const json::number_float_t*>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(yyjson_val* v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (yyjson_get_type(v))
|
||||||
|
{
|
||||||
|
case YYJSON_TYPE_OBJ:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* k, * val;
|
||||||
|
yyjson_obj_foreach(v, idx, max, k, val)
|
||||||
|
{
|
||||||
|
st.str += yyjson_get_len(k);
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_ARR:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* val;
|
||||||
|
yyjson_arr_foreach(v, idx, max, val)
|
||||||
|
{
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_STR:
|
||||||
|
st.str += yyjson_get_len(v);
|
||||||
|
break;
|
||||||
|
case YYJSON_TYPE_NUM:
|
||||||
|
if (yyjson_is_sint(v))
|
||||||
|
{
|
||||||
|
st.num += static_cast<double>(yyjson_get_sint(v));
|
||||||
|
}
|
||||||
|
else if (yyjson_is_uint(v))
|
||||||
|
{
|
||||||
|
st.num += static_cast<double>(yyjson_get_uint(v));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
st.num += yyjson_get_real(v);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(simdjson::dom::element e, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (e.type())
|
||||||
|
{
|
||||||
|
case simdjson::dom::element_type::OBJECT:
|
||||||
|
for (auto f : simdjson::dom::object(e))
|
||||||
|
{
|
||||||
|
st.str += f.key.size();
|
||||||
|
walk(f.value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::ARRAY:
|
||||||
|
for (auto c : simdjson::dom::array(e))
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::STRING:
|
||||||
|
st.str += std::string_view(e).size();
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::INT64:
|
||||||
|
st.num += static_cast<double>(int64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::UINT64:
|
||||||
|
st.num += static_cast<double>(uint64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::DOUBLE:
|
||||||
|
st.num += double(e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk_od(simdjson::ondemand::value v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case simdjson::ondemand::json_type::object:
|
||||||
|
for (auto f : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += std::string_view(f.unescaped_key()).size();
|
||||||
|
walk_od(f.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::array:
|
||||||
|
for (auto c : v.get_array())
|
||||||
|
{
|
||||||
|
walk_od(c.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::string:
|
||||||
|
st.str += std::string_view(v.get_string()).size();
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::number:
|
||||||
|
{
|
||||||
|
simdjson::ondemand::number n = v.get_number();
|
||||||
|
switch (n.get_number_type())
|
||||||
|
{
|
||||||
|
case simdjson::ondemand::number_type::signed_integer:
|
||||||
|
st.num += static_cast<double>(n.get_int64());
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::number_type::unsigned_integer:
|
||||||
|
st.num += static_cast<double>(n.get_uint64());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
st.num += n.get_double();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case simdjson::ondemand::json_type::boolean:
|
||||||
|
(void)bool(v.get_bool());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
(void)v.is_null();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
static void walk(const boost::json::value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.kind())
|
||||||
|
{
|
||||||
|
case boost::json::kind::object:
|
||||||
|
for (const auto& kv : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += kv.key().size();
|
||||||
|
walk(kv.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::array:
|
||||||
|
for (const auto& c : v.get_array())
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case boost::json::kind::int64:
|
||||||
|
st.num += static_cast<double>(v.get_int64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::uint64:
|
||||||
|
st.num += static_cast<double>(v.get_uint64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::double_:
|
||||||
|
st.num += v.get_double();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------- selective access ----------------
|
||||||
|
// twitter: per status id, user.screen_name, retweet_count
|
||||||
|
// citm: per performance id, eventId, #seatCategories; #events
|
||||||
|
// canada: type, features[0].geometry.type, #coordinates
|
||||||
|
// jeopardy: per question: round == "Final Jeopardy!", len(category)
|
||||||
|
// status (one tweet): id, user.screen_name, retweet_count
|
||||||
|
// rpc: method, params.minuend, id
|
||||||
|
|
||||||
|
static double pick(const std::string& name, json_view r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](json_view s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
||||||
|
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
|
||||||
|
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (const json_view s : r["statuses"])
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (const json_view p : r["performances"])
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(r["events"].size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json_view g = r["features"][0]["geometry"];
|
||||||
|
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (const json_view q : r)
|
||||||
|
{
|
||||||
|
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(q["category"].get_string().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(r["method"].get_string().size());
|
||||||
|
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, const json& r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](const json & s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
||||||
|
acc += static_cast<double>(s["user"]["screen_name"].get_ref<const std::string&>().size());
|
||||||
|
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (const auto& s : r["statuses"])
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (const auto& p : r["performances"])
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(r["events"].size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json& g = r["features"][0]["geometry"];
|
||||||
|
acc += static_cast<double>(r["type"].get_ref<const std::string&>().size() + g["type"].get_ref<const std::string&>().size() + g["coordinates"].size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (const auto& q : r)
|
||||||
|
{
|
||||||
|
acc += q["round"].get_ref<const std::string&>() == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(q["category"].get_ref<const std::string&>().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(r["method"].get_ref<const std::string&>().size());
|
||||||
|
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, yyjson_val* r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
auto get = [](yyjson_val * o, const char* k)
|
||||||
|
{
|
||||||
|
return yyjson_obj_get(o, k);
|
||||||
|
};
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](yyjson_val * s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_uint(get(s, "id")));
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(get(s, "user"), "screen_name")));
|
||||||
|
acc += static_cast<double>(yyjson_get_sint(get(s, "retweet_count")));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* s;
|
||||||
|
yyjson_arr_foreach(get(r, "statuses"), idx, max, s)
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* p;
|
||||||
|
yyjson_arr_foreach(get(r, "performances"), idx, max, p)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_uint(get(p, "id")) + yyjson_get_uint(get(p, "eventId")) + yyjson_arr_size(get(p, "seatCategories")));
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(yyjson_obj_size(get(r, "events")));
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
yyjson_val* g = get(yyjson_arr_get(get(r, "features"), 0), "geometry");
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(r, "type")) + yyjson_get_len(get(g, "type")) + yyjson_arr_size(get(g, "coordinates")));
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* q;
|
||||||
|
yyjson_arr_foreach(r, idx, max, q)
|
||||||
|
{
|
||||||
|
acc += yyjson_equals_str(get(q, "round"), "Final Jeopardy!") ? 1 : 0;
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(q, "category")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(r, "method")));
|
||||||
|
acc += static_cast<double>(yyjson_get_sint(get(get(r, "params"), "minuend")) + yyjson_get_sint(get(r, "id")));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, simdjson::dom::element r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](simdjson::dom::element s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(s["id"]));
|
||||||
|
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(s["retweet_count"]));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto s : simdjson::dom::array(r["statuses"]))
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (auto p : simdjson::dom::array(r["performances"]))
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(p["id"]) + uint64_t(p["eventId"]) + simdjson::dom::array(p["seatCategories"]).size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(simdjson::dom::object(r["events"]).size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
auto g = r["features"].at(0)["geometry"];
|
||||||
|
acc += static_cast<double>(std::string_view(r["type"]).size() + std::string_view(g["type"]).size() + simdjson::dom::array(g["coordinates"]).size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : simdjson::dom::array(r))
|
||||||
|
{
|
||||||
|
acc += std::string_view(q["round"]) == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(std::string_view(q["category"]).size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(r["method"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(r["params"]["minuend"]) + int64_t(r["id"]));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick_od(const std::string& name, simdjson::ondemand::document& d)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](simdjson::ondemand::object s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(s["id"]));
|
||||||
|
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(s["retweet_count"]));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto s : d["statuses"])
|
||||||
|
{
|
||||||
|
one(s.get_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(d.get_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object ev = d["events"].get_object();
|
||||||
|
acc += static_cast<double>(ev.count_fields());
|
||||||
|
for (auto p : d["performances"])
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object o = p.get_object();
|
||||||
|
const auto a = uint64_t(o["eventId"]) + uint64_t(o["id"]);
|
||||||
|
simdjson::ondemand::array sc = o["seatCategories"].get_array();
|
||||||
|
acc += static_cast<double>(a + sc.count_elements());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(d["type"]).size());
|
||||||
|
auto g = d["features"].at(0)["geometry"];
|
||||||
|
acc += static_cast<double>(std::string_view(g["type"]).size());
|
||||||
|
simdjson::ondemand::array co = g["coordinates"].get_array();
|
||||||
|
acc += static_cast<double>(co.count_elements());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : d)
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object o = q.get_object();
|
||||||
|
acc += static_cast<double>(std::string_view(o["category"]).size());
|
||||||
|
acc += std::string_view(o["round"]) == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(d["method"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(d["params"]["minuend"]));
|
||||||
|
acc += static_cast<double>(int64_t(d["id"]));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- harness ----------------
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds] [document]\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const std::string T = std::string(argv[1]) + "/";
|
||||||
|
const int rounds = argc > 2 ? std::atoi(argv[2]) : 30;
|
||||||
|
const std::string only = argc > 3 ? argv[3] : "";
|
||||||
|
struct doc
|
||||||
|
{
|
||||||
|
std::string name, text;
|
||||||
|
int batch;
|
||||||
|
};
|
||||||
|
std::vector<doc> docs;
|
||||||
|
for (const char* f :
|
||||||
|
{"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
|
||||||
|
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
|
||||||
|
}
|
||||||
|
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
|
||||||
|
docs.push_back({"rpc", R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 3})", 5000});
|
||||||
|
|
||||||
|
// correctness cross-check of the workloads
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
stats a, b, c, dd;
|
||||||
|
walk(json::parse(dc.text), a);
|
||||||
|
auto d = json_document::parse(dc.text);
|
||||||
|
walk(d.root(), b);
|
||||||
|
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||||
|
walk(yyjson_doc_get_root(y), c);
|
||||||
|
simdjson::dom::parser p;
|
||||||
|
walk(p.parse(dc.text).value(), dd);
|
||||||
|
const bool ok = a.nodes == b.nodes && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
||||||
|
&& std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num);
|
||||||
|
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
||||||
|
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::FILE* csv = std::fopen("bench_view.csv", "w");
|
||||||
|
std::fprintf(csv, "doc,bytes,workload,engine,ns\n");
|
||||||
|
json_document reused;
|
||||||
|
simdjson::dom::parser sj;
|
||||||
|
simdjson::ondemand::parser od;
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
if (!only.empty() && dc.name != only)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string& s = dc.text;
|
||||||
|
const simdjson::padded_string ps(s);
|
||||||
|
const std::string name = dc.name;
|
||||||
|
std::vector<std::pair<std::string, std::vector<engine>>> workloads;
|
||||||
|
|
||||||
|
workloads.push_back({"parse", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
|
||||||
|
#endif
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); g_sink = static_cast<double>(j.size()); }},
|
||||||
|
}});
|
||||||
|
workloads.push_back({"traverse", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
|
||||||
|
#endif
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); stats st; walk(j, st); g_sink = st.num; }},
|
||||||
|
}});
|
||||||
|
workloads.push_back({"select", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
|
||||||
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }},
|
||||||
|
}});
|
||||||
|
{
|
||||||
|
// serialization of an already parsed document
|
||||||
|
static json_document vd;
|
||||||
|
vd.read(s);
|
||||||
|
static yyjson_doc* yd = nullptr;
|
||||||
|
if (yd)
|
||||||
|
{
|
||||||
|
yyjson_doc_free(yd);
|
||||||
|
}
|
||||||
|
yd = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
static simdjson::dom::parser sjd;
|
||||||
|
static simdjson::dom::element se;
|
||||||
|
se = sjd.parse(ps).value_unsafe();
|
||||||
|
static json jd;
|
||||||
|
jd = json::parse(s);
|
||||||
|
workloads.push_back({"dump", {
|
||||||
|
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& wl : workloads)
|
||||||
|
{
|
||||||
|
std::vector<double> best(wl.second.size(), 1e300);
|
||||||
|
const int r = s.size() > 10000000 ? std::max(3, rounds / 5) : rounds;
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
for (int b = 0; b < dc.batch; ++b)
|
||||||
|
{
|
||||||
|
wl.second[k].fn();
|
||||||
|
}
|
||||||
|
const double ns = std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch;
|
||||||
|
best[k] = std::min(best[k], ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double ref = best[0];
|
||||||
|
std::printf("%-13s %-9s", dc.name.c_str(), wl.first.c_str());
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
const double us = best[k] / 1e3;
|
||||||
|
std::printf(" %s %s%s (%.2fx)", wl.second[k].name.c_str(), us >= 100 ? "" : "", (us >= 1000 ? std::to_string(static_cast<long>(us)) + "us" : (std::to_string(us).substr(0, 5) + "us")).c_str(), best[k] / ref);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", dc.name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
Executable
+300
@@ -0,0 +1,300 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# __ _____ _____ _____
|
||||||
|
# __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
# | | |__ | | | | | | version 3.12.0
|
||||||
|
# |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
#
|
||||||
|
# SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""Compare json_view with yyjson, simdjson, Boost.JSON, and json::parse.
|
||||||
|
|
||||||
|
Builds bench_view.cpp and bench_corpus.cpp against the include/ directory of
|
||||||
|
this checkout, runs them, and writes the results with everything needed to
|
||||||
|
reproduce them (date, commit, CPU, OS, compiler, library versions, flags) to
|
||||||
|
results/<date>-<host>.md and .csv next to this script.
|
||||||
|
|
||||||
|
The other libraries come from the system (--system, the default: pkg-config
|
||||||
|
or Homebrew) or are downloaded as pinned releases and checked against their
|
||||||
|
SHA-256 (--download). Boost.JSON is optional: without Boost headers, its
|
||||||
|
columns are skipped, and the results say so.
|
||||||
|
|
||||||
|
Only the Python 3 standard library is used; a C++17 compiler is needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
REPO = os.path.abspath(os.path.join(HERE, '..', '..', '..'))
|
||||||
|
|
||||||
|
# pinned releases for --download; the hashes are those of the archives
|
||||||
|
PINNED = {
|
||||||
|
'yyjson': {
|
||||||
|
'version': '0.13.0',
|
||||||
|
'url': 'https://github.com/ibireme/yyjson/archive/refs/tags/0.13.0.tar.gz',
|
||||||
|
'sha256': '34e0f62a2bc11ab20d601e8ca1cc2b2079503aa45119a19133d89d19b94a0fae',
|
||||||
|
'dir': 'yyjson-0.13.0',
|
||||||
|
},
|
||||||
|
'simdjson': {
|
||||||
|
'version': '4.6.11',
|
||||||
|
'url': 'https://github.com/simdjson/simdjson/archive/refs/tags/v4.6.11.tar.gz',
|
||||||
|
'sha256': '61d948fc24f0d793829ad658058e7597d064988a89b4607ea02e401a82df98ff',
|
||||||
|
'dir': 'simdjson-4.6.11',
|
||||||
|
},
|
||||||
|
'boost': {
|
||||||
|
'version': '1.92.0',
|
||||||
|
'url': 'https://archives.boost.io/release/1.92.0/source/boost_1_92_0.tar.gz',
|
||||||
|
'sha256': 'c4a3b310ddd2472416e091067166b0713be97c63f38c212c484ada022fd296ce',
|
||||||
|
'dir': 'boost_1_92_0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# the documents of bench_view.cpp, relative to the json_test_data directory
|
||||||
|
DEFAULT_CORPUS = [
|
||||||
|
'nativejson-benchmark/twitter.json',
|
||||||
|
'nativejson-benchmark/citm_catalog.json',
|
||||||
|
'nativejson-benchmark/canada.json',
|
||||||
|
'jeopardy/jeopardy.json',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd, **kwargs):
|
||||||
|
print('+ ' + ' '.join(shlex.quote(c) for c in cmd), flush=True)
|
||||||
|
return subprocess.run(cmd, check=True, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def output(cmd):
|
||||||
|
try:
|
||||||
|
return subprocess.run(cmd, check=True, capture_output=True, text=True).stdout.strip()
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# libraries
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Library:
|
||||||
|
"""include directories, sources to compile, and linker flags of a library"""
|
||||||
|
|
||||||
|
def __init__(self, name, include=None, sources=None, link=None, version=''):
|
||||||
|
self.name = name
|
||||||
|
self.include = include or []
|
||||||
|
self.sources = sources or []
|
||||||
|
self.link = link or []
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
|
||||||
|
def header_version(path, pattern):
|
||||||
|
try:
|
||||||
|
with open(path, encoding='utf-8', errors='replace') as f:
|
||||||
|
m = re.search(pattern, f.read())
|
||||||
|
return m.group(1) if m else ''
|
||||||
|
except OSError:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def library_version(name, include_dirs):
|
||||||
|
patterns = {
|
||||||
|
'yyjson': ('yyjson.h', r'#define\s+YYJSON_VERSION_STRING\s+"([^"]+)"'),
|
||||||
|
'simdjson': ('simdjson.h', r'#define\s+SIMDJSON_VERSION\s+"?([0-9.]+)"?'),
|
||||||
|
'boost': (os.path.join('boost', 'version.hpp'), r'#define\s+BOOST_LIB_VERSION\s+"([^"]+)"'),
|
||||||
|
}
|
||||||
|
header, pattern = patterns[name]
|
||||||
|
for d in include_dirs:
|
||||||
|
v = header_version(os.path.join(d, header), pattern)
|
||||||
|
if v:
|
||||||
|
return v.replace('_', '.')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def system_library(name):
|
||||||
|
"""a library found with pkg-config or Homebrew, or None"""
|
||||||
|
flags = output(['pkg-config', '--cflags', '--libs', name]).split()
|
||||||
|
if flags:
|
||||||
|
include = [f[2:] for f in flags if f.startswith('-I')]
|
||||||
|
link = [f for f in flags if f.startswith('-L') or f.startswith('-l')]
|
||||||
|
libdirs = [f[2:] for f in link if f.startswith('-L')]
|
||||||
|
link += ['-Wl,-rpath,' + d for d in libdirs]
|
||||||
|
return Library(name, include, [], link, library_version(name, include))
|
||||||
|
prefix = output(['brew', '--prefix', name]) if shutil.which('brew') else ''
|
||||||
|
if prefix and os.path.isdir(os.path.join(prefix, 'include')):
|
||||||
|
include = [os.path.join(prefix, 'include')]
|
||||||
|
link = []
|
||||||
|
if name != 'boost':
|
||||||
|
lib = os.path.join(prefix, 'lib')
|
||||||
|
link = ['-L' + lib, '-l' + name, '-Wl,-rpath,' + lib]
|
||||||
|
return Library(name, include, [], link, library_version(name, include))
|
||||||
|
if name == 'boost':
|
||||||
|
for d in ['/usr/include', '/usr/local/include']:
|
||||||
|
if os.path.isfile(os.path.join(d, 'boost', 'json.hpp')):
|
||||||
|
return Library(name, [d], [], [], library_version(name, [d]))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def download_library(name, work):
|
||||||
|
"""a pinned release, downloaded and checked, or an error"""
|
||||||
|
pin = PINNED[name]
|
||||||
|
archive = os.path.join(work, 'download', os.path.basename(pin['url']))
|
||||||
|
os.makedirs(os.path.dirname(archive), exist_ok=True)
|
||||||
|
if not os.path.isfile(archive):
|
||||||
|
print(f'downloading {pin["url"]}', flush=True)
|
||||||
|
urllib.request.urlretrieve(pin['url'], archive)
|
||||||
|
with open(archive, 'rb') as f:
|
||||||
|
digest = hashlib.sha256(f.read()).hexdigest()
|
||||||
|
if digest != pin['sha256']:
|
||||||
|
sys.exit(f'error: SHA-256 of {archive} is {digest}, expected {pin["sha256"]}')
|
||||||
|
src = os.path.join(work, 'download', pin['dir'])
|
||||||
|
if not os.path.isdir(src):
|
||||||
|
with tarfile.open(archive) as t:
|
||||||
|
# (the 'data' filter rejects links and paths outside the target where Python has it)
|
||||||
|
kwargs = {'filter': 'data'} if hasattr(tarfile, 'data_filter') else {}
|
||||||
|
t.extractall(os.path.join(work, 'download'), **kwargs) # noqa: S202 (checked archive)
|
||||||
|
if name == 'yyjson':
|
||||||
|
return Library(name, [os.path.join(src, 'src')], [os.path.join(src, 'src', 'yyjson.c')], [], pin['version'])
|
||||||
|
if name == 'simdjson':
|
||||||
|
single = os.path.join(src, 'singleheader')
|
||||||
|
return Library(name, [single], [os.path.join(single, 'simdjson.cpp')], [], pin['version'])
|
||||||
|
return Library(name, [src], [], [], pin['version'])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# machine description
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def cpu_model():
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
return output(['sysctl', '-n', 'machdep.cpu.brand_string'])
|
||||||
|
try:
|
||||||
|
with open('/proc/cpuinfo', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('model name') or line.startswith('Model'):
|
||||||
|
return line.split(':', 1)[1].strip()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
# (AArch64 Linux: /proc/cpuinfo has no model name, lscpu knows it)
|
||||||
|
for line in output(['lscpu']).splitlines():
|
||||||
|
if line.startswith('Model name:'):
|
||||||
|
return line.split(':', 1)[1].strip()
|
||||||
|
return platform.processor() or platform.machine()
|
||||||
|
|
||||||
|
|
||||||
|
def git_commit():
|
||||||
|
commit = output(['git', '-C', REPO, 'rev-parse', '--short=12', 'HEAD'])
|
||||||
|
dirty = output(['git', '-C', REPO, 'status', '--porcelain', '--untracked-files=no'])
|
||||||
|
return commit + (' (with local changes)' if dirty else '')
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# main
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument('--data', required=True, help='json_test_data directory (with nativejson-benchmark/ and jeopardy/)')
|
||||||
|
ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries')
|
||||||
|
ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON')
|
||||||
|
ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)')
|
||||||
|
ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)')
|
||||||
|
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
|
||||||
|
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
cxx = os.environ.get('CXX', 'c++')
|
||||||
|
cc = os.environ.get('CC', 'cc')
|
||||||
|
os.makedirs(args.build_dir, exist_ok=True)
|
||||||
|
|
||||||
|
libs = {}
|
||||||
|
for name in ['yyjson', 'simdjson', 'boost']:
|
||||||
|
if name == 'boost' and args.no_boost:
|
||||||
|
continue
|
||||||
|
lib = download_library(name, args.build_dir) if args.download else system_library(name)
|
||||||
|
if lib is None and name != 'boost':
|
||||||
|
sys.exit(f'error: {name} not found; install it, or use --download')
|
||||||
|
if lib is not None:
|
||||||
|
libs[name] = lib
|
||||||
|
with_boost = 'boost' in libs
|
||||||
|
if not with_boost:
|
||||||
|
print('Boost.JSON not found: its columns are skipped', flush=True)
|
||||||
|
|
||||||
|
flags = ['-std=c++17', '-O3', '-DNDEBUG', f'-DJSON_VIEW_BENCH_BOOST={1 if with_boost else 0}']
|
||||||
|
if args.native:
|
||||||
|
flags.append('-mcpu=native' if platform.machine().lower() in ('arm64', 'aarch64') else '-march=native')
|
||||||
|
include = ['-I' + os.path.join(REPO, 'include')] + ['-I' + d for lib in libs.values() for d in lib.include]
|
||||||
|
link = [f for lib in libs.values() for f in lib.link]
|
||||||
|
|
||||||
|
# C sources of downloaded libraries are compiled once
|
||||||
|
objects = []
|
||||||
|
for lib in libs.values():
|
||||||
|
for src in lib.sources:
|
||||||
|
obj = os.path.join(args.build_dir, os.path.basename(src) + '.o')
|
||||||
|
compiler = cc if src.endswith('.c') else cxx
|
||||||
|
run([compiler] + (['-std=c++17'] if compiler == cxx else []) + ['-O3', '-DNDEBUG', '-c', src, '-o', obj]
|
||||||
|
+ ['-I' + d for d in lib.include])
|
||||||
|
objects.append(obj)
|
||||||
|
|
||||||
|
binaries = {}
|
||||||
|
for bench in ['bench_view', 'bench_corpus']:
|
||||||
|
exe = os.path.join(args.build_dir, bench)
|
||||||
|
run([cxx] + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
||||||
|
binaries[bench] = exe
|
||||||
|
|
||||||
|
# run: bench_view on its documents, bench_corpus on those and the given files
|
||||||
|
corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus
|
||||||
|
outputs = {}
|
||||||
|
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=args.build_dir,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=args.build_dir,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
for name, text in outputs.items():
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
# results with their metadata
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host'
|
||||||
|
stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}')
|
||||||
|
os.makedirs(os.path.dirname(stem), exist_ok=True)
|
||||||
|
meta = [
|
||||||
|
('date', f'{now:%Y-%m-%d %H:%M}'),
|
||||||
|
('commit', git_commit()),
|
||||||
|
('CPU', cpu_model()),
|
||||||
|
('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'),
|
||||||
|
('compiler', output([cxx, '--version']).splitlines()[0] if output([cxx, '--version']) else cxx),
|
||||||
|
('flags', ' '.join(flags)),
|
||||||
|
('yyjson', libs['yyjson'].version),
|
||||||
|
('simdjson', libs['simdjson'].version),
|
||||||
|
('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'),
|
||||||
|
('libraries from', 'pinned downloads' if args.download else 'the system'),
|
||||||
|
('rounds', str(args.rounds)),
|
||||||
|
]
|
||||||
|
with open(stem + '.md', 'w', encoding='utf-8') as f:
|
||||||
|
f.write(f'# json_view comparison, {now:%Y-%m-%d}\n\n')
|
||||||
|
f.write('Generated by `tests/benchmarks/json_view/compare.py`; best of the interleaved rounds.\n\n')
|
||||||
|
f.write('| | |\n|---|---|\n')
|
||||||
|
for key, value in meta:
|
||||||
|
f.write(f'| {key} | {value} |\n')
|
||||||
|
for name, text in outputs.items():
|
||||||
|
f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n')
|
||||||
|
with open(stem + '.csv', 'w', encoding='utf-8') as out:
|
||||||
|
out.write(''.join(f'# {key}: {value}\n' for key, value in meta))
|
||||||
|
for name in ['bench_view', 'bench_corpus']:
|
||||||
|
path = os.path.join(args.build_dir, name + '.csv')
|
||||||
|
if os.path.isfile(path):
|
||||||
|
with open(path, encoding='utf-8') as f:
|
||||||
|
out.write(f'# {name}\n' + f.read())
|
||||||
|
print(f'results: {stem}.md, {stem}.csv')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -144,3 +144,37 @@ static void ViewMaterialize(benchmark::State& state, const char* filename)
|
|||||||
state.SetBytesProcessed(state.iterations() * str.size());
|
state.SetBytesProcessed(state.iterations() * str.size());
|
||||||
}
|
}
|
||||||
JSON_VIEW_BENCHMARK_FILES(ViewMaterialize);
|
JSON_VIEW_BENCHMARK_FILES(ViewMaterialize);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// serialize a parsed document (compare with Dump)
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static void ViewDump(benchmark::State& state, const char* filename, int indent)
|
||||||
|
{
|
||||||
|
const std::string str = read_file(filename);
|
||||||
|
const json_document d = json_document::parse(str);
|
||||||
|
|
||||||
|
while (state.KeepRunning())
|
||||||
|
{
|
||||||
|
std::string output = d.root().dump(indent);
|
||||||
|
benchmark::DoNotOptimize(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetBytesProcessed(state.iterations() * d.root().dump(indent).size());
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, jeopardy / -, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", -1);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, jeopardy / 4, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, canada / -, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", -1);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, canada / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, citm_catalog / -, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", -1);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, citm_catalog / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, twitter / -, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", -1);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, twitter / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, floats / -, TEST_DATA_DIRECTORY "/regression/floats.json", -1);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, floats / 4, TEST_DATA_DIRECTORY "/regression/floats.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, signed_ints / -, TEST_DATA_DIRECTORY "/regression/signed_ints.json", -1);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, signed_ints / 4, TEST_DATA_DIRECTORY "/regression/signed_ints.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, unsigned_ints / -, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json", -1);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, unsigned_ints / 4, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, small_signed_ints / -, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json", -1);
|
||||||
|
BENCHMARK_CAPTURE(ViewDump, small_signed_ints / 4, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json", 4);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using nlohmann::ordered_json_view;
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -872,7 +873,15 @@ TEST_CASE("json_view values")
|
|||||||
std::mt19937_64 rng(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
std::mt19937_64 rng(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
std::vector<std::string> tokens = {"0.1", "-0.0", "1e308", "1.7976931348623157e308", "2.2250738585072011e-308", "4.9e-324", "5e-324",
|
std::vector<std::string> tokens = {"0.1", "-0.0", "1e308", "1.7976931348623157e308", "2.2250738585072011e-308", "4.9e-324", "5e-324",
|
||||||
"0.1000000000000000055511151231257827021181583404541015625", "123456789012345678901234567890",
|
"0.1000000000000000055511151231257827021181583404541015625", "123456789012345678901234567890",
|
||||||
"9007199254740993", "1.00000000000000011102230246251565404236316680908203125", "7.2057594037927933e16"
|
"9007199254740993", "1.00000000000000011102230246251565404236316680908203125", "7.2057594037927933e16",
|
||||||
|
// around the limits of the conversion from the digit layout: 19 and 20
|
||||||
|
// digits, and those of Clinger's fast path (2^53, 10^22)
|
||||||
|
"1234567890.123456789", "1234567890.1234567891", "0.0000000000000000001", "123456789012345678.9",
|
||||||
|
"9007199254740992.0", "9007199254740993.0", "9007199254740994.0", "1.5e22", "1.5e23", "15e-22", "15e-23",
|
||||||
|
"1e-400", "0.0e0", "-0.0e-5", "12E+3", "12e-0",
|
||||||
|
// and of float: 2^24 + 1 and 2^24 + 3 (ties), the subnormal and normal limits
|
||||||
|
"16777217", "16777219", "1.4e-45", "7.006492321624085e-46", "7.006492321624086e-46",
|
||||||
|
"1.17549435e-38", "0.30000001192092896", "3.4028234e37"
|
||||||
};
|
};
|
||||||
for (int i = 0; i < 20000; ++i)
|
for (int i = 0; i < 20000; ++i)
|
||||||
{
|
{
|
||||||
@@ -1081,3 +1090,247 @@ TEST_CASE("json_view JSON pointers")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("json_view dump")
|
||||||
|
{
|
||||||
|
SECTION("the output of ordered_json::dump()")
|
||||||
|
{
|
||||||
|
generator g;
|
||||||
|
for (int i = 0; i < 2000; ++i)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
g.value(text, 0);
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
if (has_duplicate_keys(d.root()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CAPTURE(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
for (const int indent :
|
||||||
|
{
|
||||||
|
-1, 0, 2
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const bool ensure_ascii :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(d.root().dump(indent, i % 2 == 0 ? ' ' : '\t', ensure_ascii) == j.dump(indent, i % 2 == 0 ? ' ' : '\t', ensure_ascii));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// also of each element
|
||||||
|
for (const ordered_json_view e : d.root())
|
||||||
|
{
|
||||||
|
CHECK(e.dump() == e.materialize().dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("strings")
|
||||||
|
{
|
||||||
|
const std::string text = R"(["plain", "\u0000\u0001\u001f\u007f\u0080é€😀", "\"\\\/\b\f\n\r\t", "aéあ😀b", "long text beyond the eight bytes of a word \n with an escape in the middle"])";
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true));
|
||||||
|
const ordered_json_document keys = ordered_json_document::parse(R"({"é\n": {"\"": [], "": {}}})");
|
||||||
|
CHECK(keys.root().dump(2, ' ', true) == ordered_json::parse(R"({"é\n": {"\"": [], "": {}}})").dump(2, ' ', true));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("numbers")
|
||||||
|
{
|
||||||
|
const std::string text = "[1.50, 1E2, -0, -0.0, 123456789012345678901234567890, 18446744073709551615, -9223372036854775808, 0.1, 1e-7, 5e-324]";
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
CHECK(d.root().dump() == json::parse(text).dump());
|
||||||
|
CHECK(d.root().dump() == "[1.5,100.0,0,-0.0,1.2345678901234568e+29,18446744073709551615,-9223372036854775808,0.1,1e-07,5e-324]");
|
||||||
|
CHECK(d.root().dump(-1, ' ', false, json_view::number_format::source) == "[1.50,1E2,-0,-0.0,123456789012345678901234567890,18446744073709551615,-9223372036854775808,0.1,1e-7,5e-324]");
|
||||||
|
|
||||||
|
// random doubles, written as parse() and dump() would
|
||||||
|
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
|
std::string many = "[";
|
||||||
|
for (int i = 0; i < 5000; ++i)
|
||||||
|
{
|
||||||
|
const std::uint64_t bits = rng();
|
||||||
|
double x = 0;
|
||||||
|
std::memcpy(&x, &bits, sizeof(x));
|
||||||
|
if (std::isfinite(x))
|
||||||
|
{
|
||||||
|
many += (many.size() > 1 ? "," : "") + json(x).dump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
many += ']';
|
||||||
|
CHECK(json_document::parse(many).root().dump() == json::parse(many).dump());
|
||||||
|
|
||||||
|
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||||
|
CHECK(nlohmann::basic_json_document<json_float>::parse("[0.1, 1.5e10, 3.4028235e38]").root().dump() == json_float::parse("[0.1, 1.5e10, 3.4028235e38]").dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("members in document order, all of them")
|
||||||
|
{
|
||||||
|
const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})");
|
||||||
|
CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})");
|
||||||
|
CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting")
|
||||||
|
{
|
||||||
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
|
CHECK(json_document::parse(deep).root().dump() == deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("streams and discarded views")
|
||||||
|
{
|
||||||
|
const json_document d = json_document::parse(R"({"a": [1, 2]})");
|
||||||
|
std::ostringstream compact;
|
||||||
|
compact << d.root();
|
||||||
|
CHECK(compact.str() == R"({"a":[1,2]})");
|
||||||
|
std::ostringstream pretty;
|
||||||
|
pretty << std::setw(2) << std::setfill('.') << d.root() << d.root()["a"];
|
||||||
|
CHECK(pretty.str() == "{\n..\"a\": [\n....1,\n....2\n..]\n}[1,2]");
|
||||||
|
CHECK(json_view().dump() == json(json::value_t::discarded).dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("json_view comparison")
|
||||||
|
{
|
||||||
|
SECTION("equality of the values parse() produces")
|
||||||
|
{
|
||||||
|
generator g;
|
||||||
|
std::vector<std::string> texts;
|
||||||
|
for (int i = 0; i < 600; ++i)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
g.value(text, 0);
|
||||||
|
texts.push_back(text);
|
||||||
|
// the same value written differently: sorted keys, canonical numbers
|
||||||
|
texts.push_back(json::parse(text).dump(1));
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i + 2 < texts.size(); ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = i; k < i + 3; ++k)
|
||||||
|
{
|
||||||
|
CAPTURE(texts[i]);
|
||||||
|
CAPTURE(texts[k]);
|
||||||
|
const json_document a = json_document::parse(texts[i]);
|
||||||
|
const json_document b = json_document::parse(texts[k]);
|
||||||
|
const json ja = json::parse(texts[i]);
|
||||||
|
const json jb = json::parse(texts[k]);
|
||||||
|
CHECK((a.root() == b.root()) == (ja == jb));
|
||||||
|
CHECK((a.root() != b.root()) == (ja != jb));
|
||||||
|
CHECK((a.root() == jb) == (ja == jb));
|
||||||
|
CHECK((jb == a.root()) == (ja == jb));
|
||||||
|
CHECK((a.root() != jb) == (ja != jb));
|
||||||
|
CHECK((jb != a.root()) == (ja != jb));
|
||||||
|
|
||||||
|
// ordered_json compares members in order
|
||||||
|
const ordered_json_document oa = ordered_json_document::parse(texts[i]);
|
||||||
|
const ordered_json_document ob = ordered_json_document::parse(texts[k]);
|
||||||
|
const ordered_json oja = ordered_json::parse(texts[i]);
|
||||||
|
const ordered_json ojb = ordered_json::parse(texts[k]);
|
||||||
|
CHECK((oa.root() == ob.root()) == (oja == ojb));
|
||||||
|
CHECK((oa.root() == ojb) == (oja == ojb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("numbers, duplicate keys, member order")
|
||||||
|
{
|
||||||
|
const auto same = [](const char* x, const char* y)
|
||||||
|
{
|
||||||
|
return json_document::parse(x).root() == json_document::parse(y).root();
|
||||||
|
};
|
||||||
|
CHECK(same("1", "1.0"));
|
||||||
|
CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]"));
|
||||||
|
CHECK(!same("1", "1.5"));
|
||||||
|
CHECK(same("18446744073709551615", "18446744073709551615"));
|
||||||
|
CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})"));
|
||||||
|
CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})"));
|
||||||
|
CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})"));
|
||||||
|
CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})"));
|
||||||
|
CHECK(!same("[1, 2]", "[2, 1]"));
|
||||||
|
CHECK(!same("\"a\"", "\"b\""));
|
||||||
|
CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\""));
|
||||||
|
CHECK(!same("null", "false"));
|
||||||
|
CHECK(!same("[]", "{}"));
|
||||||
|
CHECK(ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})").root() == ordered_json_document::parse(R"({"a": 3, "b": 2})").root());
|
||||||
|
CHECK(ordered_json_document::parse(R"({"a": 1, "b": 2})").root() != ordered_json_document::parse(R"({"b": 2, "a": 1})").root());
|
||||||
|
|
||||||
|
// discarded values compare as basic_json's do
|
||||||
|
const json discarded(json::value_t::discarded);
|
||||||
|
CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested
|
||||||
|
CHECK((json_view() == discarded) == (discarded == discarded));
|
||||||
|
CHECK(!(json_view() == json_document::parse("null").root())); // NOLINT(readability-container-size-empty)
|
||||||
|
CHECK(!(json_document::parse("null").root() == discarded));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting")
|
||||||
|
{
|
||||||
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
|
const json_document a = json_document::parse(deep);
|
||||||
|
const json_document b = json_document::parse(deep);
|
||||||
|
CHECK(a.root() == b.root());
|
||||||
|
CHECK(a.root() == json::parse(deep));
|
||||||
|
const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']');
|
||||||
|
CHECK(a.root() != json_document::parse(other).root());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("json_view large objects")
|
||||||
|
{
|
||||||
|
// objects with 128 members or more are looked up with a hash index
|
||||||
|
for (const std::size_t members :
|
||||||
|
{
|
||||||
|
127u, 128u, 129u, 10000u
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(members);
|
||||||
|
std::string text = "{";
|
||||||
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
|
{
|
||||||
|
text += (i != 0 ? ",\"" : "\"") + std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\\n" : "") + "\":" + std::to_string(i);
|
||||||
|
}
|
||||||
|
text += R"(,"":"empty key","k1":"a duplicate of an earlier key"})";
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const json_view v = d.root();
|
||||||
|
const json j = json::parse(text);
|
||||||
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
|
{
|
||||||
|
const std::string key = std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\n" : "");
|
||||||
|
CHECK(v[key].get<std::size_t>() == i);
|
||||||
|
CHECK(v.contains(key));
|
||||||
|
CHECK(v.find(key).key() == key);
|
||||||
|
CHECK(v.at(key).get<std::size_t>() == i);
|
||||||
|
CHECK(!v.contains(key + "x"));
|
||||||
|
}
|
||||||
|
CHECK(v[""].get_string() == "empty key");
|
||||||
|
CHECK(v["k1"].get<int>() == 1); // the first of duplicate keys, as for small objects
|
||||||
|
CHECK(!v.contains("missing"));
|
||||||
|
CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
|
||||||
|
CHECK(v == j);
|
||||||
|
CHECK(v.materialize() == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested, reused, and in arrays")
|
||||||
|
{
|
||||||
|
std::string inner = "{";
|
||||||
|
for (int i = 0; i < 300; ++i)
|
||||||
|
{
|
||||||
|
inner += (i != 0 ? ",\"m" : "\"m") + std::to_string(i) + "\":" + std::to_string(i);
|
||||||
|
}
|
||||||
|
inner += '}';
|
||||||
|
const std::string text = "[" + inner + ",{\"x\":" + inner + "}," + inner + "]";
|
||||||
|
json_document d = json_document::parse(text);
|
||||||
|
CHECK(d.root()[0]["m299"].get<int>() == 299);
|
||||||
|
CHECK(d.root()[1]["x"]["m150"].get<int>() == 150);
|
||||||
|
CHECK(d.root()[2]["m0"].get<int>() == 0);
|
||||||
|
const std::size_t with_index = d.memory_usage();
|
||||||
|
d.read(std::string("{\"small\": 1}"));
|
||||||
|
CHECK(d.root()["small"].get<int>() == 1);
|
||||||
|
d.read(text);
|
||||||
|
CHECK(d.root()[2]["m7"].get<int>() == 7);
|
||||||
|
CHECK(d.memory_usage() >= with_index / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#endif
|
#endif
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -142,6 +143,32 @@ void check_same(const std::string& text)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a string value and a key must be accepted or rejected as json::parse does,
|
||||||
|
// and give its value (cheaper than check_same: the options do not matter)
|
||||||
|
void check_string(const std::string& content)
|
||||||
|
{
|
||||||
|
for (const std::string& text :
|
||||||
|
{
|
||||||
|
"[\"" + content + "\"]", "{\"" + content + "\":1}"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const bool accepted = json::accept(text);
|
||||||
|
for (const bool sentinel :
|
||||||
|
{
|
||||||
|
true, false
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const built b = build(text, false, false, sentinel);
|
||||||
|
if (b.ok != accepted || (accepted && value_of(b) != json::parse(text)))
|
||||||
|
{
|
||||||
|
CAPTURE(text);
|
||||||
|
CHECK(b.ok == accepted);
|
||||||
|
CHECK((b.ok && accepted ? value_of(b) == json::parse(text) : true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// a small deterministic generator of documents
|
// a small deterministic generator of documents
|
||||||
struct generator
|
struct generator
|
||||||
{
|
{
|
||||||
@@ -401,3 +428,81 @@ TEST_CASE("json_view builder")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("json_view builder: strings across vector blocks")
|
||||||
|
{
|
||||||
|
// Strings are scanned 8 or 16 bytes at a time (NEON, SSE2, or SWAR) and
|
||||||
|
// non-ASCII text with the vector UTF-8 check (NEON, SSSE3) or one sequence
|
||||||
|
// at a time. Sequences are placed so that they start at every offset
|
||||||
|
// around the block boundaries of keys (16, 32) and values (8, 24), with
|
||||||
|
// text of several lengths after them.
|
||||||
|
const std::array<std::size_t, 16> prefixes = {{0, 6, 7, 8, 13, 14, 15, 16, 21, 22, 23, 24, 29, 30, 31, 32}};
|
||||||
|
const std::array<std::size_t, 3> suffixes = {{0, 3, 17}};
|
||||||
|
const auto around = [&](const std::string & seq, std::size_t prefix, std::size_t suffix)
|
||||||
|
{
|
||||||
|
return std::string(prefix, 'a') + seq + std::string(suffix, 'b');
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("every two-byte sequence")
|
||||||
|
{
|
||||||
|
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
|
||||||
|
{
|
||||||
|
for (unsigned second = 0; second <= 0xFF; ++second)
|
||||||
|
{
|
||||||
|
if (second == '"' || second == '\\')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string seq = {static_cast<char>(lead), static_cast<char>(second)};
|
||||||
|
check_string(around(seq, prefixes[(lead + second) % 16], suffixes[second % 3]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("three- and four-byte sequences")
|
||||||
|
{
|
||||||
|
const std::array<unsigned, 8> conts = {{0x7F, 0x80, 0x8F, 0x90, 0x9F, 0xA0, 0xBF, 0xC0}};
|
||||||
|
for (unsigned lead = 0xE0; lead <= 0xF7; ++lead)
|
||||||
|
{
|
||||||
|
for (const unsigned b2 : conts)
|
||||||
|
{
|
||||||
|
for (const unsigned b3 : conts)
|
||||||
|
{
|
||||||
|
for (const std::size_t prefix : prefixes)
|
||||||
|
{
|
||||||
|
std::string seq = {static_cast<char>(lead), static_cast<char>(b2), static_cast<char>(b3)};
|
||||||
|
if (lead >= 0xF0)
|
||||||
|
{
|
||||||
|
seq += static_cast<char>(prefix % 2 == 0 ? 0x80 : 0xBF);
|
||||||
|
}
|
||||||
|
check_string(around(seq, prefix, suffixes[prefix % 3]));
|
||||||
|
// cut short before the end of the string
|
||||||
|
check_string(around(seq.substr(0, seq.size() - 1), prefix, suffixes[prefix % 3]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("long runs of text with one damaged byte")
|
||||||
|
{
|
||||||
|
const std::array<const char*, 7> chars = {{"a", "\xc3\xa9", "\xe3\x81\x82", "\xf0\x9f\x98\x80", "\xed\x9f\xbf", "\xef\xbf\xbf", "\xf4\x8f\xbf\xbf"}};
|
||||||
|
const std::array<char, 11> damage = {{'\x80', '\xbf', '\xc0', '\xc1', '\xe0', '\xed', '\xf5', '\xff', '\x1f', '"', '\\'}};
|
||||||
|
std::mt19937 rng(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
|
||||||
|
for (int i = 0; i < 4000; ++i)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
const auto n = rng() % 60;
|
||||||
|
for (unsigned k = 0; k < n; ++k)
|
||||||
|
{
|
||||||
|
text += chars[rng() % chars.size()];
|
||||||
|
}
|
||||||
|
check_string(text);
|
||||||
|
if (!text.empty())
|
||||||
|
{
|
||||||
|
text[rng() % text.size()] = damage[rng() % damage.size()];
|
||||||
|
check_string(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user