mirror of
https://github.com/nlohmann/json.git
synced 2026-09-29 19:20:30 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57b5d56522 | ||
|
|
ebfbbf29cb | ||
|
|
a491cc7663 | ||
|
|
6536c1b869 | ||
|
|
038f448dec | ||
|
|
bf6b5b4719 | ||
|
|
1c7041908d | ||
|
|
20fd4c6a8b | ||
|
|
f57de1aa0a | ||
|
|
401af52511 | ||
|
|
ecce914701 | ||
|
|
1318023103 | ||
|
|
0192171c37 | ||
|
|
1ef0582ed7 | ||
|
|
2aedf52d54 | ||
|
|
bc36fd323b | ||
|
|
9856da2561 | ||
|
|
85f414ae7a | ||
|
|
358970ee7f |
@@ -52,6 +52,7 @@ labels:
|
||||
- "single_include/nlohmann/json_view\\.hpp"
|
||||
- "tests/src/unit-json_view.*"
|
||||
- "tests/src/fuzzer-parse_json_view\\.cpp"
|
||||
- "tests/benchmarks/json_view/.*"
|
||||
- "tools/amalgamate/config_json_view\\.json"
|
||||
- "docs/mkdocs/docs/features/json_view\\.md"
|
||||
- "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/value_t.hpp",
|
||||
"include/nlohmann/detail/view/builder.hpp",
|
||||
"include/nlohmann/detail/view/compare.hpp",
|
||||
"include/nlohmann/detail/view/document_data.hpp",
|
||||
"include/nlohmann/detail/view/errors.hpp",
|
||||
"include/nlohmann/detail/view/input.hpp",
|
||||
@@ -77,8 +78,12 @@ cc_library(
|
||||
"include/nlohmann/detail/view/materialize.hpp",
|
||||
"include/nlohmann/detail/view/node.hpp",
|
||||
"include/nlohmann/detail/view/number.hpp",
|
||||
"include/nlohmann/detail/view/pointer.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/value.hpp",
|
||||
"include/nlohmann/json.hpp",
|
||||
"include/nlohmann/json_fwd.hpp",
|
||||
"include/nlohmann/json_view.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 an adapted version of the Eisel-Lemire algorithm and its table of powers of five 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>`) 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">
|
||||
|
||||
|
||||
@@ -150,10 +150,14 @@ 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::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::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::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::front', 'Method', 'api/basic_json_view/front/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get', 'Method', 'api/basic_json_view/get/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get_string', 'Method', 'api/basic_json_view/get_string/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get_to', 'Method', 'api/basic_json_view/get_to/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_array', 'Method', 'api/basic_json_view/is_array/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_binary', 'Method', 'api/basic_json_view/is_binary/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_boolean', 'Method', 'api/basic_json_view/is_boolean/index.html');
|
||||
@@ -169,12 +173,18 @@ 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::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::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::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_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::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_name', 'Method', 'api/basic_json_view/type_name/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::value', 'Method', 'api/basic_json_view/value/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_document', 'Class', 'api/json_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_view', 'Class', 'api/json_view/index.html');
|
||||
@@ -297,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_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 ('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
|
||||
- [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
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -163,6 +163,8 @@ overload (3).
|
||||
- [get_ref](get_ref.md) get a reference to the stored value
|
||||
- [operator ValueType](operator_ValueType.md) get a value via implicit conversion
|
||||
- [Converting values](../../features/conversions.md) - the type conversions article
|
||||
- [basic_json_view::get](../basic_json_view/get.md) - the same conversion on a zero-copy view (many types are
|
||||
converted without ever building a `basic_json` value)
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ Constant.
|
||||
## See also
|
||||
|
||||
- [get_ptr()](get_ptr.md) get a pointer value
|
||||
- [basic_json_view::get_string](../basic_json_view/get_string.md) - the closest counterpart on a zero-copy view: a
|
||||
string without a copy, but as a view rather than a reference to a value that must already exist
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ Depends on the `json_serializer<ValueType>::from_json()` implementation.
|
||||
- [get_ref](get_ref.md) get a reference to the stored value
|
||||
- [get_ptr](get_ptr.md) get a pointer to the stored value
|
||||
- [Converting values](../../features/conversions.md) - the type conversions article
|
||||
- [basic_json_view::get_to](../basic_json_view/get_to.md) - the same conversion on a zero-copy view
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -166,6 +166,8 @@ Linear.
|
||||
|
||||
- [operator!=](operator_ne.md) compare for inequality
|
||||
- [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
|
||||
|
||||
|
||||
@@ -89,6 +89,12 @@ Linear.
|
||||
--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
|
||||
|
||||
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
|
||||
|
||||
@@ -181,6 +181,7 @@ changes to any JSON value.
|
||||
|
||||
- see [`at`](at.md) for access by reference with range checking
|
||||
- see [`operator[]`](operator%5B%5D.md) for unchecked access by reference
|
||||
- [basic_json_view::value](../basic_json_view/value.md) - the same access on a zero-copy view
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -9,11 +9,15 @@ basic_json_view at(const string_t& key) const;
|
||||
// (2)
|
||||
basic_json_view at(size_type idx) const;
|
||||
basic_json_view at(int idx) const;
|
||||
|
||||
// (3)
|
||||
basic_json_view at(const json_pointer& ptr) const;
|
||||
```
|
||||
|
||||
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see
|
||||
[Notes on duplicate keys](operator[].md#notes)).
|
||||
2. Returns the array element at index `idx`.
|
||||
3. Returns the value a JSON pointer `ptr` refers to, starting at this value.
|
||||
|
||||
## Parameters
|
||||
|
||||
@@ -23,10 +27,14 @@ basic_json_view at(int idx) const;
|
||||
`idx` (in)
|
||||
: index of the element to access
|
||||
|
||||
`ptr` (in)
|
||||
: JSON pointer to the element to access
|
||||
|
||||
## Return value
|
||||
|
||||
1. the value of the first member with key `key`
|
||||
2. the element at index `idx`
|
||||
3. the value `ptr` resolves to, starting at this value
|
||||
|
||||
## Exception safety
|
||||
|
||||
@@ -42,6 +50,20 @@ Strong exception safety: if an exception is thrown, there are no changes to the
|
||||
[`BasicJsonType::at`](../basic_json/at.md):
|
||||
- Throws [`type_error.304`](../../home/exceptions.md#jsonexceptiontype_error304) if the value is not an array.
|
||||
- Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `#!cpp idx >= size()`.
|
||||
3. The function can throw the following exceptions, all with the same message as the corresponding call to
|
||||
[`BasicJsonType::at`](../basic_json/at.md):
|
||||
- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in `ptr`
|
||||
begins with `#!cpp '0'`.
|
||||
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is
|
||||
not a number.
|
||||
- Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if an array index in `ptr`
|
||||
is out of range.
|
||||
- Throws [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if a reference token is
|
||||
`#!cpp "-"` at an array -- `at` never inserts an element, so `#!cpp "-"` is always invalid.
|
||||
- Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a reference token names
|
||||
an object member that does not exist.
|
||||
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
|
||||
because a reference token is used on a primitive value.
|
||||
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
@@ -54,16 +76,20 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
already known from the index, without reading the key bytes -- before comparing its content.
|
||||
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).
|
||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level (as 1.) or the index into the array (as 2.).
|
||||
|
||||
## Notes
|
||||
|
||||
Unlike [`operator[]`](operator[].md), which returns a [discarded](is_discarded.md) view for a missing key or an
|
||||
out-of-range index, `at` always throws -- exactly as `BasicJsonType::at` does, and with the same messages, so
|
||||
existing error handling written against `BasicJsonType::at` keeps working unchanged when switched to a view.
|
||||
existing error handling written against `BasicJsonType::at` keeps working unchanged when switched to a view. This
|
||||
also holds for overload 3: unlike [`operator[]`](operator[].md) with a JSON pointer, which returns a discarded view
|
||||
for a missing key or an out-of-range index, `at` throws for those too (`out_of_range.403`/`out_of_range.401`).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
??? example "Example: (1)/(2) access specified element with bounds checking"
|
||||
|
||||
The example below reads required fields out of a service configuration with `at`, and shows that the exceptions
|
||||
it throws -- for a wrong type and for a missing key -- carry the same messages
|
||||
@@ -79,11 +105,27 @@ existing error handling written against `BasicJsonType::at` keeps working unchan
|
||||
--8<-- "examples/basic_json_view__at.output"
|
||||
```
|
||||
|
||||
??? example "Example: (3) access specified element via JSON pointer with bounds checking"
|
||||
|
||||
The example below shows that `at` with a JSON pointer throws exactly the exceptions, with exactly the messages,
|
||||
that [`BasicJsonType::at`](../basic_json/at.md) throws for the same pointer and the same document.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__at_json_pointer.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__at_json_pointer.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [operator[]](operator[].md) - access specified element (returns a discarded view instead of throwing)
|
||||
- [front](front.md), [back](back.md) - access the first or last element
|
||||
- [`BasicJsonType::at`](../basic_json/at.md) - the corresponding function of `basic_json`
|
||||
- [`json_pointer`](../json_pointer/index.md) - JSON pointer type used by overload 3
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
# <small>nlohmann::basic_json_view::</small>contains
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
bool contains(string_view_t key) const;
|
||||
bool contains(const char* key) const;
|
||||
bool contains(const string_t& key) const;
|
||||
|
||||
// (2)
|
||||
bool contains(const json_pointer& ptr) const;
|
||||
```
|
||||
|
||||
Checks whether the value is an object with a member with key `key`.
|
||||
1. Checks whether the value is an object with a member with key `key`.
|
||||
2. Checks whether a JSON pointer `ptr` can be resolved, starting at this value.
|
||||
|
||||
## Parameters
|
||||
|
||||
`key` (in)
|
||||
: key value to check its existence
|
||||
|
||||
`ptr` (in)
|
||||
: JSON pointer to check its existence
|
||||
|
||||
## Return value
|
||||
|
||||
`#!cpp true` if the value is an object and has a member with key `key`, `#!cpp false` otherwise.
|
||||
1. `#!cpp true` if the value is an object and has a member with key `key`, `#!cpp false` otherwise
|
||||
2. `#!cpp true` if `ptr` can be resolved to a value starting at this view, `#!cpp false` otherwise
|
||||
|
||||
## Exception safety
|
||||
|
||||
@@ -23,22 +32,34 @@ No-throw guarantee: this function never throws exceptions.
|
||||
|
||||
## Complexity
|
||||
|
||||
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
|
||||
known from the index, without reading the key bytes -- before comparing its content.
|
||||
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
|
||||
known from the index, without reading the key bytes -- before comparing its content.
|
||||
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
|
||||
[`at`](at.md#complexity) with a JSON pointer.
|
||||
|
||||
## Notes
|
||||
|
||||
This method always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md)
|
||||
Overload 1 always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md)
|
||||
view.
|
||||
|
||||
!!! info "Postconditions"
|
||||
|
||||
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md).
|
||||
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md). If
|
||||
`#!cpp v.contains(ptr)` returns `#!cpp true`, then `#!cpp v[ptr]` is not discarded and `#!cpp v.at(ptr)` does not
|
||||
throw.
|
||||
|
||||
!!! info "Overload 2 never throws"
|
||||
|
||||
Unlike [`BasicJsonType::contains(const json_pointer&)`](../basic_json/contains.md), which can throw for certain
|
||||
malformed pointers (for instance an empty array-index reference token), overload 2 never throws: a missing key,
|
||||
an out-of-range or malformed array index, a `#!cpp "-"` index, or a reference token used on a primitive all
|
||||
simply make it return `#!cpp false`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
??? example "Example: (1) check with key"
|
||||
|
||||
The example below counts how many of a batch of records carry an optional `retry_of` field, using `contains()`
|
||||
to check without ever materializing a single record of the batch.
|
||||
@@ -53,10 +74,26 @@ view.
|
||||
--8<-- "examples/basic_json_view__contains.output"
|
||||
```
|
||||
|
||||
??? example "Example: (2) check with JSON pointer"
|
||||
|
||||
The example below checks an optional, nested field with a JSON pointer, and shows two pointers that
|
||||
`#!cpp contains()` resolves to `#!cpp false` without throwing.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__contains_json_pointer.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__contains_json_pointer.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [find](find.md) - find a value in an object
|
||||
- [count](count.md) - returns the number of occurrences of a key
|
||||
- [at](at.md), [operator[]](operator[].md) - resolve a JSON pointer and throw, or return a discarded view
|
||||
- [`BasicJsonType::contains`](../basic_json/contains.md) - the corresponding function of `basic_json`
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,130 @@
|
||||
# <small>nlohmann::basic_json_view::</small>get
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
T get() const;
|
||||
```
|
||||
|
||||
Converts the value to `T`.
|
||||
|
||||
For the types below, the conversion works directly on the flat index -- no `BasicJsonType` value is built for it:
|
||||
|
||||
- `#!cpp bool`
|
||||
- arithmetic types other than `#!cpp bool` (from a number; from a boolean, as `#!cpp 0`/`#!cpp 1`, exactly as
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md) converts a boolean)
|
||||
- `#!cpp std::nullptr_t`
|
||||
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) -- a copy of the string
|
||||
- [`string_view_t`](index.md#member-types) -- **no copy**: the returned view points into the document's
|
||||
[`source()`](../basic_json_document/source.md) text, or, for a string that contains escape sequences, into the
|
||||
document's own buffer of decoded strings (see [`get_string()`](get_string.md))
|
||||
- `BasicJsonType` -- equivalent to [`materialize()`](materialize.md)
|
||||
- `basic_json_view` -- returns `#!cpp *this`
|
||||
- `#!cpp std::vector<U, A>` -- element by element, each converted with `#!cpp get<U>()`; `#!cpp
|
||||
std::vector<basic_json_view>` keeps a view of every element instead of a value
|
||||
- `#!cpp std::map<K, V, C, A>` and `#!cpp std::unordered_map<K, V, H, E, A>`, if `K` is constructible from a `#!cpp
|
||||
(const char*, std::size_t)` pair -- member by member, each value converted with `#!cpp get<V>()`; with a repeated
|
||||
key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and
|
||||
[`materialize()`](materialize.md)) does; `#!cpp std::map<std::string, basic_json_view>` keeps views of the members
|
||||
instead of values
|
||||
|
||||
Every other `T` -- `#!cpp std::list`, `#!cpp std::pair`, `#!cpp std::array`, enumerations, user types with a
|
||||
`from_json()`, ... -- is converted by `#!cpp materialize().get<T>()`: the subtree is built into a real `BasicJsonType`
|
||||
value first (as [`BasicJsonType::parse()`](../basic_json/parse.md) would), and converted from there exactly as
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md) would convert it.
|
||||
|
||||
## Template parameters
|
||||
|
||||
`T`
|
||||
: the type to convert the value to
|
||||
|
||||
## Return value
|
||||
|
||||
the value, converted to `T`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- For the directly-converted types listed above (other than `BasicJsonType` and `basic_json_view`, which never
|
||||
throw): throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value's type does not
|
||||
match `T` -- the same exception, with the same message, that [`BasicJsonType::get<T>()`](../basic_json/get.md)
|
||||
throws for the same JSON type and `T`.
|
||||
- For `#!cpp std::vector<U, A>`: throws `type_error.302` if the value is not an array; otherwise, whatever converting
|
||||
an element to `U` throws.
|
||||
- For `#!cpp std::map`/`#!cpp std::unordered_map`: throws `type_error.302` if the value is not an object; otherwise,
|
||||
whatever converting a member to the mapped type throws.
|
||||
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws -- typically `type_error.302`,
|
||||
or whatever a user-provided `from_json()` throws.
|
||||
|
||||
None of the exceptions thrown directly by this function (the first three bullets above) carry a
|
||||
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
|
||||
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
|
||||
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
- `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `basic_json_view`:
|
||||
constant.
|
||||
- `#!cpp std::basic_string<char, Traits, Alloc>`: constant, plus one allocation and a copy of the string's bytes.
|
||||
- `BasicJsonType`: linear in the size of the subtree, see [`materialize()`](materialize.md).
|
||||
- `#!cpp std::vector<U, A>`: linear in the number of elements, times the complexity of converting one element to `U`.
|
||||
- `#!cpp std::map`/`#!cpp std::unordered_map`: linear in the number of members for walking them, plus the container's
|
||||
own insertion cost per member (logarithmic for `#!cpp std::map`, amortized constant for `#!cpp
|
||||
std::unordered_map`), times the complexity of converting one member to the mapped type.
|
||||
- every other `T`: linear in the size of the subtree (building the `BasicJsonType` value), plus the complexity of
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md) on it.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! info "Floating-point values"
|
||||
|
||||
A floating-point `T` is converted from the same digits the lexer would see during `#!cpp BasicJsonType::parse()`,
|
||||
using the same conversion, so the result is bit-for-bit identical to `#!cpp BasicJsonType::parse(text).get<T>()`
|
||||
for the same source text.
|
||||
|
||||
!!! info "Duplicate keys"
|
||||
|
||||
`#!cpp std::map`/`#!cpp std::unordered_map` conversions keep the *last* value of a repeated key, like
|
||||
[`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the opposite
|
||||
of [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md), which resolve to the
|
||||
*first* occurrence (see the [Notes on duplicate keys](operator[].md#notes)).
|
||||
|
||||
!!! info "No pointers, references, or implicit conversion"
|
||||
|
||||
Unlike `BasicJsonType`, `basic_json_view` has no stored value anywhere to hand out a pointer or a reference to, so
|
||||
it provides neither `#!cpp get_ptr()`, `#!cpp get_ref()`, nor `#!cpp operator ValueType()`.
|
||||
[`get_string()`](get_string.md) (equivalently, `#!cpp get<string_view_t>()`) is the zero-copy alternative for
|
||||
strings.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below reads typed fields straight into C++ variables, collects a view of every array element with
|
||||
`#!cpp get<std::vector<basic_json_view>>()` instead of a value, and converts a nested object into a user type
|
||||
through its `from_json()` -- which runs on a `BasicJsonType` value `materialize()` builds for just that one
|
||||
member.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__get.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__get.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [get_to](get_to.md) - convert and write into a passed value
|
||||
- [get_string](get_string.md) - the string, without a copy
|
||||
- [number_token](number_token.md) - a number's token text, without a copy
|
||||
- [materialize](materialize.md) - build the `BasicJsonType` value of this subtree
|
||||
- [`BasicJsonType::get`](../basic_json/get.md) - the corresponding function of `basic_json`
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,71 @@
|
||||
# <small>nlohmann::basic_json_view::</small>get_string
|
||||
|
||||
```cpp
|
||||
string_view_t get_string() const;
|
||||
```
|
||||
|
||||
Returns the string value as a [`string_view_t`](index.md#member-types), without copying it.
|
||||
|
||||
## Return value
|
||||
|
||||
The string, as a [`string_view_t`](index.md#member-types) that points either into the document's
|
||||
[`source()`](../basic_json_document/source.md) text (a string with no escape sequences), or into the document's own
|
||||
buffer of decoded strings (a string that contains escape sequences, such as `#!json "\n"` or `#!json "\u00e9"`, which
|
||||
had to be decoded once when the document was parsed).
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
|
||||
`"type must be string, but is array"`.
|
||||
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
Constant.
|
||||
|
||||
## Notes
|
||||
|
||||
`basic_json_view` has no `BasicJsonType` value stored anywhere, so unlike `BasicJsonType`, it has no `get_ref()` to
|
||||
hand out a reference to a stored `string_t`. `get_string()` (equivalently, [`get<string_view_t>()`](get.md)) is the
|
||||
zero-copy alternative: [`BasicJsonType::get_ref<const string_t&>()`](../basic_json/get_ref.md) is its closest
|
||||
counterpart, except that it returns a view instead of a reference to a value that must already exist.
|
||||
|
||||
The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it -- see the
|
||||
[validity rules](index.md) of `basic_json_view` -- and, for a string with no escapes, for as long as the document's
|
||||
source text.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below pulls one field out of a JSON text that stands in for a large API response, and shows that no
|
||||
`#!cpp std::string` was allocated for it: the returned view still points inside the original buffer. A field that
|
||||
contains an escape sequence cannot point into the original text -- it was decoded once into the document's own
|
||||
buffer instead -- but still avoids a per-field allocation.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__get_string.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__get_string.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [get](get.md) - convert the value to a given type (`#!cpp get<string_view_t>()` is equivalent to this function)
|
||||
- [number_token](number_token.md) - a number's token text, without a copy
|
||||
- [`BasicJsonType::get_ref`](../basic_json/get_ref.md) - the closest counterpart of `basic_json`
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,65 @@
|
||||
# <small>nlohmann::basic_json_view::</small>get_to
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
T& get_to(T& v) const;
|
||||
```
|
||||
|
||||
Converts the value to `T` and assigns it to `v`. Equivalent to
|
||||
|
||||
```cpp
|
||||
v = get<T>();
|
||||
return v;
|
||||
```
|
||||
|
||||
## Template parameters
|
||||
|
||||
`T`
|
||||
: the type to convert the value to
|
||||
|
||||
## Parameters
|
||||
|
||||
`v` (out)
|
||||
: the variable to store the converted value in
|
||||
|
||||
## Return value
|
||||
|
||||
`v`, allowing calls to chain
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, `v` is not modified.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Whatever [`get<T>()`](get.md) throws for the same value and `T`.
|
||||
|
||||
## Complexity
|
||||
|
||||
Whatever [`get<T>()`](get.md) has for the same `T`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below reads several fields of a service configuration directly into existing variables, then uses
|
||||
the returned reference to fold the `#!cpp host`/`#!cpp port` pair into a single string in the same expression.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__get_to.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__get_to.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [get](get.md) - convert the value to a given type
|
||||
- [`BasicJsonType::get_to`](../basic_json/get_to.md) - the corresponding function of `basic_json`
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -20,8 +20,12 @@ Moving the document itself does not invalidate its views: the index is heap-allo
|
||||
`basic_json_document` object.
|
||||
|
||||
`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element
|
||||
access, lookup, iteration, and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on
|
||||
demand. It does not (yet) provide `get<T>()`, JSON Pointer support, `dump()`, or comparison.
|
||||
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
|
||||
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). [`operator==`](operator_eq.md) and
|
||||
[`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
|
||||
|
||||
@@ -44,6 +48,7 @@ demand. It does not (yet) provide `get<T>()`, JSON Pointer support, `dump()`, or
|
||||
- **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
|
||||
- **item** - a (key, value) pair produced by [`items()`](items.md)
|
||||
- [**number_format**](number_format.md) - how [`dump()`](dump.md) writes numbers
|
||||
|
||||
## Member functions
|
||||
|
||||
@@ -72,6 +77,7 @@ demand. It does not (yet) provide `get<T>()`, JSON Pointer support, `dump()`, or
|
||||
|
||||
- [**at**](at.md) - access specified element with bounds checking
|
||||
- [**operator[]**](operator[].md) - access specified element
|
||||
- [**value**](value.md) - access specified element with default value
|
||||
- [**front**](front.md) - access the first element
|
||||
- [**back**](back.md) - access the last element
|
||||
|
||||
@@ -96,8 +102,22 @@ demand. It does not (yet) provide `get<T>()`, JSON Pointer support, `dump()`, or
|
||||
|
||||
### Conversion
|
||||
|
||||
- [**get**](get.md) - get a value
|
||||
- [**get_to**](get_to.md) - get a value and write it to a destination
|
||||
- [**get_string**](get_string.md) - get a string value 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
|
||||
|
||||
### 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_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.
|
||||
@@ -0,0 +1,74 @@
|
||||
# <small>nlohmann::basic_json_view::</small>number_token
|
||||
|
||||
```cpp
|
||||
string_view_t number_token() const;
|
||||
```
|
||||
|
||||
Returns the number exactly as it appears in the source text, without parsing or rounding it.
|
||||
|
||||
## Return value
|
||||
|
||||
The number's token text, as a [`string_view_t`](index.md#member-types) into the document's
|
||||
[`source()`](../basic_json_document/source.md) text -- for example `#!cpp "1.50"`, `#!cpp "1E2"`, `#!cpp "-0"`, or an
|
||||
integer literal with more digits than any number type holds (such as a 30-digit integer, which [`get<T>()`](get.md)
|
||||
and [`materialize()`](materialize.md) can only represent approximately, as a `number_float_t`).
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
|
||||
`"type must be number, but is string"`.
|
||||
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
Constant.
|
||||
|
||||
## Notes
|
||||
|
||||
`BasicJsonType` has no counterpart to this function: once a number is parsed into `number_integer_t`,
|
||||
`number_unsigned_t`, or `number_float_t`, its original textual form (leading zeros aside, which are already rejected
|
||||
by the grammar; trailing zeros in the fraction; the case and sign of the exponent; ...) is gone. `number_token()` is
|
||||
useful precisely where that form must survive -- a price or an identifier that must be reproduced exactly, or a
|
||||
number too large for any of `BasicJsonType`'s number types to hold without loss.
|
||||
|
||||
The returned [`string_view_t`](index.md#member-types) always points into the document's
|
||||
[`source()`](../basic_json_document/source.md) text -- numbers are never decoded into the document's separate string
|
||||
buffer -- and is valid exactly as long as that text is, see the [validity rules](index.md) of `basic_json_view`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below keeps a price and an order ID exactly as they were written in an incoming order, where
|
||||
converting them with [`get<T>()`](get.md) would lose information: the price picks up floating-point rounding, and
|
||||
the order ID -- more digits than a 64-bit integer holds -- can only be approximated as a `#!cpp double` once
|
||||
`#!cpp materialize()`d.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__number_token.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__number_token.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [get](get.md) - convert the value to a given type
|
||||
- [get_string](get_string.md) - the string, without a copy
|
||||
- [`BasicJsonType::number_integer_t`](../basic_json/number_integer_t.md),
|
||||
[`number_unsigned_t`](../basic_json/number_unsigned_t.md), [`number_float_t`](../basic_json/number_float_t.md) - the
|
||||
number types `#!cpp get<T>()` and `materialize()` convert into
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -9,12 +9,18 @@ basic_json_view operator[](const string_t& key) const;
|
||||
// (2)
|
||||
basic_json_view operator[](size_type idx) const;
|
||||
basic_json_view operator[](int idx) const;
|
||||
|
||||
// (3)
|
||||
basic_json_view operator[](const json_pointer& ptr) const;
|
||||
```
|
||||
|
||||
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see
|
||||
the [Notes](#notes) below) -- or a [discarded](is_discarded.md) view if there is no such member.
|
||||
2. Returns the array element at index `idx`, or a [discarded](is_discarded.md) view if `idx` is out of range. (The
|
||||
`#!cpp int` overload only exists so that an integer literal is not ambiguous between this overload and 1.)
|
||||
3. Returns the value a JSON pointer `ptr` refers to, starting at this value, or a [discarded](is_discarded.md) view
|
||||
wherever resolving it further is not possible without inserting into or extending the document (see
|
||||
[Return value](#return-value) and [Exceptions](#exceptions) below).
|
||||
|
||||
## Parameters
|
||||
|
||||
@@ -24,11 +30,18 @@ basic_json_view operator[](int idx) const;
|
||||
`idx` (in)
|
||||
: index of the element to access
|
||||
|
||||
`ptr` (in)
|
||||
: JSON pointer to the element to access
|
||||
|
||||
## Return value
|
||||
|
||||
1. the value of the first member with key `key`, or a discarded view if `#!cpp is_object()` is `#!cpp false` or no
|
||||
member has this key
|
||||
2. the element at index `idx`, or a discarded view if `#!cpp is_array()` is `#!cpp false` or `#!cpp idx >= size()`
|
||||
3. the value `ptr` resolves to, starting at this value, or a discarded view for exactly the reference tokens where the
|
||||
**const** overload of [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) invokes undefined behavior for
|
||||
the same pointer and the same document: an object member that does not exist, or an array index that is out of
|
||||
range
|
||||
|
||||
## Exception safety
|
||||
|
||||
@@ -42,9 +55,19 @@ Strong exception safety: if an exception is thrown, there are no changes to the
|
||||
2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an array --
|
||||
the same exception, with the same message, that the **const** overload of
|
||||
[`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a numeric argument on a non-array value.
|
||||
3. Throws the same exceptions, with the same messages, that the **const** overload of
|
||||
[`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for the same pointer and the same document:
|
||||
- [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if a reference token is `#!cpp "-"`
|
||||
at an array.
|
||||
- [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token cannot be
|
||||
resolved because it is used on a primitive value.
|
||||
- [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in `ptr` begins
|
||||
with `#!cpp '0'`.
|
||||
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
|
||||
number.
|
||||
|
||||
Neither exception carries a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType`
|
||||
value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
@@ -55,6 +78,8 @@ value to point at, so the exception is created without one, even if `BasicJsonTy
|
||||
different length than `key` is rejected without touching the source text.
|
||||
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).
|
||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level (as 1.) or the index into the array (as 2.).
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -74,9 +99,18 @@ document.
|
||||
early. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
|
||||
duplicates, in document order. See the example below and [`size()`](size.md#notes).
|
||||
|
||||
!!! info "JSON pointer resolution"
|
||||
|
||||
Overload 3 walks `ptr` one reference token at a time, starting at this value, the same way [`at`](at.md) and
|
||||
[`contains`](contains.md) do. It only ever returns a discarded view where the **const** overload of
|
||||
`BasicJsonType::operator[]` would be undefined behavior for the same pointer -- a missing object member or an
|
||||
out-of-range array index -- and still throws for every other way `ptr` can fail to resolve. See
|
||||
[`at`](at.md#exceptions) for the checked version, which throws in every case instead, and
|
||||
[`contains`](contains.md) for a version that never throws.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
??? example "Example: (1)/(2) access specified element"
|
||||
|
||||
The example below reads a couple of fields out of a batch of user records without ever materializing a full
|
||||
`BasicJsonType` value for the batch. `operator[]` is used both to look up an optional object member and to index
|
||||
@@ -93,12 +127,29 @@ document.
|
||||
--8<-- "examples/basic_json_view__operator[].output"
|
||||
```
|
||||
|
||||
??? example "Example: (3) access specified element via JSON pointer"
|
||||
|
||||
The example below reaches straight into one deeply nested field of a large document with a single JSON pointer,
|
||||
without ever building a tree for the rest of it, and shows the discarded-view and throwing outcomes of a pointer
|
||||
that cannot be fully resolved.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__operator[]_json_pointer.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__operator[]_json_pointer.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [at](at.md) - access specified element with bounds checking (throws instead of returning a discarded view)
|
||||
- [front](front.md), [back](back.md) - access the first or last element
|
||||
- [find](find.md), [contains](contains.md) - look up a member without throwing
|
||||
- [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) - the corresponding function of `basic_json`
|
||||
- [`json_pointer`](../json_pointer/index.md) - JSON pointer type used by overload 3
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,131 @@
|
||||
# <small>nlohmann::basic_json_view::</small>value
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
template<typename T>
|
||||
T value(string_view_t key, const T& default_value) const;
|
||||
string_t value(string_view_t key, const char* default_value) const;
|
||||
|
||||
// (2)
|
||||
template<typename T>
|
||||
T value(const json_pointer& ptr, const T& default_value) const;
|
||||
string_t value(const json_pointer& ptr, const char* default_value) const;
|
||||
```
|
||||
|
||||
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see
|
||||
[Notes on duplicate keys](operator[].md#notes)) -- converted to `T`, or `default_value` if there is no such member.
|
||||
2. Returns the value a JSON pointer `ptr` refers to, starting at this value, converted to `T`, or `default_value` if
|
||||
`ptr` cannot be resolved.
|
||||
|
||||
Both overloads have a dedicated `#!cpp const char*` overload, so `#!cpp v.value(key, "default")` (and the JSON pointer
|
||||
equivalent) deduce `string_t`, not `const char*`, for their return type and for the comparison used to pick between
|
||||
`key` and `default_value`.
|
||||
|
||||
## Template parameters
|
||||
|
||||
`T`
|
||||
: the type to convert the found value to; also the type of `default_value`
|
||||
|
||||
## Parameters
|
||||
|
||||
`key` (in)
|
||||
: object key of the element to access
|
||||
|
||||
`ptr` (in)
|
||||
: JSON pointer to the element to access
|
||||
|
||||
`default_value` (in)
|
||||
: the value to return if `key`/`ptr` resolves to no value
|
||||
|
||||
## Return value
|
||||
|
||||
1. the first member with key `key`, converted to `T`, or `default_value`
|
||||
2. the value `ptr` resolves to, converted to `T`, or `default_value`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
1. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the value is not an object --
|
||||
the same exception, with the same message, that [`BasicJsonType::value`](../basic_json/value.md) throws for the
|
||||
same call. If a member with key `key` is found, throws whatever converting it to `T` throws (typically
|
||||
[`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302), with the same message
|
||||
[`BasicJsonType::value`](../basic_json/value.md) throws for the same mismatch); a missing member never throws.
|
||||
2. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if this value -- not the value `ptr`
|
||||
resolves to -- is neither an object nor an array. Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106)
|
||||
or [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if `ptr` contains a malformed array
|
||||
index. If `ptr` resolves to a value, throws whatever converting it to `T` throws. Every other way `ptr` can fail to
|
||||
resolve -- a missing key, an out-of-range or "`-`" array index, an unresolvable token on a primitive -- yields
|
||||
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
|
||||
`out_of_range` and returns `default_value`.
|
||||
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
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
|
||||
`T` (see [`get`](get.md)).
|
||||
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
|
||||
[`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value
|
||||
to `T`.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! info "Differences to `at` and `operator[]`"
|
||||
|
||||
Unlike [`at`](at.md), this function does not throw if `key`/`ptr` resolves to no value. Unlike
|
||||
[`operator[]`](operator[].md), it never returns a [discarded](is_discarded.md) view -- it always returns a `T` --
|
||||
and it is available on any view, since it never needs to insert a missing element the way the non-const
|
||||
`BasicJsonType::operator[]` would.
|
||||
|
||||
!!! info "Which values can be asked"
|
||||
|
||||
As for [`BasicJsonType::value`](../basic_json/value.md), the key overload (1) requires an object, and the JSON
|
||||
pointer overload (2) an object or an array.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: (1) access specified object element with default value"
|
||||
|
||||
The example below reads a couple of optional configuration fields with a default, so that a missing key never
|
||||
needs a `#!cpp try`/`#!cpp catch` of its own.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__value.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__value.output"
|
||||
```
|
||||
|
||||
??? example "Example: (2) access specified element via JSON pointer with default value"
|
||||
|
||||
The example below reads an optional, nested configuration value with a default, given as a JSON pointer.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__value_json_pointer.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_view__value_json_pointer.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [at](at.md) - access specified element with bounds checking (throws instead of returning a default value)
|
||||
- [operator[]](operator[].md) - access specified element (returns a discarded view instead of a default value)
|
||||
- [`BasicJsonType::value`](../basic_json/value.md) - the corresponding function of `basic_json`
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -33,6 +33,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_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_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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
- [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
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_pointer = nlohmann::json::json_pointer;
|
||||
|
||||
int main()
|
||||
{
|
||||
json_document doc = json_document::parse(R"({"region": "eu", "servers": ["eu-1", "eu-2"]})");
|
||||
const auto root = doc.root();
|
||||
|
||||
std::cout << root.at(json_pointer("/servers/1")).materialize().dump() << '\n';
|
||||
|
||||
// at() throws for every resolution failure -- with the very same
|
||||
// message json::at(ptr) would throw for the same pointer and the same
|
||||
// document
|
||||
try
|
||||
{
|
||||
static_cast<void>(root.at(json_pointer("/servers/5")));
|
||||
}
|
||||
catch (const nlohmann::json::out_of_range& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
static_cast<void>(root.at(json_pointer("/missing")));
|
||||
}
|
||||
catch (const nlohmann::json::out_of_range& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
"eu-2"
|
||||
[json.exception.out_of_range.401] array index 5 is out of range
|
||||
[json.exception.out_of_range.403] key 'missing' not found
|
||||
@@ -0,0 +1,39 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_pointer = nlohmann::json::json_pointer;
|
||||
|
||||
int main()
|
||||
{
|
||||
// "retry_of" is only present on some records, nested a level down
|
||||
// inside "meta"
|
||||
json_document batch = json_document::parse(R"(
|
||||
[
|
||||
{"id": 1, "meta": {}},
|
||||
{"id": 2, "meta": {"retry_of": 1}}
|
||||
]
|
||||
)");
|
||||
|
||||
const auto records = batch.root();
|
||||
const json_pointer retry_of("/meta/retry_of");
|
||||
for (std::size_t i = 0; i < records.size(); ++i)
|
||||
{
|
||||
const auto record = records[i];
|
||||
if (record.contains(retry_of))
|
||||
{
|
||||
std::cout << "record " << i << " is a retry of " << record[retry_of].get<int>() << '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "record " << i << " is original\n";
|
||||
}
|
||||
}
|
||||
|
||||
// contains() with a JSON pointer never throws -- not even for a
|
||||
// pointer that indexes into a primitive ("/0/id/x") or uses a
|
||||
// malformed array index ("/01"), either of which would need a
|
||||
// try/catch with json::contains(ptr)
|
||||
std::cout << std::boolalpha << records.contains(json_pointer("/0/id/x")) << '\n';
|
||||
std::cout << std::boolalpha << records.contains(json_pointer("/01")) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
record 0 is original
|
||||
record 1 is a retry of 1
|
||||
false
|
||||
false
|
||||
@@ -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,55 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_view = nlohmann::json_view;
|
||||
|
||||
// address has no direct conversion in get<T>(), so get<address>() falls back
|
||||
// to materialize().get<address>() -- a real nlohmann::json value is built
|
||||
// for just this one member, and its own from_json() runs on that
|
||||
struct address
|
||||
{
|
||||
std::string city;
|
||||
int zip = 0;
|
||||
};
|
||||
|
||||
void from_json(const nlohmann::json& j, address& a)
|
||||
{
|
||||
j.at("city").get_to(a.city);
|
||||
j.at("zip").get_to(a.zip);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
json_document doc = json_document::parse(R"(
|
||||
{
|
||||
"name": "Alice",
|
||||
"active": true,
|
||||
"orders": [1, 2, 3],
|
||||
"address": {"city": "Berlin", "zip": 10115}
|
||||
}
|
||||
)");
|
||||
const json_view customer = doc.root();
|
||||
|
||||
// read typed fields straight into C++ variables -- none of these build
|
||||
// a nlohmann::json value
|
||||
const std::string name = customer["name"].get<std::string>();
|
||||
const bool active = customer["active"].get<bool>();
|
||||
std::cout << name << (active ? " (active)" : " (inactive)") << '\n';
|
||||
|
||||
// std::vector<json_view> keeps views of the array elements instead of
|
||||
// copies of their values
|
||||
bool first = true;
|
||||
for (const json_view order : customer["orders"].get<std::vector<json_view>>())
|
||||
{
|
||||
std::cout << (first ? "" : " ") << order.get<int>();
|
||||
first = false;
|
||||
}
|
||||
std::cout << '\n';
|
||||
|
||||
// everything else goes through materialize()
|
||||
const address a = customer["address"].get<address>();
|
||||
std::cout << a.city << ' ' << a.zip << '\n';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Alice (active)
|
||||
1 2 3
|
||||
Berlin 10115
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
#include <string>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_view = nlohmann::json_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a "large response" stand-in: only the "id" field is ever read out of it
|
||||
const std::string text =
|
||||
R"({"id": "8f14e45f-ceea-467e-bb92-963f5e3c7a08", "note": "created via API\n", "payload": "..."})";
|
||||
const json_document doc = json_document::parse(text);
|
||||
const json_view response = doc.root();
|
||||
|
||||
const json_view::string_view_t id = response["id"].get_string();
|
||||
std::cout << id << '\n';
|
||||
|
||||
// no std::string was allocated for "id": its bytes still live inside
|
||||
// the original buffer, so id's data lies inside [text.data(),
|
||||
// text.data() + text.size())
|
||||
const bool id_in_source = id.data() >= text.data() && id.data() + id.size() <= text.data() + text.size();
|
||||
std::cout << std::boolalpha << id_in_source << '\n';
|
||||
|
||||
// "note" contains an escape sequence ('\n'), so it was decoded once
|
||||
// into the document's own buffer -- get_string() still avoids a copy
|
||||
// into a new std::string, but the bytes no longer live inside "text"
|
||||
const json_view::string_view_t note = response["note"].get_string();
|
||||
const bool note_in_source = note.data() >= text.data() && note.data() + note.size() <= text.data() + text.size();
|
||||
std::cout << std::boolalpha << note_in_source << '\n';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
8f14e45f-ceea-467e-bb92-963f5e3c7a08
|
||||
true
|
||||
false
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
#include <string>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_view = nlohmann::json_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
json_document doc = json_document::parse(R"({"host": "db.example.com", "port": 5432, "ssl": true})");
|
||||
const json_view config = doc.root();
|
||||
|
||||
// get_to() writes directly into existing variables -- handy for filling
|
||||
// in the members of a struct one field at a time, without an
|
||||
// intermediate value from get<T>() for each one
|
||||
std::string host;
|
||||
int port = 0;
|
||||
bool ssl = false;
|
||||
config["host"].get_to(host);
|
||||
config["port"].get_to(port);
|
||||
config["ssl"].get_to(ssl);
|
||||
std::cout << host << ':' << port << (ssl ? " (tls)" : "") << '\n';
|
||||
|
||||
// the return value is a reference to the argument, so a call can be
|
||||
// used directly in a larger expression
|
||||
std::string other_host;
|
||||
std::cout << config["host"].get_to(other_host).size() << '\n';
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
db.example.com:5432 (tls)
|
||||
14
|
||||
@@ -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,29 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_view = nlohmann::json_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a price and an order id from an incoming order -- both need to be
|
||||
// reproduced exactly, e.g. for an invoice or an audit log
|
||||
json_document doc = json_document::parse(R"(
|
||||
{"price": 19.90, "order_id": 1234567890123456789012345, "quantity": 3}
|
||||
)");
|
||||
const json_view order = doc.root();
|
||||
|
||||
// number_token() returns the number exactly as written in the source
|
||||
std::cout << order["price"].number_token() << '\n';
|
||||
std::cout << order["order_id"].number_token() << '\n';
|
||||
|
||||
// get<double>() converts it instead -- the exact source text is gone:
|
||||
// "19.90" becomes the double closest to 19.9, printed without the
|
||||
// trailing zero, and the 25-digit order id -- far beyond any 64-bit
|
||||
// integer -- can only be approximated as a double
|
||||
std::cout << order["price"].get<double>() << '\n';
|
||||
std::cout << order.materialize()["order_id"].dump() << '\n';
|
||||
|
||||
// an ordinary quantity has nothing to lose either way
|
||||
std::cout << order["quantity"].number_token() << " == " << order["quantity"].get<int>() << '\n';
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
19.90
|
||||
1234567890123456789012345
|
||||
19.9
|
||||
1.2345678901234568e+24
|
||||
3 == 3
|
||||
@@ -0,0 +1,47 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_pointer = nlohmann::json::json_pointer;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a larger document; operator[] with a JSON pointer reaches straight to
|
||||
// one deeply nested field, without ever building a tree for the rest
|
||||
json_document doc = json_document::parse(R"(
|
||||
{
|
||||
"region": {
|
||||
"servers": [
|
||||
{"name": "eu-1", "metrics": {"cpu": 0.42}},
|
||||
{"name": "eu-2", "metrics": {"cpu": 0.71}}
|
||||
]
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
const auto root = doc.root();
|
||||
std::cout << root[json_pointer("/region/servers/1/metrics/cpu")].materialize().dump() << '\n';
|
||||
|
||||
// a missing key or an out-of-range index along the path gives a
|
||||
// discarded view, exactly where const json::operator[] would be
|
||||
// undefined behavior for the same pointer
|
||||
if (const auto missing = root[json_pointer("/region/servers/5/metrics/cpu")])
|
||||
{
|
||||
std::cout << missing.materialize().dump() << '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "no such server\n";
|
||||
}
|
||||
|
||||
// indexing into a primitive still throws, as basic_json::operator[]
|
||||
// does for the same pointer
|
||||
try
|
||||
{
|
||||
static_cast<void>(root[json_pointer("/region/servers/0/name/x")]);
|
||||
}
|
||||
catch (const nlohmann::json::out_of_range& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
0.71
|
||||
no such server
|
||||
[json.exception.out_of_range.404] unresolved reference token 'x'
|
||||
@@ -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
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
#include <string>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_view = nlohmann::json_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
json_document doc = json_document::parse(R"({"server": {"host": "localhost"}})");
|
||||
const json_view server = doc.root()["server"];
|
||||
|
||||
// "port" is missing -- value() returns the default instead of
|
||||
// throwing, so optional configuration fields never need their own
|
||||
// try/catch
|
||||
std::cout << server.value("host", std::string("0.0.0.0")) << '\n';
|
||||
std::cout << server.value("port", 8080) << '\n';
|
||||
|
||||
// a present but wrong-typed default still throws -- value() only
|
||||
// replaces "not found", not "wrong type", exactly as basic_json::value
|
||||
try
|
||||
{
|
||||
static_cast<void>(server.value("host", 0));
|
||||
}
|
||||
catch (const nlohmann::json::type_error& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
localhost
|
||||
8080
|
||||
[json.exception.type_error.302] type must be number, but is string
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
using json_view = nlohmann::json_view;
|
||||
using json_pointer = nlohmann::json::json_pointer;
|
||||
|
||||
int main()
|
||||
{
|
||||
json_document doc = json_document::parse(R"({"server": {"host": "localhost", "limits": {"connections": 100}}})");
|
||||
const json_view config = doc.root();
|
||||
|
||||
// a nested, optional setting read with a default -- no exception, even
|
||||
// though "timeout" is missing several levels down
|
||||
std::cout << config.value(json_pointer("/server/limits/connections"), 10) << '\n';
|
||||
std::cout << config.value(json_pointer("/server/limits/timeout"), 30) << '\n';
|
||||
|
||||
// an out-of-range array index also falls back to the default
|
||||
json_document list_doc = json_document::parse(R"({"servers": ["a", "b"]})");
|
||||
std::cout << list_doc.root().value(json_pointer("/servers/5"), std::string("none")) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
100
|
||||
30
|
||||
none
|
||||
@@ -139,8 +139,53 @@ 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
|
||||
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
||||
`BasicJsonType` was built.
|
||||
- **`get<T>()`, JSON Pointer, `dump()`, and comparison are not (yet) provided** by `basic_json_view`. For now,
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can do those things with.
|
||||
- **Ordering comparisons are not provided** by `basic_json_view` -- there is no `#!cpp operator<`.
|
||||
[`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
|
||||
|
||||
[`get<T>()`](../api/basic_json_view/get.md) converts many `T` directly from the flat index, without ever building a
|
||||
`basic_json` value for the conversion: `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`,
|
||||
`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json`/`ordered_json` (via
|
||||
[`materialize()`](../api/basic_json_view/materialize.md)), `basic_json_view` itself, `#!cpp std::vector<U>`, and
|
||||
`#!cpp std::map`/`#!cpp std::unordered_map` with string-like keys. Every other type -- `#!cpp std::list`,
|
||||
`#!cpp std::pair`, `#!cpp std::array`, enumerations, user types with a `from_json()` -- goes through
|
||||
[`materialize()`](../api/basic_json_view/materialize.md)`.get<T>()` instead: the subtree is built into a real
|
||||
`basic_json` value first, exactly as [`parse()`](../api/basic_json/parse.md) would, and converted from there.
|
||||
|
||||
Two conversions never copy at all:
|
||||
|
||||
- [`get_string()`](../api/basic_json_view/get_string.md) (equivalently, `#!cpp get<string_view_t>()`) returns a
|
||||
string as a `string_view_t` pointing into the document's [`source()`](../api/basic_json_document/source.md) text --
|
||||
or, for a string that contains escape sequences, into the document's own buffer of decoded strings -- instead of
|
||||
allocating a new `#!cpp std::string`.
|
||||
- [`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written in the
|
||||
source, e.g. `#!cpp "1.50"`, `#!cpp "1E2"`, or an integer with more digits than any number type holds, instead of
|
||||
rounding it into a `#!cpp double`/`#!cpp int64_t` the way `#!cpp get<T>()` (and
|
||||
[`basic_json::parse()`](../api/basic_json/parse.md)) would.
|
||||
|
||||
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`
|
||||
|
||||
|
||||
@@ -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 and its table of powers of five 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>`) 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,10 +253,14 @@ nav:
|
||||
- 'cend': api/basic_json_view/cend.md
|
||||
- 'contains': api/basic_json_view/contains.md
|
||||
- 'count': api/basic_json_view/count.md
|
||||
- 'dump': api/basic_json_view/dump.md
|
||||
- 'empty': api/basic_json_view/empty.md
|
||||
- 'end': api/basic_json_view/end.md
|
||||
- 'find': api/basic_json_view/find.md
|
||||
- 'front': api/basic_json_view/front.md
|
||||
- 'get': api/basic_json_view/get.md
|
||||
- 'get_string': api/basic_json_view/get_string.md
|
||||
- 'get_to': api/basic_json_view/get_to.md
|
||||
- 'is_array': api/basic_json_view/is_array.md
|
||||
- 'is_binary': api/basic_json_view/is_binary.md
|
||||
- 'is_boolean': api/basic_json_view/is_boolean.md
|
||||
@@ -272,12 +276,18 @@ nav:
|
||||
- 'is_structured': api/basic_json_view/is_structured.md
|
||||
- 'items': api/basic_json_view/items.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
|
||||
- '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_eq.md
|
||||
- 'operator!=': api/basic_json_view/operator_ne.md
|
||||
- 'size': api/basic_json_view/size.md
|
||||
- 'source_offset': api/basic_json_view/source_offset.md
|
||||
- 'type': api/basic_json_view/type.md
|
||||
- 'type_name': api/basic_json_view/type_name.md
|
||||
- 'value': api/basic_json_view/value.md
|
||||
- byte_container_with_subtype:
|
||||
- 'Overview': api/byte_container_with_subtype/index.md
|
||||
- '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md
|
||||
@@ -358,6 +368,8 @@ nav:
|
||||
- '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_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_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
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct json_pointer_access;
|
||||
} // namespace detail
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/
|
||||
template<typename RefStringType>
|
||||
@@ -42,6 +47,8 @@ class json_pointer
|
||||
template<typename>
|
||||
friend class json_pointer;
|
||||
|
||||
friend struct detail::json_pointer_access;
|
||||
|
||||
template<typename T>
|
||||
struct string_t_helper
|
||||
{
|
||||
@@ -1164,4 +1171,18 @@ inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace detail
|
||||
{
|
||||
/// the reference tokens of a json_pointer, for code that resolves pointers
|
||||
/// without a basic_json value (such as the zero-copy view)
|
||||
struct json_pointer_access
|
||||
{
|
||||
template<typename RefStringType>
|
||||
static const std::vector<typename json_pointer<RefStringType>::string_t>& reference_tokens(const json_pointer<RefStringType>& ptr) noexcept
|
||||
{
|
||||
return ptr.reference_tokens;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -492,7 +492,7 @@ class builder
|
||||
switch (cur()) \
|
||||
{ \
|
||||
case '"': \
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!string())) { return false; } \
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!string<true>())) { return false; } \
|
||||
goto NEXT; \
|
||||
case '{': \
|
||||
open(value_t::object); \
|
||||
@@ -576,7 +576,7 @@ obj_key:
|
||||
{
|
||||
return fail(error_code::expected_key);
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!string()))
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!string<false>()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -679,7 +679,7 @@ root_done:
|
||||
switch (cur())
|
||||
{
|
||||
case '"':
|
||||
return string();
|
||||
return string<true>();
|
||||
case 't':
|
||||
return literal("true", 4, value_t::boolean, node_flags::is_true);
|
||||
case 'f':
|
||||
@@ -965,12 +965,13 @@ indent_done:
|
||||
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()
|
||||
{
|
||||
++p; // opening quote
|
||||
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 == '"'))
|
||||
{
|
||||
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
|
||||
@@ -40,6 +40,12 @@ namespace view
|
||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
/// a parse error without a position (as those of json_pointer)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throw the exception BasicJsonType::parse would throw for this input
|
||||
|
||||
|
||||
@@ -19,3 +19,8 @@
|
||||
#undef NLOHMANN_VIEW_THROW
|
||||
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
#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;
|
||||
break;
|
||||
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;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
|
||||
@@ -8,12 +8,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cfloat> // FLT_EVAL_METHOD
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint64_t
|
||||
#include <cstring> // memcpy
|
||||
#include <string> // string
|
||||
#include <type_traits> // integral_constant, is_same
|
||||
|
||||
#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/scan.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -68,6 +75,96 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
||||
return v;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the double 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. The result is correctly rounded (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.
|
||||
|
||||
@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 double layout_double(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;
|
||||
}
|
||||
|
||||
double result = 0;
|
||||
if (w != 0)
|
||||
{
|
||||
#if !defined(FLT_EVAL_METHOD) || FLT_EVAL_METHOD == 0
|
||||
static const std::array<double, 23> pow10 = {{1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}};
|
||||
if (q >= -22 && q <= 22 && w <= (std::uint64_t{1} << 53))
|
||||
{
|
||||
// Clinger's fast path: both operands exact, one rounding
|
||||
result = static_cast<double>(w);
|
||||
result = q < 0 ? result / pow10[static_cast<std::size_t>(-q)] : result * pow10[static_cast<std::size_t>(q)];
|
||||
return negative ? -result : result;
|
||||
}
|
||||
#endif
|
||||
const std::uint64_t bits = eisel_lemire(q, w);
|
||||
std::memcpy(&result, &bits, sizeof(result));
|
||||
}
|
||||
return negative ? -result : result;
|
||||
}
|
||||
|
||||
/// the value of the float token of a node, as parse() converts it; 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::is_same<FloatType, double> {});
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
FloatType float_value(const document_data& d, const node& n, std::true_type /*double*/)
|
||||
{
|
||||
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_double(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 detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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> // uint64_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// what resolving a JSON pointer does where it cannot continue
|
||||
enum class pointer_mode
|
||||
{
|
||||
unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined
|
||||
checked, ///< as basic_json::at(): out_of_range.401/403
|
||||
value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used)
|
||||
contains, ///< as basic_json::contains(): no exceptions at all
|
||||
};
|
||||
|
||||
/// the outcome of reading an array index from a reference token
|
||||
enum class index_status
|
||||
{
|
||||
ok,
|
||||
leading_zero, ///< parse_error.106
|
||||
not_number, ///< parse_error.109
|
||||
unresolved, ///< out_of_range.404
|
||||
too_large, ///< out_of_range.410
|
||||
};
|
||||
|
||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||
/// but reports errors instead of throwing them
|
||||
template<typename StringType>
|
||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||
{
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_status::leading_zero;
|
||||
}
|
||||
if (s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))
|
||||
{
|
||||
return index_status::not_number;
|
||||
}
|
||||
if (s.empty())
|
||||
{
|
||||
return index_status::unresolved;
|
||||
}
|
||||
std::uint64_t v = 0;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0';
|
||||
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
|
||||
{
|
||||
return index_status::unresolved; // not a number, or beyond unsigned long long
|
||||
}
|
||||
v = (v * 10) + d;
|
||||
}
|
||||
if (v >= static_cast<std::uint64_t>((std::numeric_limits<std::size_t>::max)()))
|
||||
{
|
||||
return index_status::too_large;
|
||||
}
|
||||
idx = static_cast<std::size_t>(v);
|
||||
return index_status::ok;
|
||||
}
|
||||
|
||||
/// throws the exception json_pointer::array_index throws for this status
|
||||
template<typename StringType>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case index_status::leading_zero:
|
||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
||||
case index_status::not_number:
|
||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
||||
case index_status::too_large:
|
||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||
case index_status::unresolved:
|
||||
case index_status::ok:
|
||||
default:
|
||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief resolve the reference tokens of a JSON pointer, starting at a view
|
||||
|
||||
The exceptions are those basic_json throws for the same pointer; where
|
||||
basic_json's behavior is undefined (a missing key or an index out of range
|
||||
with const operator[]), the result is a discarded view.
|
||||
*/
|
||||
template<typename View, typename Tokens>
|
||||
View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
using string_view_t = typename View::string_view_t;
|
||||
const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked;
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
if (cur.is_object())
|
||||
{
|
||||
const auto it = cur.find(string_view_t(token.data(), token.size()));
|
||||
if (it == cur.end())
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
cur = *it;
|
||||
}
|
||||
else if (cur.is_array())
|
||||
{
|
||||
if (token.size() == 1 && token[0] == '-')
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
std::size_t idx = 0;
|
||||
const index_status status = array_index(token, idx);
|
||||
if (status != index_status::ok)
|
||||
{
|
||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||
{
|
||||
throw_array_index_error(status, token);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
if (idx >= cur.size())
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
cur = cur[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <nlohmann/json.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
|
||||
// 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,
|
||||
/// 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
|
||||
/// constants in predicted branches (most strings are short); longer runs
|
||||
/// continue eight bytes at a time.
|
||||
/// bytes are checked one by one, so that the position advances by constants
|
||||
/// in predicted branches: 16 for keys, whose lengths repeat from record to
|
||||
/// 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
|
||||
{
|
||||
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)
|
||||
{
|
||||
#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
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
while (p != e && plain[*p] != 0)
|
||||
@@ -101,6 +120,10 @@ stop:
|
||||
{
|
||||
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
|
||||
// that exactly what json::parse accepts is accepted)
|
||||
do
|
||||
@@ -113,6 +136,7 @@ stop:
|
||||
p += n;
|
||||
}
|
||||
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
|
||||
@@ -0,0 +1,108 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 <cstdint> // int64_t
|
||||
#include <map> // map
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // enable_if, is_constructible
|
||||
#include <unordered_map> // unordered_map
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/errors.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
|
||||
{
|
||||
|
||||
/// selects a conversion by its target type
|
||||
template<typename T>
|
||||
struct value_tag {};
|
||||
|
||||
/*!
|
||||
@brief the number or boolean of a node converted to an arithmetic type
|
||||
|
||||
As basic_json's get<T>() for arithmetic types: integers and floats are
|
||||
converted with static_cast, booleans give 0 or 1, and other types throw
|
||||
type_error.302.
|
||||
*/
|
||||
template<typename T, typename BasicJsonType>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n)
|
||||
{
|
||||
switch (static_cast<value_t>(n.kind))
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
return static_cast<T>(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(n)));
|
||||
case value_t::number_integer:
|
||||
return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n))));
|
||||
case value_t::number_float:
|
||||
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n));
|
||||
case value_t::boolean:
|
||||
return static_cast<T>((n.flags & node_flags::is_true) != 0);
|
||||
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:
|
||||
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
}
|
||||
}
|
||||
|
||||
/// std::vector from an array, element by element (type_error.302 otherwise)
|
||||
template<typename View, typename U, typename A>
|
||||
std::vector<U, A> vector_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||
{
|
||||
throw_type_error(302, "type must be array, but is ", v.type_name());
|
||||
}
|
||||
std::vector<U, A> r;
|
||||
r.reserve(v.size());
|
||||
for (const View e : v)
|
||||
{
|
||||
r.push_back(e.template get<U>());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// a map with string keys from an object; with duplicate keys, the last
|
||||
/// value is kept, as parse() does (type_error.302 for other types)
|
||||
template<typename Map, typename View>
|
||||
Map map_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||
{
|
||||
throw_type_error(302, "type must be object, but is ", v.type_name());
|
||||
}
|
||||
Map r;
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
{
|
||||
const auto key = it.key();
|
||||
r[typename Map::key_type(key.data(), key.size())] = it.value().template get<typename Map::mapped_type>();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// whether a map type is read member by member (its keys are made from
|
||||
/// characters and a length); other maps go through basic_json
|
||||
template<typename Key>
|
||||
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -27,11 +27,17 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#ifndef JSON_NO_IO
|
||||
#include <ostream> // ostream
|
||||
#endif
|
||||
#include <string> // string
|
||||
#include <tuple> // tuple_element, tuple_size
|
||||
#include <type_traits> // enable_if, integral_constant, 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
|
||||
#include <unordered_map> // unordered_map
|
||||
#include <utility> // forward, move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -41,6 +47,7 @@
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/view/builder.hpp>
|
||||
#include <nlohmann/detail/view/compare.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/input.hpp>
|
||||
@@ -49,7 +56,10 @@
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/materialize.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/pointer.hpp>
|
||||
#include <nlohmann/detail/view/serializer.hpp>
|
||||
#include <nlohmann/detail/view/string_ref.hpp>
|
||||
#include <nlohmann/detail/view/value.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
@@ -268,6 +278,14 @@ class basic_json_view
|
||||
return operator[](static_cast<size_type>(idx));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to; a discarded view if a key is
|
||||
/// missing or an index is out of range. Other errors throw what const
|
||||
/// basic_json::operator[] throws.
|
||||
basic_json_view operator[](const json_pointer& ptr) const
|
||||
{
|
||||
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
|
||||
}
|
||||
|
||||
/// the value of the member with this key (the first one, should the key
|
||||
/// occur more than once). Throws type_error.304 if this is not an object,
|
||||
/// and out_of_range.403 if there is no such member.
|
||||
@@ -315,6 +333,51 @@ class basic_json_view
|
||||
return at(static_cast<size_type>(idx));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to; throws what basic_json::at()
|
||||
/// throws if it cannot be resolved
|
||||
basic_json_view at(const json_pointer& ptr) const
|
||||
{
|
||||
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::checked);
|
||||
}
|
||||
|
||||
/// the member with this key converted to T, or the default value if there
|
||||
/// is no such member (the first one, should the key occur more than
|
||||
/// once). Throws type_error.306 if this is not an object.
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
T value(string_view_t key, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
return r ? r.template get<T>() : default_value;
|
||||
}
|
||||
|
||||
string_t value(string_view_t key, const char* default_value) const
|
||||
{
|
||||
return value(key, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the value a JSON pointer refers to converted to T, or the default
|
||||
/// value if the pointer cannot be resolved. Throws type_error.306 if this
|
||||
/// is neither an object nor an array.
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
T value(const json_pointer& ptr, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r ? r.template get<T>() : default_value;
|
||||
}
|
||||
|
||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||
{
|
||||
return value(ptr, string_t(default_value));
|
||||
}
|
||||
|
||||
/// the first element or member value; a primitive value itself. Throws
|
||||
/// invalid_iterator.214 for null, discarded views, and empty containers.
|
||||
basic_json_view front() const
|
||||
@@ -381,6 +444,13 @@ class basic_json_view
|
||||
return contains(string_view_t(key.data(), key.size()));
|
||||
}
|
||||
|
||||
/// whether a JSON pointer can be resolved (never throws, as
|
||||
/// basic_json::contains())
|
||||
bool contains(const json_pointer& ptr) const
|
||||
{
|
||||
return static_cast<bool>(detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains));
|
||||
}
|
||||
|
||||
/// 1 if this is an object with a member with this key, else 0 (duplicate
|
||||
/// keys count once)
|
||||
size_type count(string_view_t key) const
|
||||
@@ -438,6 +508,140 @@ class basic_json_view
|
||||
return detail::view::view_items<basic_json_view>(*this);
|
||||
}
|
||||
|
||||
////////////////
|
||||
// conversion //
|
||||
////////////////
|
||||
|
||||
/// the value converted to T, as BasicJsonType::get<T>(): arithmetic types,
|
||||
/// strings (string_view_t without a copy), std::nullptr_t, std::vector,
|
||||
/// maps with string keys, and views are converted directly; other types
|
||||
/// through materialize().get<T>()
|
||||
template<typename T>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T get() const
|
||||
{
|
||||
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& get_to(T& v) const
|
||||
{
|
||||
v = get<T>();
|
||||
return v;
|
||||
}
|
||||
|
||||
/// the string, without a copy; valid as long as the view is. Throws
|
||||
/// type_error.302 for other types.
|
||||
string_view_t get_string() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
|
||||
}
|
||||
return {m_doc->str(*m_node), m_node->len};
|
||||
}
|
||||
|
||||
/// the text of a number as it appears in the source (e.g. "1.50", "1E2",
|
||||
/// or an integer with more digits than any number type holds). Throws
|
||||
/// type_error.302 for other types.
|
||||
string_view_t number_token() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
}
|
||||
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 //
|
||||
/////////////////
|
||||
@@ -470,6 +674,30 @@ class basic_json_view
|
||||
: 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
|
||||
/// (object required)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
||||
@@ -478,6 +706,83 @@ class basic_json_view
|
||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
||||
}
|
||||
|
||||
// --- get() dispatch ---
|
||||
|
||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
|
||||
}
|
||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||
}
|
||||
|
||||
template < typename T, typename std::enable_if < std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, int >::type = 0 >
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
||||
}
|
||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||
}
|
||||
|
||||
std::nullptr_t get_impl(detail::view::value_tag<std::nullptr_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||
{
|
||||
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
string_view_t get_impl(detail::view::value_tag<string_view_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return get_string();
|
||||
}
|
||||
|
||||
template<typename Traits, typename Alloc>
|
||||
std::basic_string<char, Traits, Alloc> get_impl(detail::view::value_tag<std::basic_string<char, Traits, Alloc>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
const string_view_t s = get_string();
|
||||
return std::basic_string<char, Traits, Alloc>(s.data(), s.size());
|
||||
}
|
||||
|
||||
BasicJsonType get_impl(detail::view::value_tag<BasicJsonType> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return materialize();
|
||||
}
|
||||
|
||||
basic_json_view get_impl(detail::view::value_tag<basic_json_view> /*unused*/, detail::priority_tag<2> /*unused*/) const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename U, typename A>
|
||||
std::vector<U, A> get_impl(detail::view::value_tag<std::vector<U, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::vector_value<basic_json_view, U, A>(*this);
|
||||
}
|
||||
|
||||
template<typename K, typename V, typename C, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
|
||||
std::map<K, V, C, A> get_impl(detail::view::value_tag<std::map<K, V, C, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::map_value<std::map<K, V, C, A>>(*this);
|
||||
}
|
||||
|
||||
template<typename K, typename V, typename H, typename E, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
|
||||
std::unordered_map<K, V, H, E, A> get_impl(detail::view::value_tag<std::unordered_map<K, V, H, E, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
return detail::view::map_value<std::unordered_map<K, V, H, E, A>>(*this);
|
||||
}
|
||||
|
||||
/// everything else through the BasicJsonType value (from_json included)
|
||||
template<typename T>
|
||||
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<0> /*unused*/) const
|
||||
{
|
||||
return materialize().template get<T>();
|
||||
}
|
||||
|
||||
const document_data* m_doc = nullptr;
|
||||
const node* m_node = nullptr;
|
||||
};
|
||||
|
||||
@@ -19781,6 +19781,11 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct json_pointer_access;
|
||||
} // namespace detail
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/
|
||||
template<typename RefStringType>
|
||||
@@ -19793,6 +19798,8 @@ class json_pointer
|
||||
template<typename>
|
||||
friend class json_pointer;
|
||||
|
||||
friend struct detail::json_pointer_access;
|
||||
|
||||
template<typename T>
|
||||
struct string_t_helper
|
||||
{
|
||||
@@ -20915,6 +20922,20 @@ inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace detail
|
||||
{
|
||||
/// the reference tokens of a json_pointer, for code that resolves pointers
|
||||
/// without a basic_json value (such as the zero-copy view)
|
||||
struct json_pointer_access
|
||||
{
|
||||
template<typename RefStringType>
|
||||
static const std::vector<typename json_pointer<RefStringType>::string_t>& reference_tokens(const json_pointer<RefStringType>& ptr) noexcept
|
||||
{
|
||||
return ptr.reference_tokens;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// #include <nlohmann/detail/json_ref.hpp>
|
||||
|
||||
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}
|
||||
)
|
||||
|
||||
# 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
|
||||
|
||||
#############################################################################
|
||||
|
||||
@@ -21,6 +21,7 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
|
||||
| `ViewParseIndented` | as `ParseIndented`, with a reused `json_document` |
|
||||
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
|
||||
| `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`,
|
||||
`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());
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -17,12 +17,19 @@ using nlohmann::ordered_json_document;
|
||||
using nlohmann::ordered_json_view;
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -649,3 +656,566 @@ TEST_CASE("json_view element access and iteration")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// an exception message without the context that basic_json adds with
|
||||
// JSON_DIAGNOSTICS ("(/path) ") and JSON_DIAGNOSTIC_POSITIONS ("(bytes 1-2) ");
|
||||
// the view's exceptions have no such context
|
||||
std::string without_path(std::string msg)
|
||||
{
|
||||
for (const char* prefix :
|
||||
{"] (/", "] (bytes "
|
||||
})
|
||||
{
|
||||
const std::size_t open = msg.find(prefix);
|
||||
if (open != std::string::npos)
|
||||
{
|
||||
msg.erase(open + 2, msg.find(") ", open) + 2 - (open + 2));
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
// the bits of a float, to compare values bit for bit
|
||||
std::uint64_t bits(double x)
|
||||
{
|
||||
std::uint64_t r = 0;
|
||||
std::memcpy(&r, &x, sizeof(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
std::uint32_t bits(float x)
|
||||
{
|
||||
std::uint32_t r = 0;
|
||||
std::memcpy(&r, &x, sizeof(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
bool has_duplicate_keys(const ordered_json_view& v)
|
||||
{
|
||||
if (v.is_object() && v.size() != v.materialize().size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return std::any_of(v.begin(), v.end(), [](const ordered_json_view e)
|
||||
{
|
||||
return e.is_structured() && has_duplicate_keys(e);
|
||||
});
|
||||
}
|
||||
|
||||
// compares the conversions of a view with those of ordered_json
|
||||
void check_values(const ordered_json_view& v, const ordered_json& j, const std::string& text)
|
||||
{
|
||||
CHECK(v.get<ordered_json>() == j);
|
||||
switch (j.type())
|
||||
{
|
||||
case json::value_t::number_integer:
|
||||
case json::value_t::number_unsigned:
|
||||
case json::value_t::number_float:
|
||||
{
|
||||
// (converting a float out of range of the target type is undefined)
|
||||
if (j.is_number_unsigned())
|
||||
{
|
||||
CHECK(v.get<std::uint64_t>() == j.get<std::uint64_t>());
|
||||
}
|
||||
else if (j.is_number_integer())
|
||||
{
|
||||
CHECK(v.get<std::int64_t>() == j.get<std::int64_t>());
|
||||
}
|
||||
CHECK(bits(v.get<double>()) == bits(j.get<double>()));
|
||||
if (std::abs(j.get<double>()) < 1e9)
|
||||
{
|
||||
CHECK(v.get<int>() == j.get<int>());
|
||||
}
|
||||
const auto token = v.number_token();
|
||||
CHECK(text.compare(v.source_offset(), token.size(), token.data(), token.size()) == 0);
|
||||
break;
|
||||
}
|
||||
case json::value_t::string:
|
||||
CHECK(v.get<std::string>() == j.get<std::string>());
|
||||
CHECK(std::string(v.get_string().data(), v.get_string().size()) == j.get<std::string>());
|
||||
break;
|
||||
case json::value_t::boolean:
|
||||
CHECK(v.get<bool>() == j.get<bool>());
|
||||
CHECK(v.get<int>() == j.get<int>());
|
||||
break;
|
||||
case json::value_t::null:
|
||||
CHECK(v.get<std::nullptr_t>() == nullptr);
|
||||
break;
|
||||
case json::value_t::array:
|
||||
CHECK(v.get<std::vector<ordered_json>>() == j.get<std::vector<ordered_json>>());
|
||||
break;
|
||||
case json::value_t::object:
|
||||
CHECK((v.get<std::map<std::string, ordered_json>>() == j.get<std::map<std::string, ordered_json>>()));
|
||||
break;
|
||||
case json::value_t::binary:
|
||||
case json::value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// conversions to the wrong type throw what basic_json throws
|
||||
if (!j.is_number())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<int>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<int>()); })));
|
||||
}
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<bool>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<bool>()); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::string>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::string>()); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::nullptr_t>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::nullptr_t>()); })));
|
||||
if (!j.is_array())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::vector<int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::vector<int>>()); })));
|
||||
}
|
||||
if (!j.is_object())
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v.get<std::map<std::string, int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::map<std::string, int>>()); })));
|
||||
}
|
||||
|
||||
if (v.is_array())
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (const ordered_json_view e : v)
|
||||
{
|
||||
check_values(e, j[i++], text);
|
||||
}
|
||||
}
|
||||
else if (v.is_object())
|
||||
{
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
{
|
||||
const std::string key(it.key().data(), it.key().size());
|
||||
if (v.size() == j.size()) // (no duplicate keys)
|
||||
{
|
||||
check_values(it.value(), j[key], text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct record
|
||||
{
|
||||
std::string name{}; // NOLINT(readability-redundant-member-init)
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
void from_json(const json& j, record& r)
|
||||
{
|
||||
j.at("name").get_to(r.name);
|
||||
j.at("count").get_to(r.count);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("json_view values")
|
||||
{
|
||||
SECTION("generated documents")
|
||||
{
|
||||
generator g;
|
||||
for (int i = 0; i < 2000; ++i)
|
||||
{
|
||||
std::string text;
|
||||
g.value(text, 0);
|
||||
CAPTURE(text);
|
||||
const ordered_json_document d = ordered_json_document::parse(text);
|
||||
check_values(d.root(), ordered_json::parse(text), text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("floats are converted as parse() converts them")
|
||||
{
|
||||
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",
|
||||
"0.1000000000000000055511151231257827021181583404541015625", "123456789012345678901234567890",
|
||||
"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"
|
||||
};
|
||||
for (int i = 0; i < 20000; ++i)
|
||||
{
|
||||
const std::uint64_t bits = rng();
|
||||
double d = 0;
|
||||
std::memcpy(&d, &bits, sizeof(d));
|
||||
if (!std::isfinite(d))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::array<char, 400> buf{};
|
||||
switch (i % 5) // NOLINT(hicpp-multiway-paths-covered)
|
||||
{
|
||||
case 0:
|
||||
std::snprintf(buf.data(), buf.size(), "%.17g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
case 1:
|
||||
std::snprintf(buf.data(), buf.size(), "%.15g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
case 2:
|
||||
std::snprintf(buf.data(), buf.size(), "%.3e", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
case 3:
|
||||
std::snprintf(buf.data(), buf.size(), "%.25g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
default:
|
||||
std::snprintf(buf.data(), buf.size(), "%.0f", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
break;
|
||||
}
|
||||
tokens.emplace_back(buf.data());
|
||||
}
|
||||
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
CAPTURE(token);
|
||||
const std::string text = "[" + token + "]";
|
||||
const double b = json::parse(text)[0].get<double>();
|
||||
CHECK(bits(json_document::parse(text).root()[0].get<double>()) == bits(b));
|
||||
if (std::abs(b) < 1e38)
|
||||
{
|
||||
CHECK(bits(nlohmann::basic_json_document<json_float>::parse(text).root()[0].get<float>()) == bits(json_float::parse(text)[0].get<float>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("number tokens")
|
||||
{
|
||||
const json_document d = json_document::parse(R"([1.50, 1E2, -0, 123456789012345678901234567890, -12, 7, "x"])");
|
||||
const json_view v = d.root();
|
||||
CHECK(v[0].number_token() == "1.50");
|
||||
CHECK(v[1].number_token() == "1E2");
|
||||
CHECK(v[2].number_token() == "-0");
|
||||
CHECK(v[3].number_token() == "123456789012345678901234567890");
|
||||
CHECK(v[4].number_token() == "-12");
|
||||
CHECK(v[5].number_token() == "7");
|
||||
CHECK_THROWS_WITH_AS(v[6].number_token(), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(v.get_string(), "[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("conversions")
|
||||
{
|
||||
const std::string text = R"({"name": "widget", "count": 3, "tags": ["a", "b\n"], "sizes": {"s": 1, "m": 2}, "pair": [1, "x"]})";
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
const json j = json::parse(text);
|
||||
|
||||
// user types with from_json, and other types, through basic_json
|
||||
const record r = v.get<record>();
|
||||
CHECK(r.name == "widget");
|
||||
CHECK(r.count == 3);
|
||||
CHECK((v["pair"].get<std::pair<int, std::string>>() == j["pair"].get<std::pair<int, std::string>>()));
|
||||
CHECK(v["tags"].get<std::list<std::string>>() == j["tags"].get<std::list<std::string>>());
|
||||
CHECK((v["sizes"].get<std::unordered_map<std::string, int>>() == j["sizes"].get<std::unordered_map<std::string, int>>()));
|
||||
CHECK(v["tags"].get<std::vector<std::string>>() == std::vector<std::string> {"a", "b\n"});
|
||||
|
||||
// views of the elements
|
||||
const auto views = v["tags"].get<std::vector<json_view>>();
|
||||
CHECK(views.size() == 2);
|
||||
CHECK(views[1].get_string() == "b\n");
|
||||
const auto members = v.get<std::map<std::string, json_view>>();
|
||||
CHECK(members.at("count").get<int>() == 3);
|
||||
CHECK(v.get<json_view>()["name"].get_string() == "widget");
|
||||
|
||||
// strings without a copy point into the source text
|
||||
CHECK(v["name"].get_string().data() == text.data() + text.find("widget"));
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
CHECK(v["name"].get<std::string_view>() == "widget");
|
||||
#endif
|
||||
|
||||
std::string name;
|
||||
int count = 0;
|
||||
CHECK(&v["name"].get_to(name) == &name);
|
||||
v["count"].get_to(count);
|
||||
CHECK(name == "widget");
|
||||
CHECK(count == 3);
|
||||
|
||||
// a duplicate key: the last value, as parse()
|
||||
CHECK((json_document::parse(R"({"a":1,"a":2})").root().get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 2}}));
|
||||
|
||||
const json_view invalid;
|
||||
CHECK_THROWS_WITH_AS(invalid.get<int>(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&);
|
||||
CHECK(invalid.get<json>().is_discarded());
|
||||
}
|
||||
|
||||
SECTION("value")
|
||||
{
|
||||
const json_document d = json_document::parse(R"({"n": 1, "s": "text", "o": {"x": [10, 20]}})");
|
||||
const json_view v = d.root();
|
||||
const json j = v.materialize();
|
||||
CHECK(v.value("n", 0) == j.value("n", 0));
|
||||
CHECK(v.value("missing", 42) == j.value("missing", 42));
|
||||
CHECK(v.value("s", "default") == j.value("s", "default"));
|
||||
CHECK(v.value("missing", "default") == j.value("missing", "default"));
|
||||
CHECK(v.value(std::string("n"), 2.5) == j.value(std::string("n"), 2.5));
|
||||
CHECK(v.value(json::json_pointer("/o/x/1"), 0) == j.value(json::json_pointer("/o/x/1"), 0));
|
||||
CHECK(v.value(json::json_pointer("/o/x/5"), 0) == j.value(json::json_pointer("/o/x/5"), 0));
|
||||
CHECK(v.value(json::json_pointer("/o/y"), "none") == j.value(json::json_pointer("/o/y"), "none"));
|
||||
// with a JSON pointer, arrays can be asked as well
|
||||
CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0));
|
||||
CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["o"]["x"].value("k", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["o"]["x"].value("k", 0)); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
|
||||
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("json_view JSON pointers")
|
||||
{
|
||||
SECTION("every value of generated documents")
|
||||
{
|
||||
generator g;
|
||||
for (int i = 0; i < 1000; ++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);
|
||||
const ordered_json flat = j.flatten();
|
||||
for (const auto& leaf : flat.items())
|
||||
{
|
||||
// the leaf and each of its parents
|
||||
for (ordered_json::json_pointer p(leaf.key());; p = p.parent_pointer())
|
||||
{
|
||||
CAPTURE(p.to_string());
|
||||
CHECK(d.root()[p].materialize() == j[p]);
|
||||
CHECK(d.root().at(p).materialize() == j.at(p));
|
||||
CHECK(d.root().contains(p));
|
||||
if (p.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("errors are those of basic_json")
|
||||
{
|
||||
const std::string text = R"({"a": [1, {"b": null}], "c": "s", "": {"": 0}, "a~b": 1, "c/d": 2})";
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
const json j = v.materialize();
|
||||
for (const char* pointer :
|
||||
{"", "/", "//", "/a", "/a/0", "/a/1/b", "/a/-", "/a/01", "/a/00", "/a/1a", "/a/a", "/a/", "/a/2", "/a/99", "/a/99999999999999999999",
|
||||
"/a/18446744073709551615", "/a/-1", "/a/+1", "/a/ 1", "/x", "/c/x", "/a/0/x", "/a/1/b/c", "/a~0b", "/c~1d", "/c~1d/x", "/a/1/-"
|
||||
})
|
||||
{
|
||||
CAPTURE(pointer);
|
||||
const json::json_pointer p(pointer);
|
||||
const std::string at_error = without_path(exception_of([&] { static_cast<void>(j.at(p)); }));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.at(p)); }) == at_error);
|
||||
if (at_error.empty())
|
||||
{
|
||||
CHECK(v.at(p).materialize() == j.at(p));
|
||||
CHECK(v[p].materialize() == j[p]);
|
||||
}
|
||||
else if (at_error.find("out_of_range.401") != std::string::npos || at_error.find("out_of_range.403") != std::string::npos) // NOLINT(abseil-string-find-str-contains)
|
||||
{
|
||||
// undefined behavior for const basic_json::operator[]
|
||||
CHECK(!v[p]);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(exception_of([&] { static_cast<void>(v[p]); }) == without_path(exception_of([&] { static_cast<void>(j[p]); })));
|
||||
}
|
||||
// (basic_json::contains() throws out_of_range.404 for an empty
|
||||
// array index token, although it is not meant to throw; the view
|
||||
// answers false)
|
||||
const std::string contains_error = exception_of([&] { static_cast<void>(j.contains(p)); });
|
||||
CHECK(v.contains(p) == (contains_error.empty() && j.contains(p)));
|
||||
CHECK(exception_of([&] { static_cast<void>(v.value(p, 5)); }) == without_path(exception_of([&] { static_cast<void>(j.value(p, 5)); })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <nlohmann/detail/view/string_ref.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
@@ -136,6 +137,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
|
||||
struct generator
|
||||
{
|
||||
@@ -364,3 +391,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