mirror of
https://github.com/nlohmann/json.git
synced 2026-11-14 16:02:48 +00:00
Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fb114e45f | ||
|
|
d1235cecc9 | ||
|
|
e28506dd86 | ||
|
|
03a0595c1e | ||
|
|
4e72af9d54 | ||
|
|
0f5d07310c | ||
|
|
0b924f5375 | ||
|
|
6fa14d8f95 | ||
|
|
dec0e9b749 | ||
|
|
86d5c56c54 | ||
|
|
aa0b371f7c | ||
|
|
ace1030107 | ||
|
|
2dc1e54856 | ||
|
|
9855db81f3 | ||
|
|
533cbfee51 | ||
|
|
9bf531436f | ||
|
|
3e031b06a5 | ||
|
|
7c26e99ac5 | ||
|
|
6aefd8c662 | ||
|
|
52fecc1fab | ||
|
|
fa37b952df | ||
|
|
e9970066c5 | ||
|
|
3f8677d028 | ||
|
|
ef3d350639 | ||
|
|
5e13f834c1 | ||
|
|
e13e976801 | ||
|
|
898e6ee81d | ||
|
|
ef1cdb59ac | ||
|
|
bd5227c2ec | ||
|
|
0cbe95ffa2 | ||
|
|
08d7fbb0a1 | ||
|
|
4155e2938f | ||
|
|
4971200b54 | ||
|
|
823eee3b59 | ||
|
|
3ff37ead75 | ||
|
|
4c3a3eb5b5 | ||
|
|
43e97b629b | ||
|
|
122d718330 | ||
|
|
a4974e72c1 | ||
|
|
a55cae45fd | ||
|
|
667e296707 | ||
|
|
ec2e50e26f | ||
|
|
4da932f518 | ||
|
|
a2248ed5d5 | ||
|
|
cc8334113c |
No files matched your search
@@ -29,6 +29,13 @@ updates:
|
||||
- dependency-name: mermaid
|
||||
update-types: ["version-update:semver-major"]
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /.github/requirements
|
||||
schedule:
|
||||
interval: daily
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /tools/astyle
|
||||
schedule:
|
||||
@@ -36,7 +43,7 @@ updates:
|
||||
cooldown:
|
||||
default-days: 7
|
||||
ignore:
|
||||
# astyle is deliberately pinned (see tools/astyle/requirements.txt): newer versions
|
||||
# astyle is deliberately pinned (see tools/astyle/requirements.in): newer versions
|
||||
# reformat the code, and the pinned version defines the formatting CI enforces
|
||||
- dependency-name: astyle
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ environment:
|
||||
matrix:
|
||||
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
|
||||
# only build the C++17 variant of each test, split into two jobs each to
|
||||
# stay below AppVeyor's 60-minute limit per job.
|
||||
# stay below AppVeyor's 60-minute limit per job. The Visual Studio 2019
|
||||
# job builds the tests for all standards and is split for the same reason.
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
configuration: Debug
|
||||
platform: x86
|
||||
@@ -50,7 +51,13 @@ environment:
|
||||
configuration: Release
|
||||
platform: x86
|
||||
CXX_FLAGS: "/W4 /WX"
|
||||
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF"
|
||||
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF -DJSON_TestShard=0/2"
|
||||
GENERATOR: Visual Studio 16 2019
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
configuration: Release
|
||||
platform: x86
|
||||
CXX_FLAGS: "/W4 /WX"
|
||||
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF -DJSON_TestShard=1/2"
|
||||
GENERATOR: Visual Studio 16 2019
|
||||
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
|
||||
+2
-1
@@ -63,7 +63,8 @@ labels:
|
||||
- "single_include/nlohmann/json_view\\.hpp"
|
||||
- "tests/src/unit-json_view.*"
|
||||
- "tests/src/json_view_test_helpers\\.hpp"
|
||||
- "tests/src/fuzzer-parse_json_view\\.cpp"
|
||||
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.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 @@
|
||||
meson==1.12.1
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.14
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --generate-hashes --no-emit-index-url --output-file=.github/requirements/meson.txt --strip-extras .github/requirements/meson.in
|
||||
#
|
||||
meson==1.12.1 \
|
||||
--hash=sha256:930bc7542cbd9f57009e182fd014eba48cf1a0180a7b9006d2d32d8f168d8b02 \
|
||||
--hash=sha256:ab0a6ca09f8ef70c564c8241fb5a23957886a0b53fb58412b5e07eaf07dba743
|
||||
# via -r .github/requirements/meson.in
|
||||
@@ -0,0 +1 @@
|
||||
semgrep==1.182.1
|
||||
File diff suppressed because it is too large.
Load diff
@@ -37,7 +37,7 @@ jobs:
|
||||
path: pr/
|
||||
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
env:
|
||||
MAIN_DIR: ${{ github.workspace }}/main
|
||||
INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann
|
||||
@@ -67,23 +67,22 @@ jobs:
|
||||
- name: Install astyle
|
||||
run: |
|
||||
python3 -mvenv venv
|
||||
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
|
||||
venv/bin/pip3 install --require-hashes -r $MAIN_DIR/tools/astyle/requirements.txt
|
||||
|
||||
- name: Install generate_natvis dependencies
|
||||
run: pip3 install -r $NATVIS_TOOL_DIR/requirements.txt
|
||||
run: pip3 install --require-hashes -r $MAIN_DIR/tools/generate_natvis/requirements.txt
|
||||
|
||||
- name: Regenerate the tools/macro_builder tables in macro_scope.hpp
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
|
||||
# Built from this PR's own tools/macro_builder/main.cpp, not a
|
||||
# develop checkout: unlike amalgamate.py and generate_natvis.py,
|
||||
# this tool has no other source of truth to check against (its
|
||||
# own README documents that it must reproduce macro_scope.hpp
|
||||
# byte for byte), so there is nothing to gain from checking out a
|
||||
# separate copy, and doing so would make this step fail on a PR
|
||||
# that adds support for a new dispatch table until that PR itself
|
||||
# merges to develop.
|
||||
# develop checkout: unlike amalgamate.py, this tool has no other
|
||||
# source of truth to check against (its own README documents that
|
||||
# it must reproduce macro_scope.hpp byte for byte), so there is
|
||||
# nothing to gain from checking out a separate copy, and doing so
|
||||
# would make this step fail on a PR that adds support for a new
|
||||
# dispatch table until that PR itself merges to develop.
|
||||
TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX)
|
||||
c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder"
|
||||
"$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp"
|
||||
@@ -146,14 +145,36 @@ jobs:
|
||||
- name: Regenerate nlohmann_json.natvis
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
# The template is what this check verifies the committed natvis
|
||||
# file against, so take it from this PR rather than from the
|
||||
# develop checkout: generate_natvis.py loads the template from its
|
||||
# own directory, and with develop's copy a PR that changes the
|
||||
# template could never pass. Like the macro_builder step above,
|
||||
# this only renders PR content in a job without credentials.
|
||||
cp tools/generate_natvis/nlohmann_json.natvis.j2 $NATVIS_TOOL_DIR/
|
||||
python3 $NATVIS_TOOL_DIR/generate_natvis.py $MAIN_DIR
|
||||
# The generator and its template are what this check verifies the
|
||||
# committed natvis file against (which namespaces it lists, and
|
||||
# what each entry looks like), so run this PR's copy rather than
|
||||
# the develop checkout's: with develop's, a PR that changes either
|
||||
# could never pass. generate_natvis.py loads the template from its
|
||||
# own directory. Like the macro_builder step above, this only runs
|
||||
# PR content in a job without credentials.
|
||||
python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR
|
||||
|
||||
- name: Check the nlohmann_json.natvis generator options
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
# The options for configurations outside the checked-in file write
|
||||
# to a temporary file, so they do not affect the diff below.
|
||||
GENERATOR=tools/generate_natvis/generate_natvis.py
|
||||
python3 $GENERATOR --list-tags
|
||||
TAG_COUNT=$(python3 $GENERATOR --list-tags | wc -l)
|
||||
|
||||
python3 $GENERATOR --abi-tags diag,_dp,_vns --abi-tags _diag --output $RUNNER_TEMP/abi_tags.natvis
|
||||
grep -q '<!-- Namespace nlohmann::json_abi_diag_dp_vns -->' $RUNNER_TEMP/abi_tags.natvis
|
||||
grep -q '<!-- Namespace nlohmann::json_abi_diag_dp_vns_v[0-9_]* -->' $RUNNER_TEMP/abi_tags.natvis
|
||||
test $(grep -c '<!-- Namespace ' $RUNNER_TEMP/abi_tags.natvis) -eq $(( $(grep -c '<!-- Namespace ' nlohmann_json.natvis) + 2 ))
|
||||
|
||||
python3 $GENERATOR --all --output $RUNNER_TEMP/all.natvis
|
||||
test $(grep -c '<!-- Namespace ' $RUNNER_TEMP/all.natvis) -eq $(( (1 << TAG_COUNT) * 2 + 1 ))
|
||||
|
||||
if python3 $GENERATOR --abi-tags _unknown --output $RUNNER_TEMP/unknown.natvis; then
|
||||
echo "generate_natvis.py accepted an unknown ABI tag"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build patch and check for differences
|
||||
id: diff
|
||||
|
||||
@@ -21,7 +21,7 @@ permissions:
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
languages: c-cpp
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
@@ -37,6 +37,14 @@ jobs:
|
||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "pr"
|
||||
})[0];
|
||||
// a run that failed before its jobs started (e.g. a stale queued
|
||||
// run cancelled long after its push) has no artifact to report on
|
||||
if (!matchArtifact) {
|
||||
core.notice('Run ${{ github.event.workflow_run.id }} has no "pr" artifact; nothing to comment on.');
|
||||
core.setOutput('found', 'false');
|
||||
return;
|
||||
}
|
||||
core.setOutput('found', 'true');
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
@@ -51,9 +59,11 @@ jobs:
|
||||
# Extract the untrusted PR artifact into a dedicated empty directory and
|
||||
# read only the two expected files by fixed path afterwards. This avoids a
|
||||
# malicious archive overwriting workspace files or escaping via ../ paths.
|
||||
- run: unzip -o pr.zip -d ./pr_artifact
|
||||
- if: steps.download.outputs.found == 'true'
|
||||
run: unzip -o pr.zip -d ./pr_artifact
|
||||
|
||||
- name: 'Comment on PR'
|
||||
if: steps.download.outputs.found == 'true'
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -31,3 +31,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
|
||||
with:
|
||||
# Semgrep pins mcp==1.29.0 and opentelemetry-api~=1.37.0 exactly
|
||||
# (.github/requirements/semgrep.txt), so these cannot be upgraded
|
||||
# until Semgrep lifts its pins. They do not apply to our use: CI
|
||||
# only runs `semgrep scan` on the checkout and never starts the MCP
|
||||
# server (GHSA-84m7-p3x7-pcfv, StreamableHTTP sessions) or parses
|
||||
# incoming trace context (GHSA-xcq7-p3pf-p88h, tracestate parsing).
|
||||
# Remove the entries once a Semgrep release allows the fixed versions.
|
||||
allow-ghsas: GHSA-84m7-p3x7-pcfv, GHSA-xcq7-p3pf-p88h
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
@@ -0,0 +1,84 @@
|
||||
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
|
||||
|
||||
concurrency:
|
||||
# (the label is part of the group: adding an unrelated label must not cancel a running benchmark)
|
||||
group: json-view-benchmarks-${{ github.workflow }}-${{ github.ref }}-${{ github.event.label.name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
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 }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.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@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
||||
path: tests/benchmarks/json_view/results/
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
|
||||
uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -80,6 +80,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
name: Scan
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
# SEMGREP_APP_TOKEN is still passed through so registry auth works if a
|
||||
# token is ever added.
|
||||
- name: Install Semgrep
|
||||
run: python3 -m pip install --user semgrep==1.168.0
|
||||
run: python3 -m pip install --user --require-hashes -r .github/requirements/semgrep.txt
|
||||
|
||||
# `semgrep scan --sarif` always exits 0 even with findings; continue-on-error
|
||||
# is a safety net so the SARIF upload still runs if the scan itself errors.
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
# Upload SARIF file generated in previous step
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_gcc
|
||||
|
||||
ci_meson_install:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Check that Meson and pkg-config offer the CMake options
|
||||
run: make check_build_options
|
||||
- name: Install Meson
|
||||
run: pip install meson
|
||||
run: pip install --require-hashes -r .github/requirements/meson.txt
|
||||
- name: Install with Meson
|
||||
run: |
|
||||
meson setup build-meson --prefix=${{ github.workspace }}/install
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
cmake --build build-import-split
|
||||
|
||||
ci_infer:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
|
||||
ci_static_analysis_ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_test_coverage:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -430,7 +430,7 @@ jobs:
|
||||
run: cmake --build build --target ci_nvhpc
|
||||
|
||||
ci_emscripten:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -450,7 +450,7 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
ci_test_documentation:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
|
||||
|
||||
@@ -37,6 +37,7 @@ cc_library(
|
||||
"include/nlohmann/detail/input/parser.hpp",
|
||||
"include/nlohmann/detail/input/position_t.hpp",
|
||||
"include/nlohmann/detail/input/pow5_table.hpp",
|
||||
"include/nlohmann/detail/input/simd_scan.hpp",
|
||||
"include/nlohmann/detail/input/string_scan.hpp",
|
||||
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
||||
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
||||
@@ -73,6 +74,7 @@ cc_library(
|
||||
"include/nlohmann/detail/view/edit.hpp",
|
||||
"include/nlohmann/detail/view/edit_storage.hpp",
|
||||
"include/nlohmann/detail/view/errors.hpp",
|
||||
"include/nlohmann/detail/view/image.hpp",
|
||||
"include/nlohmann/detail/view/input.hpp",
|
||||
"include/nlohmann/detail/view/iterator.hpp",
|
||||
"include/nlohmann/detail/view/lookup.hpp",
|
||||
|
||||
@@ -6,6 +6,7 @@ This file describes the source for supporting files; that is, files that are not
|
||||
- [GitHub](#github)
|
||||
- [REUSE](#reuse)
|
||||
- [Package Managers](#package-managers)
|
||||
- [Debugging](#debugging)
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
@@ -289,3 +290,29 @@ The module definition for [Bazel](https://bazel.build)'s [Bzlmod](https://bazel.
|
||||
Further documentation:
|
||||
|
||||
- [Bazel modules](https://bazel.build/external/module)
|
||||
|
||||
## Debugging
|
||||
|
||||
### `nlohmann_json.natvis`
|
||||
|
||||
The [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects) file that
|
||||
gives `json` values a friendly view in the Visual Studio debugger. The file is generated by
|
||||
`tools/generate_natvis/generate_natvis.py` from the template `tools/generate_natvis/nlohmann_json.natvis.j2` and the ABI
|
||||
tags and version in `include/nlohmann/detail/abi_macros.hpp`; change the script or the template rather than editing
|
||||
the file by hand. The file can be updated by calling
|
||||
|
||||
```shell
|
||||
make natvis
|
||||
```
|
||||
|
||||
The "Check amalgamation" workflow fails if the file is out of date.
|
||||
|
||||
The file covers the namespace variants with up to two ABI tags. A Natvis file for other configurations can be
|
||||
generated with the `--abi-tags` or `--all` options, for example
|
||||
`make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my_nlohmann_json.natvis"`; see
|
||||
[`tools/generate_natvis`](tools/generate_natvis/README.md). With MSVC, CMake adds the file to the `nlohmann_json`
|
||||
target and installs it.
|
||||
|
||||
Further documentation:
|
||||
|
||||
- [Create custom views of C++ objects in the debugger using the Natvis framework](https://learn.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects)
|
||||
@@ -46,7 +46,7 @@ all:
|
||||
@echo "clean - remove built files"
|
||||
@echo "(fuzzers) - see tests/fuzzing.md, or run 'make -C tests fuzzers' to build the AFL fuzzers"
|
||||
@echo "macro_builder_check - check that macro_scope.hpp matches tools/macro_builder's output"
|
||||
@echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version"
|
||||
@echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version (pass generator options via NATVIS_ARGS, see tools/generate_natvis/README.md)"
|
||||
@echo "pretty - beautify code with Artistic Style"
|
||||
@echo "pvs_studio - run the PVS-Studio static analyzer and open the report"
|
||||
@echo "release - create the release files (include.zip, json.tar.xz, single headers, signatures, hashes)"
|
||||
@@ -120,13 +120,15 @@ $(AMALGAMATED_VIEW_FILE): $(SRCS)
|
||||
tools/amalgamate/amalgamate.py -c tools/amalgamate/config_json_view.json -s . --verbose=yes
|
||||
|
||||
NATVIS_VENV=tools/generate_natvis/venv
|
||||
# extra options for generate_natvis.py, e.g. NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my.natvis"
|
||||
NATVIS_ARGS=
|
||||
|
||||
# regenerate nlohmann_json.natvis from the ABI tags and version in include/nlohmann/detail/abi_macros.hpp
|
||||
# (the dependencies are installed into a virtual environment first)
|
||||
natvis:
|
||||
@test -d $(NATVIS_VENV) || python3 -mvenv $(NATVIS_VENV)
|
||||
@$(NATVIS_VENV)/bin/python3 -c 'import jinja2' 2>/dev/null || $(NATVIS_VENV)/bin/pip3 install --quiet -r tools/generate_natvis/requirements.txt
|
||||
$(NATVIS_VENV)/bin/python3 tools/generate_natvis/generate_natvis.py .
|
||||
$(NATVIS_VENV)/bin/python3 tools/generate_natvis/generate_natvis.py $(NATVIS_ARGS) .
|
||||
|
||||
# regenerate the two tools/macro_builder blocks of $(MACRO_SCOPE_HPP) (see its README.md) and diff against the
|
||||
# checked-in header; phony, because it never writes $(MACRO_SCOPE_HPP) itself
|
||||
|
||||
+3
-3
@@ -361,10 +361,10 @@ add_custom_target(ci_test_coverage
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused
|
||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept
|
||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage json.info.filtered.noexcept
|
||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage --ignore-errors inconsistent json.info.filtered.noexcept
|
||||
|
||||
COMMENT "Compile and test with coverage"
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
cppcheck==1.5.2
|
||||
cppcheck==1.5.3
|
||||
@@ -132,6 +132,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -143,6 +147,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -112,6 +112,16 @@ By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input
|
||||
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
||||
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
||||
|
||||
!!! tip "Performance"
|
||||
|
||||
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
|
||||
`#!cpp std::istream` comes close: the parser scans the characters its stream buffer holds in bulk, without reading
|
||||
ahead, so the stream is left exactly where it would be after reading one character at a time. A `#!cpp FILE*` is
|
||||
read one character at a time, but the file is locked only once, so other threads that use the same `FILE` object
|
||||
wait until parsing is done. See [input sources and performance](../../features/parsing/index.md#input-sources-and-performance),
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md), and
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: (1) parse from a character array"
|
||||
@@ -247,6 +257,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -261,6 +275,11 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- Extended empty-input detection to also cover an `std::istream` without a stream buffer, and fixed a crash
|
||||
(`std::terminate`) when parsing from an `std::istream` with `eofbit` in its exception mask, in version 3.13.0.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) restores reading one character at a
|
||||
time).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) restores `std::fgetc`).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -116,6 +116,10 @@ consumer `sax` has a super-linear complexity.
|
||||
|
||||
A UTF-8 byte order mark is silently ignored.
|
||||
|
||||
When reading from a `#!c FILE*`, the file is locked while it is parsed. The lock is recursive, so the SAX handler may
|
||||
use the same file with stdio functions, but it must not wait for another thread that uses the same `FILE` object; see
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -137,6 +141,10 @@ A UTF-8 byte order mark is silently ignored.
|
||||
|
||||
- [parse](parse.md) - deserialize from a compatible input
|
||||
- [accept](accept.md) - check if the input is valid JSON
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -148,6 +156,10 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||
after the parsed value when `strict` is `#!cpp false`.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -123,4 +123,6 @@ Linear in the size of the JSON value `j`.
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
- Writes unsigned integers wider than 64 bits as high-precision numbers since version 3.13.0; previously, they were
|
||||
silently truncated to 64 bits.
|
||||
@@ -37,8 +37,9 @@ With (2), the bytes written before the exception remain in the output adapter.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an unsigned integer
|
||||
above 9223372036854775807, which BON8 cannot represent
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer outside
|
||||
the range of int64 (an unsigned integer above 9223372036854775807, or, with a number type wider than 64 bits, any
|
||||
integer beyond int64), which BON8 cannot represent
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not
|
||||
valid UTF-8
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
|
||||
@@ -47,6 +47,10 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
- Throws [`type_error.317`](../../home/exceptions.md#jsonexceptiontype_error317) if the top-level type of the JSON value
|
||||
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains a signed integer
|
||||
outside the range of int64 or an unsigned integer outside the range of uint64, which is only possible with a number
|
||||
type wider than 64 bits; example:
|
||||
`"integer number 9223372036854775808 cannot be represented by BSON as it does not fit int64"`
|
||||
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
|
||||
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
|
||||
@@ -119,3 +123,5 @@ pass before anything is written.
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||
is written.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -46,6 +46,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
|
||||
outside [-2^64, 2^64-1], which is only possible with a number type wider than 64 bits; example:
|
||||
`"integer number 18446744073709551616 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
@@ -90,3 +93,5 @@ Linear in the size of the JSON value `j`.
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid CBOR.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -46,6 +46,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
|
||||
outside [-2^63, 2^64-1], which is only possible with a number type wider than 64 bits; example:
|
||||
`"integer number 18446744073709551616 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]"`
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a string, binary
|
||||
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
|
||||
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
|
||||
@@ -112,3 +115,5 @@ Linear in the size of the JSON value `j`.
|
||||
`number_unsigned_t`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid MessagePack.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -18,10 +18,10 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (`
|
||||
1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many
|
||||
were removed; `#!cpp 0` if `object` has no member with this key.
|
||||
2. Removes the element at index `idx` of `array`, which must already exist (`#!cpp idx < array.size()`).
|
||||
3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), and returns how many values
|
||||
were removed: the *parent* of the target must already exist, and the target itself is removed as in 1. (an object
|
||||
member; `#!cpp 0` or more) or 2. (an array element; always `#!cpp 1`). `ptr` must not be empty -- [`root()`](root.md)
|
||||
itself cannot be erased.
|
||||
3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md). `ptr` must not be empty
|
||||
([`root()`](root.md) itself cannot be erased), and the *parent* of the target must already exist. The target is
|
||||
removed as in 1. if the parent is an object (returns the number of removed members, `#!cpp 0` or more) and as in 2.
|
||||
if it is an array (always returns `#!cpp 1`).
|
||||
|
||||
## Template parameters
|
||||
|
||||
@@ -69,10 +69,10 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (`
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token); otherwise (an
|
||||
object, or a primitive value the pointer's parent resolves to) throws what 1. throws.
|
||||
|
||||
Every overload also throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view
|
||||
does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a
|
||||
view of a *different* document (overloads 1-2 only; overload 3 always starts from this document's own
|
||||
[`root()`](root.md)).
|
||||
Overloads 1. and 2. also throw [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202)
|
||||
("view does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md)
|
||||
view or a view of a *different* document. Overload 3. never throws `invalid_iterator.202`: it takes no view, and it
|
||||
always starts from this document's own [`root()`](root.md).
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -92,9 +92,10 @@ view of a *different* document (overloads 1-2 only; overload 3 always starts fro
|
||||
|
||||
Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still
|
||||
referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) -- it just no
|
||||
longer appears when `array`/`object` is read, dumped, or iterated. Removing an element of `array` (2.) does shift the
|
||||
*links* to the elements after it, the same way `insert`, `set`, or `push_back` on the same array would; any iterator
|
||||
already taken over `array`/`object` is invalidated by an erase, since it was walking the old layout.
|
||||
longer appears when `array`/`object` is read, dumped, or iterated. What does change is the container's sequence of
|
||||
*links* to its elements: removing an element of `array` (2.) shifts the links to the elements after it by one, as
|
||||
[`insert`](insert.md) does in the other direction. *Every* iterator already taken over `array`/`object` is invalidated
|
||||
by an erase, not only those at or after the removed element, since the container may now use a different sequence.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -49,14 +49,21 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp
|
||||
|
||||
- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view<BasicJsonType, Editable>`)
|
||||
- **value_t** - the JSON type enumeration, see [`basic_json::value_t`](../basic_json/value_t.md)
|
||||
- **image_check** - how [`load()`](load.md) validates an image (`full`, `bounds`, `none`), see [`image_check`](load.md#image_check)
|
||||
|
||||
## Member functions
|
||||
|
||||
- [(constructor)](basic_json_document.md)
|
||||
|
||||
### Parsing
|
||||
|
||||
- [**parse**](parse.md) (_static_) - deserialize from a compatible input, borrowing or owning it as appropriate
|
||||
- [**parse_copy**](parse_copy.md) (_static_) - deserialize a copy of a compatible input
|
||||
- [**accept**](accept.md) (_static_) - check whether the input is valid JSON
|
||||
- [**read**](read.md) - (re-)parse into this document, reusing its memory
|
||||
|
||||
### Access
|
||||
|
||||
- [**root**](root.md) - the view of the root value
|
||||
- [**is_discarded**](is_discarded.md) - return whether the last parse failed
|
||||
- [**source**](source.md) - the parsed text
|
||||
@@ -64,6 +71,14 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp
|
||||
- [**node_count**](node_count.md) - the number of index entries (values plus object keys)
|
||||
- [**memory_usage**](memory_usage.md) - the number of bytes held by the document
|
||||
- [**shrink_to_fit**](shrink_to_fit.md) - release unused index capacity
|
||||
|
||||
### Images
|
||||
|
||||
- [**save**](save.md) - the document as an image that `load()` reads without parsing
|
||||
- [**load**](load.md) (_static_) - read an image written by `save()`
|
||||
|
||||
### Edits
|
||||
|
||||
- [**set**](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers
|
||||
to (`#!cpp Editable` documents only)
|
||||
- [**push_back**](push_back.md) - append to an array (`#!cpp Editable` documents only)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
# <small>nlohmann::basic_json_document::</small>load
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
static basic_json_document load(const std::uint8_t* image, std::size_t size,
|
||||
const image_check check = image_check::full);
|
||||
|
||||
// (2)
|
||||
static basic_json_document load(const std::vector<std::uint8_t>& image,
|
||||
const image_check check = image_check::full);
|
||||
|
||||
// (3)
|
||||
static basic_json_document load(std::vector<std::uint8_t>&& image,
|
||||
const image_check check = image_check::full);
|
||||
```
|
||||
|
||||
1. Reads an image [`save()`](save.md) wrote, from a pointer and a byte count. The image is **borrowed**: `image`
|
||||
must stay alive and unchanged for as long as the returned document, and any view taken from it, is used.
|
||||
2. Reads an image from a `#!cpp std::vector`. Also **borrowed** -- equivalent to overload 1 called with
|
||||
`#!cpp image.data()` and `#!cpp image.size()`.
|
||||
3. Reads an image, keeping the vector instead of copying it: `image` is moved into the document (no copy), which
|
||||
then owns it for as long as it needs the text and the decoded strings. [`owns_source()`](owns_source.md) is
|
||||
`#!cpp true` afterward.
|
||||
|
||||
In every overload, the node index is copied into storage the document itself owns -- so that it is properly aligned,
|
||||
and, for an [editable](index.md#edits) document, can be edited -- while the text and the decoded strings stay in
|
||||
`image`. The hash indexes [large objects](../../features/json_view.md) use for lookup are rebuilt, exactly as after
|
||||
parsing.
|
||||
|
||||
## Parameters
|
||||
|
||||
`image` (in)
|
||||
: the image [`save()`](save.md) wrote (overloads 1 and 2), or one to take ownership of (overload 3)
|
||||
|
||||
`size` (in)
|
||||
: the number of bytes at `image` (overload 1)
|
||||
|
||||
`check` (in)
|
||||
: how thoroughly to validate `image` before trusting it; see [`image_check`](#image_check) below (optional,
|
||||
`#!cpp image_check::full` by default)
|
||||
|
||||
## Return value
|
||||
|
||||
The document read from the image.
|
||||
|
||||
## Exception safety
|
||||
|
||||
Overloads 1 and 2 give the strong guarantee: `image` is only read, never written, so a thrown exception leaves the
|
||||
caller's buffer untouched.
|
||||
|
||||
Overload 3 moves `image` into the document *before* validating it, so that a good image is kept without a copy. If
|
||||
loading then fails, the partially built document -- and the vector now inside it -- is discarded along with the
|
||||
exception, and `image` itself is left **empty**, not restored to what was passed in. Move a copy in instead, or
|
||||
validate with overload 2 first, if the original vector must survive a failed load.
|
||||
|
||||
## Exceptions
|
||||
|
||||
On a big-endian target, throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) -- the same
|
||||
exception [`save()`](save.md#exceptions) throws there, since the image format is little-endian only.
|
||||
|
||||
Otherwise throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one
|
||||
`save()` could have written, or fails the requested `check`:
|
||||
|
||||
| message | when |
|
||||
|------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| `too short` | `image` is `#!cpp nullptr`, or `size` is smaller than the 64-byte header |
|
||||
| `unknown format` | the header's magic bytes or version do not match, or a reserved header field is not zero |
|
||||
| `sizes out of range` | the node count, text size, or decoded-string size the header describes does not fit `size`, or the `#!cpp '\0'` after the text or after the decoded strings is missing |
|
||||
| `the check failed` | `check` is not `#!cpp image_check::none`, and the image fails it -- see [`image_check`](#image_check) |
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: too short
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: unknown format
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: sizes out of range
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: the check failed
|
||||
```
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the number of nodes, which are always copied into the document. `#!cpp image_check::none` does not read
|
||||
the text or the decoded strings. `#!cpp image_check::bounds` reads one byte of the text for each float token (its sign,
|
||||
to check the recorded digits against the token's length), and nothing else of the text or the decoded strings.
|
||||
|
||||
With `#!cpp image_check::full`, linear in the size of the image (the nodes, the text, and the decoded strings), plus
|
||||
sorting the ranges of the strings and of the float tokens (at most one per node): the contents of each distinct range
|
||||
are checked once, however many nodes refer to it.
|
||||
|
||||
## Notes
|
||||
|
||||
### `image_check` { #image_check }
|
||||
|
||||
```cpp
|
||||
using image_check = detail::view::image_check;
|
||||
|
||||
enum class image_check
|
||||
{
|
||||
full,
|
||||
bounds,
|
||||
none
|
||||
};
|
||||
```
|
||||
|
||||
How thoroughly `load()` validates `image` before trusting it.
|
||||
|
||||
`full` (the default)
|
||||
: Checks everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a
|
||||
string still in the source text, that it contains no quote, backslash, or control character); and that every number
|
||||
token is well-formed and matches the value stored for it. Strings and float tokens may share a range only if the
|
||||
ranges are identical (as nodes that share a value do), and never overlap otherwise. Reading and serializing a
|
||||
checked image is safe and always produces valid JSON, exactly as for a parsed document. This is the right choice for
|
||||
an image from anything you do not fully control -- a file, a cache shared with other processes, a peer on the
|
||||
network.
|
||||
|
||||
`bounds`
|
||||
: Checks structure and bounds only: that every offset and count in the node index stays inside the image. So
|
||||
`load()`, [`root()`](root.md), element access, and [`materialize()`](../basic_json_view/materialize.md) can never
|
||||
read outside the image, even on a damaged one. It does *not* guarantee that the image describes valid JSON: a
|
||||
string that a `full` check would have rejected can make [`dump()`](../basic_json_view/dump.md) write invalid UTF-8
|
||||
or invalid JSON, or throw [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316), and a number can
|
||||
read back with a value that does not match how it is spelled. An [editable document](../json_editable_document.md)
|
||||
does not take such a string over: [`set`](set.md), [`push_back`](push_back.md) and [`insert`](insert.md) throw
|
||||
`type_error.316` when they copy it from a view of the loaded document, and leave the editable document unchanged.
|
||||
`bounds` skips scanning the text and the decoded strings, so it fits a cache your own process just wrote and reads
|
||||
straight back, where damage would mean a bug or a hardware fault rather than adversarial input.
|
||||
|
||||
`none`
|
||||
: Checks nothing. Reading a damaged image is undefined behavior, so use it only for an image you trust as much as
|
||||
your own memory.
|
||||
|
||||
**Lifetime.** Overloads 1 and 2 borrow `image`: it must stay alive and byte-for-byte unchanged for as long as the
|
||||
returned document, and any [view](../basic_json_view/index.md) taken from it, is used -- exactly like a document
|
||||
[`parse()`](parse.md) borrowed its input for. Overload 3 avoids this by keeping the vector itself; see
|
||||
[`owns_source`](owns_source.md).
|
||||
|
||||
!!! warning "Experimental"
|
||||
|
||||
The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
|
||||
stable; `load()` already rejects an image written by a different format version with `parse_error.116`
|
||||
("unknown format"). Use images to cache a document within one build of the library, or to hand one to another
|
||||
process running the *same* build on the *same* (little-endian) machine -- not as a long-term storage format.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Caching a document, ownership, and a rejected image"
|
||||
|
||||
The example below saves a parsed document as an image, checks that `load()` reproduces the original
|
||||
[`dump()`](../basic_json_view/dump.md) without parsing, and shows the difference between
|
||||
`load(std::move(image))` (owned) and `load(image)` (borrowed). It then damages one byte of the image and shows
|
||||
`image_check::full` rejecting it with `parse_error.116`, while `image_check::bounds` -- meant for a cache the
|
||||
process already trusts -- still reads it without going out of bounds.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__load.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__load.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [save](save.md) - write the document as an image
|
||||
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
|
||||
- [parse](parse.md) - deserialize from JSON text instead of an image
|
||||
- [Images](../../features/json_view.md#images) - why and when to use images
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -149,6 +149,7 @@ buffer of a given length, pass a pair of pointers: `#!cpp parse(ptr, ptr + len)`
|
||||
- [accept](accept.md) - check whether the input is valid JSON
|
||||
- [read](read.md) - (re-)parse into this document, reusing its memory
|
||||
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
|
||||
- [load](load.md) - read a document from an image instead of parsing JSON text
|
||||
- [`BasicJsonType::parse`](../basic_json/parse.md) - the corresponding function of `basic_json`
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -80,6 +80,7 @@ compile; see [`parse`](parse.md#notes).
|
||||
|
||||
- [parse](parse.md) - deserialize from a compatible input
|
||||
- [root](root.md) - the view of the root value
|
||||
- [load](load.md) - read a document from an image instead of parsing JSON text
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# <small>nlohmann::basic_json_document::</small>save
|
||||
|
||||
```cpp
|
||||
std::vector<std::uint8_t> save() const;
|
||||
```
|
||||
|
||||
Writes the document as an *image*: a byte buffer that [`load`](load.md) reads back without parsing. The image holds
|
||||
the node index, the source text (plus, for an edited document, the number tokens edits wrote), and the decoded
|
||||
strings (plus the strings edits wrote) -- everything [`root()`](root.md) needs, with nothing left to parse.
|
||||
|
||||
An edited document is written in its *current* state, with its values in document order, the way the library's own
|
||||
parser would have produced them for that JSON text: a member [`set`](set.md) added goes at the end, an
|
||||
[`erase`](erase.md)d member leaves no trace, and a float that is not finite (NaN or positive/negative infinity)
|
||||
becomes null, the same substitution [`dump()`](../basic_json_view/dump.md) makes. The same document always saves to
|
||||
the same bytes -- also across `BasicJsonType` and `#!cpp Editable`, since the image reflects document order and
|
||||
values only, not which specialization produced them.
|
||||
|
||||
## Return value
|
||||
|
||||
The image, as a `#!cpp std::vector<std::uint8_t>`. Pass it, or a pointer to its data together with its size, to
|
||||
[`load`](load.md) to read the document back.
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong guarantee: `save()` does not modify `#!cpp *this` (it is `#!cpp const`), so if it throws, the document is left
|
||||
exactly as it was, and the partially built image is discarded with the exception.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) if the document is
|
||||
[discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/
|
||||
[`read()`](read.md) with `allow_exceptions == false` left discarded.
|
||||
|
||||
On a big-endian target, throws `type_error.320` with a different message instead: the image format is little-endian
|
||||
only (see [Notes](#notes)).
|
||||
|
||||
Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or
|
||||
the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets
|
||||
[`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.type_error.320] cannot save a discarded json_document
|
||||
```
|
||||
```
|
||||
[json.exception.type_error.320] json_document images need a little-endian target
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
|
||||
```
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the size of the document: the number of nodes, plus the length of the text and the decoded strings that end
|
||||
up in the image.
|
||||
|
||||
## Notes
|
||||
|
||||
**Format.** The image begins with a 64-byte header (the magic bytes `#!cpp "NJVI"`, a version number, the node count,
|
||||
and the sizes of the text and the decoded strings, all little-endian), followed by the nodes
|
||||
([16 bytes each](../../home/architecture.md#node-index-of-json-views)), the text and a `#!cpp '\0'`, and the decoded
|
||||
strings and a `#!cpp '\0'`. [`load`](load.md) checks the header, and the sizes it describes, before reading anything
|
||||
else -- see [`load`'s Exceptions](load.md#exceptions).
|
||||
|
||||
!!! warning "Experimental"
|
||||
|
||||
The image format is versioned but not yet stable: it may change in an incompatible way before it is declared
|
||||
stable. Use images to cache a document within one build of the library, or to hand one to another process running
|
||||
the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original
|
||||
JSON text if you need to read a saved document back with a future library version.
|
||||
|
||||
**Little-endian only.** The image is written as raw little-endian bytes, with no byte-swapping. `save()` (and
|
||||
[`load`](load.md)) throw `type_error.320` on a big-endian target rather than silently produce bytes a big-endian
|
||||
reader could not interpret correctly.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Caching a parsed document as an image"
|
||||
|
||||
The example below saves a parsed configuration as an image -- the way a service might cache one to answer later
|
||||
requests without parsing the text again -- and confirms that loading it back gives exactly the same result as
|
||||
parsing did, and that saving is deterministic.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__save.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__save.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [load](load.md) - read an image written by `save()`
|
||||
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
|
||||
- [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document to JSON text instead of an image
|
||||
- [Images](../../features/json_view.md#images) - why and when to use images
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -6,7 +6,7 @@ iterator begin() const noexcept;
|
||||
|
||||
Returns an iterator to the first element of an array, or the first member value of an object, in **document order**
|
||||
-- the order the values appear in the source text, not sorted by key. A primitive value iterates as a range of one
|
||||
element (itself); `#!json null` and a [discarded](is_discarded.md) view iterate as an empty range.
|
||||
element (itself), and a `#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range.
|
||||
|
||||
## Return value
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ iterator end() const noexcept;
|
||||
```
|
||||
|
||||
Returns an iterator to one past the last element of an array, one past the last member value of an object, in
|
||||
**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself);
|
||||
`#!json null` and a [discarded](is_discarded.md) view iterate as an empty range, so `#!cpp begin() == end()` for
|
||||
them.
|
||||
**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself), and a
|
||||
`#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range (`#!cpp begin() == end()`).
|
||||
|
||||
## Return value
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ Moving the document itself does not invalidate its views: the index is heap-allo
|
||||
|
||||
`basic_json_view` provides the read-only part of the `BasicJsonType` interface:
|
||||
|
||||
- the type-inspection functions,
|
||||
- element access,
|
||||
- lookup,
|
||||
- iteration,
|
||||
- the type-inspection functions
|
||||
- element access
|
||||
- lookup
|
||||
- iteration
|
||||
- conversion: [`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, and
|
||||
[`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on demand
|
||||
- comparison: [`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.
|
||||
(`#!cpp operator<`) is provided
|
||||
|
||||
[`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and [`value`](value.md) also accept a
|
||||
[`json_pointer`](../json_pointer/index.md). Unlike `basic_json`, there are no implicit conversion operators (no
|
||||
|
||||
@@ -16,6 +16,10 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
|
||||
## Parsing
|
||||
|
||||
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time
|
||||
instead of scanning its stream buffer in bulk
|
||||
- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with `std::fgetc` instead of
|
||||
locking it once and using the unlocked stdio functions
|
||||
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned
|
||||
right after a parsed number
|
||||
- [**JSON_STRICT_BINARY_UTF8**](json_strict_binary_utf8.md) - opt in to checking strings for valid UTF-8 in the CBOR,
|
||||
@@ -34,6 +38,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
||||
- [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically
|
||||
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||
- [**JSON_NO_SIMD**](json_no_simd.md) - use only portable code instead of vector instructions (SSE2, SSSE3, NEON)
|
||||
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
||||
- [**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
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# JSON_DISABLE_STREAM_BULK_SCAN
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp std::istream` reads the input one character at a time through the stream
|
||||
buffer, as in version 3.12.0 and earlier, instead of scanning the characters that the stream buffer already holds in
|
||||
bulk.
|
||||
|
||||
The macro is an escape hatch for stream buffers that do not follow the usual contract of `std::basic_streambuf` (see
|
||||
the requirements on the stream buffer in the notes below). It does not change any result: values, exceptions, and the
|
||||
position of the stream after parsing are the same either way. Only the speed differs.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0`: streams are scanned in bulk.
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "How streams are scanned"
|
||||
|
||||
A `std::streambuf` keeps the part of the input it has read but not yet handed out in its *get area*, the range
|
||||
from `gptr()` to `egptr()`. A `std::filebuf`, for instance, reads the file in blocks of a few kilobytes into its get
|
||||
area, and a `std::stringbuf` exposes its whole string as one get area.
|
||||
|
||||
When parsing from a `std::istream`, the parser scans this get area directly, with the same fast paths that it uses
|
||||
for a `std::string`: runs of ordinary string characters, numbers, and indentation are recognized several bytes at a
|
||||
time instead of being requested from the stream buffer one character at a time. When the parser reaches the end of
|
||||
the get area, it asks the stream buffer for the next character as before, which makes the stream buffer refill its
|
||||
get area.
|
||||
|
||||
The parser only reads characters that it would also have read one at a time, and it advances the stream buffer
|
||||
exactly over the characters it consumes. Nothing is read ahead, buffered elsewhere, or put back. Therefore, the
|
||||
stream is left exactly where it was left before:
|
||||
|
||||
- [`operator>>`](../operator_gtgt.md) and [`sax_parse`](../basic_json/sax_parse.md) with `strict = false` can still
|
||||
read several values from one stream, and the stream can still be used with other `std::istream` functions after a
|
||||
value has been read.
|
||||
- With [`JSON_PRECISE_STREAM_POSITION`](json_precise_stream_position.md), the stream is still positioned right after
|
||||
a parsed number.
|
||||
- Exception messages, including the line, the column, and the last read token, are unchanged.
|
||||
|
||||
!!! note "Performance"
|
||||
|
||||
Parsing from a `std::ifstream` or `std::istringstream` is typically 1.5 to 2 times as fast as before, and reading
|
||||
many small values from one stream with `operator>>` 1.4 to 4 times as fast. Parsing from a stream is then within
|
||||
about 10 to 20 % of first reading the whole file into a `std::string` and parsing that.
|
||||
|
||||
A stream buffer without a get area (one that only overrides `underflow()` and `uflow()` and hands out every
|
||||
character from there) offers nothing to scan in bulk, so it is read one character at a time as before.
|
||||
|
||||
!!! note "Requirements on the stream buffer"
|
||||
|
||||
The bulk scan relies on the following behavior, which every stream buffer of the standard library and every stream
|
||||
buffer that keeps its input in its get area has:
|
||||
|
||||
- When `sgetc()` returns a character while `gptr() < egptr()`, that character is `*gptr()`; when `sbumpc()` returns
|
||||
one, it was at `gptr()` before the call.
|
||||
- Characters in the get area are not changed while they are in the get area.
|
||||
- `underflow()` or `uflow()` either sets up a get area that holds the returned character at `gptr()` (or at
|
||||
`gptr() - 1` after `uflow()`), or leaves the get area empty.
|
||||
|
||||
A stream buffer that returns characters from `uflow()` while leaving unrelated characters in its get area, or that
|
||||
changes the characters in its get area in place, does not meet these requirements. Define the macro to `1` for
|
||||
such stream buffers.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_dsbs`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Other inputs"
|
||||
|
||||
Strings, string views, vectors, arrays, and pointer or iterator ranges over contiguous memory are scanned in bulk
|
||||
anyway. Reading from a `#!cpp FILE*` is not affected by this macro; see
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](json_disable_unlocked_file_io.md) for that input.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: reading values from a stream (default)"
|
||||
|
||||
The stream is scanned in bulk, and the values are read one after the other:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input(R"({"id": 1} {"id": 2} 42 rest)");
|
||||
json j1, j2, j3;
|
||||
input >> j1 >> j2 >> j3; // {"id":1}, {"id":2}, 42
|
||||
|
||||
std::string word;
|
||||
input >> word; // "rest"
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Example: reading one character at a time (macro defined to 1)"
|
||||
|
||||
With the macro, the same program reads the stream one character at a time, with the same results:
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 1
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input(R"({"id": 1} {"id": 2} 42 rest)");
|
||||
json j1, j2, j3;
|
||||
input >> j1 >> j2 >> j3; // {"id":1}, {"id":2}, 42
|
||||
|
||||
std::string word;
|
||||
input >> word; // "rest"
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**operator>>**](../operator_gtgt.md) - deserialize from stream
|
||||
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
|
||||
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - leave an input stream positioned right after a
|
||||
parsed number
|
||||
- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,115 @@
|
||||
# JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, as in version 3.12.0 and
|
||||
earlier, instead of locking the file once and reading it with the unlocked stdio functions.
|
||||
|
||||
It does not change any result: values, exceptions, and the position of the file after parsing are the same either way.
|
||||
Only the speed and the locking of the file differ (see the notes on locking below).
|
||||
|
||||
Builds with MemorySanitizer use `std::fgetc` automatically, because the inline `getc_unlocked` of glibc reads the stdio
|
||||
buffer directly, which glibc (not instrumented) filled, and MemorySanitizer would report it as uninitialized.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0`: the file is locked once and read with the unlocked stdio functions where they are available.
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "Background"
|
||||
|
||||
The C standard library makes every `std::fgetc` call thread-safe by locking and unlocking the `FILE` object. When
|
||||
parsing from a `FILE*`, the parser reads one character at a time, so this happens for every character of the input.
|
||||
|
||||
Instead, the parser locks the file once when parsing starts and unlocks it when parsing ends - also when an
|
||||
exception is thrown - and reads the characters in between without locking:
|
||||
|
||||
| C library | functions |
|
||||
|------------------------------------------------------------|----------------------------------------------------|
|
||||
| glibc (Linux), Apple (macOS, iOS), Android, FreeBSD, NetBSD, OpenBSD, DragonFly BSD | `flockfile`, `getc_unlocked`, `funlockfile` (POSIX) |
|
||||
| Microsoft (MSVC, clang-cl) | `_lock_file`, `_fgetc_nolock`, `_unlock_file` |
|
||||
| others (e.g., musl, MinGW, newlib) | `std::fgetc`, as before |
|
||||
|
||||
This makes parsing from a `FILE*` 3 to 4 times as fast with the Apple C library and 1.1 to 1.3 times as fast with
|
||||
glibc, whose locking is cheaper. The binary formats ([`from_cbor`](../basic_json/from_cbor.md),
|
||||
[`from_msgpack`](../basic_json/from_msgpack.md), ...) read from a `FILE*` the same way.
|
||||
|
||||
!!! note "Locking"
|
||||
|
||||
While a value is parsed from a `FILE*`, the parsing thread holds the file's lock. The lock is recursive, so the
|
||||
parsing thread itself - for instance, a [SAX](../json_sax/index.md) handler or a
|
||||
[parser callback](../basic_json/parser_callback_t.md) - can keep using the file with stdio functions such as
|
||||
`std::ftell`. Other threads that use the same `FILE` object wait until the parser is done. Before, they could
|
||||
interleave their reads with the parser's, which made the parsed input unpredictable anyway.
|
||||
|
||||
A program in which a SAX handler or parser callback waits for **another** thread that uses the **same** `FILE`
|
||||
object would deadlock. Define the macro to `1` for such programs.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_dufio`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Other inputs"
|
||||
|
||||
Reading from a `#!cpp std::istream` is not affected by this macro; see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md) for that input.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: parsing from a `FILE*` (default)"
|
||||
|
||||
The file is locked once while it is parsed:
|
||||
|
||||
```cpp
|
||||
#include <cstdio>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::FILE* f = std::fopen("example.json", "rb");
|
||||
json j = json::parse(f); // f is locked until parse() returns
|
||||
std::fclose(f);
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Example: locking for every character (macro defined to 1)"
|
||||
|
||||
With the macro, every character is read with `std::fgetc`, with the same result:
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 1
|
||||
#include <cstdio>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::FILE* f = std::fopen("example.json", "rb");
|
||||
json j = json::parse(f); // std::fgetc for every character
|
||||
std::fclose(f);
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
|
||||
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a
|
||||
time
|
||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,83 @@
|
||||
# JSON_NO_SIMD
|
||||
|
||||
```cpp
|
||||
#define JSON_NO_SIMD
|
||||
```
|
||||
|
||||
When defined, the library uses only portable C++ code where it would otherwise use vector instructions: when scanning
|
||||
strings during parsing, and when writing floating-point numbers during serialization.
|
||||
|
||||
The macro is an escape hatch, for example for toolchains that cannot compile the vector code, for sanitizers or
|
||||
analyzers that do not understand it, or to compare the speed of both variants. It does not change any result: values,
|
||||
exceptions, and error positions are the same either way. Only the speed differs.
|
||||
|
||||
## Default definition
|
||||
|
||||
By default, `#!cpp JSON_NO_SIMD` is not defined, and vector instructions are used where the target has them.
|
||||
|
||||
```cpp
|
||||
#undef JSON_NO_SIMD
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "Which instructions are used"
|
||||
|
||||
- **x86-64:** SSE2, which every x86-64 CPU has, finds the end of runs of plain string characters 16 bytes at a time
|
||||
and writes the digits of floating-point numbers. The UTF-8 check of non-ASCII text needs SSSE3, which is not part
|
||||
of x86-64: it is compiled for SSSE3 with a function attribute and used when the CPU has SSSE3 (every x86-64 CPU
|
||||
since about 2011), else the portable check. Code compiled for SSSE3 anyway (e.g., with `-mssse3` or `-march=native`)
|
||||
skips this check of the CPU. 32-bit x86 uses SSE2 for strings when the compiler targets it.
|
||||
- **AArch64:** NEON, which every 64-bit Arm CPU has, checks UTF-8 text and long runs of plain string characters 16
|
||||
bytes at a time, and writes the digits of floating-point numbers. With MSVC, only the floating-point digits use
|
||||
NEON.
|
||||
- **Other targets:** portable code only.
|
||||
|
||||
Strings are only scanned this way for contiguous input and for the characters a `#!cpp std::istream` holds in its
|
||||
stream buffer (see [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other inputs are read one
|
||||
character at a time.
|
||||
|
||||
!!! note "Interaction with `JSON_USE_SIMDUTF`"
|
||||
|
||||
With [`JSON_USE_SIMDUTF`](json_use_simdutf.md), runs of string characters are first validated by simdutf; the code
|
||||
described here is used for what simdutf does not accept, so that errors are reported at the same position.
|
||||
`JSON_NO_SIMD` does not affect simdutf.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The macro selects between two definitions of the library's internal scanning and formatting functions. Whether it
|
||||
is defined is encoded in the [namespace](../../features/namespace.md) (tag `_ns`), resulting in distinct symbol
|
||||
names. Translation units compiled with and without it can therefore be linked into the same program without One
|
||||
Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: switching off vector instructions"
|
||||
|
||||
The program parses and serializes the same values with and without the macro:
|
||||
|
||||
```cpp
|
||||
#define JSON_NO_SIMD
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
const json j = json::parse(R"({"name": "日本語 text", "pi": 3.141592653589793})");
|
||||
std::cout << j.dump() << '\n'; // {"name":"日本語 text","pi":3.141592653589793}
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**dump**](../basic_json/dump.md) - serialization
|
||||
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
||||
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time
|
||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -72,6 +72,12 @@ The default value is `0` (disabled — existing behavior is preserved).
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Bulk scanning"
|
||||
|
||||
The parser scans the characters in the stream buffer in bulk (see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)), but it never reads ahead. The stream is left
|
||||
at the same position with and without bulk scanning, in both modes of this macro.
|
||||
|
||||
!!! tip "Workaround without the macro"
|
||||
|
||||
Separate the values in the stream with whitespace. The character consumed after a number is then the separator,
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
|
||||
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
|
||||
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
|
||||
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. On text with many
|
||||
non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
||||
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in validator (which uses SSSE3 or NEON where
|
||||
available, see [`JSON_NO_SIMD`](json_no_simd.md)). On text with many non-ASCII characters (e.g. CJK or emoji) this can
|
||||
validate faster, depending on the CPU.
|
||||
|
||||
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
||||
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
||||
message (simdutf is only used to fast-path *valid* runs; anything it flags falls back to the scalar path so the exact
|
||||
diagnostic is preserved). Streaming inputs (files, `std::istream`, wide strings, user-defined adapters) always use the
|
||||
scalar path.
|
||||
diagnostic is preserved). A `std::istream` uses simdutf for the characters its stream buffer holds (see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other streaming inputs (`FILE*`, wide strings,
|
||||
user-defined adapters) always use the scalar path.
|
||||
|
||||
When `JSON_USE_SIMDUTF` is defined you must make the `simdutf.h` header available on the include path and link the
|
||||
simdutf library. When it is not defined, no simdutf header is included and there is no dependency.
|
||||
|
||||
@@ -39,6 +39,6 @@ the library.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in
|
||||
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_vns`, and `_vssse3` in version
|
||||
3.13.0; see
|
||||
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, `_ns`, `_vns`,
|
||||
and `_vssse3` in version 3.13.0; see
|
||||
[`nlohmann` Namespace](../../features/namespace.md#structure).
|
||||
@@ -121,6 +121,7 @@ being read.
|
||||
- [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_PRECISE_STREAM_POSITION`](macros/json_precise_stream_position.md) - opt in to leaving the stream positioned right after a number
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) - read the stream one character at a time
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -133,3 +134,6 @@ being read.
|
||||
crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0.
|
||||
- Changed to the strong exception safety guarantee in version 3.13.0: `j` is no longer left with a partially parsed
|
||||
value if parsing throws.
|
||||
- Scans the characters in the stream buffer of `i` in bulk in version 3.13.0; the position of `i` after the call is
|
||||
unchanged ([`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) restores reading one character
|
||||
at a time).
|
||||
@@ -135,6 +135,9 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
headers are included.
|
||||
- [x] On Windows, the library is compiled with `<Windows.h>` being included to detect and avoid common bugs (see
|
||||
[`unit-windows_h.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-windows_h.cpp)).
|
||||
- [x] The library is compiled with Qt's keyword macros (`slots`, `signals`, `emit`, `foreach`, `forever`) being defined
|
||||
to make sure it does not use them as identifiers (see
|
||||
[`unit-qt_keywords.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-qt_keywords.cpp)).
|
||||
- [x] The library is compiled with exceptions disabled to support alternative means of error handling.
|
||||
|
||||
## Stable public API
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_document = nlohmann::json_document;
|
||||
using image_check = json_document::image_check;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << std::boolalpha;
|
||||
|
||||
// the image of a parsed document -- as if read back from a cache file or
|
||||
// received from another process running the same build of the library
|
||||
const std::string text = R"({"name": "cache", "note": "caf\u00e9", "replicas": ["db2", "db3"]})";
|
||||
const json_document parsed = json_document::parse(text);
|
||||
const std::vector<std::uint8_t> image = parsed.save();
|
||||
|
||||
// (1)/(2) load() needs no parsing, yet dumps exactly what parsing did
|
||||
const json_document borrowed = json_document::load(image);
|
||||
std::cout << (borrowed.root().dump() == parsed.root().dump()) << '\n';
|
||||
std::cout << borrowed.owns_source() << '\n'; // borrowed: still points into `image`
|
||||
|
||||
// (3) load(std::move(image)) keeps the vector instead of copying it
|
||||
std::vector<std::uint8_t> to_move = image;
|
||||
const json_document owned = json_document::load(std::move(to_move));
|
||||
std::cout << owned.owns_source() << '\n';
|
||||
|
||||
// a damaged image -- the last byte of the decoded string "note" holds
|
||||
// (an escape sequence, so it was unescaped into the document's own
|
||||
// buffer), flipped, as storage or transport corruption might do
|
||||
std::vector<std::uint8_t> damaged = image;
|
||||
damaged[damaged.size() - 2] = 0xFF;
|
||||
|
||||
// image_check::full inspects strings and numbers, so it catches the damage
|
||||
try
|
||||
{
|
||||
static_cast<void>(json_document::load(damaged, image_check::full));
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
std::cout << e.id << '\n';
|
||||
}
|
||||
|
||||
// image_check::bounds only checks structure and bounds, so a cache the
|
||||
// process already trusts loads without the extra scan -- reading a value
|
||||
// the damage did not touch is still safe
|
||||
const json_document trusted = json_document::load(damaged, image_check::bounds);
|
||||
std::cout << trusted.root()["name"].get<std::string>() << '\n';
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
true
|
||||
false
|
||||
true
|
||||
116
|
||||
cache
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << std::boolalpha;
|
||||
|
||||
// a configuration a service parses once and then caches as an image, so
|
||||
// that later requests can load() it instead of parsing the text again
|
||||
const std::string text = R"({"name": "cache", "host": "db1", "port": 6379, "replicas": ["db2", "db3"]})";
|
||||
const json_document config = json_document::parse(text);
|
||||
|
||||
// save() turns the parsed document into a byte buffer: a 64-byte header,
|
||||
// the node index, the source text, and the decoded strings
|
||||
const std::vector<std::uint8_t> image = config.save();
|
||||
std::cout << image.size() << '\n';
|
||||
|
||||
// the same document always saves to the same bytes
|
||||
std::cout << (image == json_document::parse(text).save()) << '\n';
|
||||
|
||||
// loading the image back needs no parsing, yet dumps exactly what
|
||||
// parsing the text produced
|
||||
const json_document reloaded = json_document::load(image);
|
||||
std::cout << (reloaded.root().dump() == config.root().dump()) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
316
|
||||
true
|
||||
true
|
||||
@@ -14,7 +14,8 @@ C++ types, and finally serialize it again.
|
||||
[parsing untrusted input](parsing/untrusted_input.md).
|
||||
- [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree;
|
||||
strings and numbers stay in the input and are only decoded when needed.
|
||||
[Editable documents](json_view.md#editing-a-document) can also be modified.
|
||||
- [Editable documents](json_view.md#editing-a-document) — modify a parsed document without building a `json` tree.
|
||||
- [Images](json_view.md#images) — save a parsed document and load it again without parsing it.
|
||||
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
|
||||
|
||||
## Accessing and modifying values
|
||||
|
||||
@@ -312,6 +312,57 @@ edits. See [`basic_json_document`'s Edits](../api/basic_json_document/index.md#e
|
||||
throws (the *basic* guarantee, not the strong one `dump()` and the read-only functions provide). How edits are kept in
|
||||
the index is described in the [architecture overview](../home/architecture.md#node-index-of-json-views).
|
||||
|
||||
## Images
|
||||
|
||||
[`save()`](../api/basic_json_document/save.md) writes a document as an *image*: a byte buffer that
|
||||
[`load()`](../api/basic_json_document/load.md) reads back into a document without parsing -- no lexing, no building
|
||||
the node index, nothing but copying the nodes and pointing the text and the decoded strings at the image. Where
|
||||
[`parse_copy()`](../api/basic_json_document/parse_copy.md) still has to scan the whole input,
|
||||
[`load()`](../api/basic_json_document/load.md) turns that scan into a copy of the node index alone.
|
||||
|
||||
**Why.** A document that is parsed once and then read many times -- a configuration loaded at startup, a template
|
||||
rendered on every request, a large reference dataset a worker process needs in memory -- pays for parsing once but
|
||||
can amortize [`save()`](../api/basic_json_document/save.md)'s cost across every later load. That makes images useful
|
||||
for a cache: save a document the first time it is parsed (to a file, a shared-memory segment, an in-process cache),
|
||||
and [`load()`](../api/basic_json_document/load.md) it on every later use instead of parsing the source text again.
|
||||
They are just as useful for handing a parsed document to another process (or a forked worker) running the same build
|
||||
of the library, since [`load()`](../api/basic_json_document/load.md) turns the transfer into a copy of the node index
|
||||
plus pointers into the received bytes, not a re-parse.
|
||||
|
||||
**Choosing a check.** [`load()`](../api/basic_json_document/load.md) takes an
|
||||
[`image_check`](../api/basic_json_document/load.md#image_check) that trades validation against speed:
|
||||
`image_check::full` (the default) checks everything the parser itself guarantees, so a checked image is exactly as
|
||||
safe to read and serialize as a freshly parsed document -- the right choice whenever the image did not come straight
|
||||
from this process's own [`save()`](../api/basic_json_document/save.md), such as a file or a network peer.
|
||||
`image_check::bounds` only checks structure and bounds -- cheaper, since it skips scanning the text and the decoded
|
||||
strings -- and fits a cache the process trusts, one it wrote and reads back itself. `image_check::none` skips
|
||||
validation entirely, for an image trusted as much as the process's own memory. See
|
||||
[`load()`'s Notes](../api/basic_json_document/load.md#notes) for exactly what each level does and does not guarantee.
|
||||
|
||||
??? example "Example: cache a parsed configuration as an image"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__save.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__save.output"
|
||||
```
|
||||
|
||||
!!! warning "Experimental"
|
||||
|
||||
The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
|
||||
stable. It is little-endian only, and tied to the library build that wrote it -- use it to cache a document or to
|
||||
hand one to another process running the *same* build, not as a long-term storage format; keep the original JSON
|
||||
text if a saved document needs to be readable by a future library version.
|
||||
|
||||
The idea of a document you can read without parsing comes from zero-copy formats such as
|
||||
[FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check
|
||||
[`load()`](../api/basic_json_document/load.md) runs follows the idea of FlatBuffers' Verifier. No code is taken from
|
||||
either.
|
||||
|
||||
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
|
||||
|
||||
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) | [`json_editable_document`](../api/json_editable_document.md) / [`json_editable_view`](../api/json_editable_view.md) |
|
||||
@@ -320,6 +371,7 @@ the index is described in the [architecture overview](../home/architecture.md#no
|
||||
| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only | [`set`](../api/basic_json_document/set.md)/[`push_back`](../api/basic_json_document/push_back.md)/[`insert`](../api/basic_json_document/insert.md)/[`erase`](../api/basic_json_document/erase.md) edit in place; the source text is never rewritten |
|
||||
| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use | the same, plus [`dump()`](../api/basic_json_view/dump.md) of an edited document that keeps the member order and, with [`number_format::source`](../api/basic_json_view/number_format.md), the spelling of every untouched number |
|
||||
| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back -- a configuration file, for instance -- where the rest of it should come back exactly as it was |
|
||||
| **Caching/reload** | not applicable -- re-parse, or roll your own serialization | not applicable | [`save()`](../api/basic_json_document/save.md)/[`load()`](../api/basic_json_document/load.md): cache the parsed index as an image and reload it without parsing | same, saving the document's current -- possibly edited -- state |
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -133,6 +133,14 @@ When defined, default parse and serialize functions for enums are excluded and h
|
||||
|
||||
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
|
||||
|
||||
## `JSON_DISABLE_STREAM_BULK_SCAN`
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp std::istream` reads one character at a time instead of scanning the
|
||||
characters that the stream buffer holds in bulk. Results are the same either way; the macro is an escape hatch for
|
||||
stream buffers that do not follow the usual `std::streambuf` contract. The default value is `0`.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md).
|
||||
|
||||
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
|
||||
|
||||
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
||||
@@ -141,6 +149,14 @@ is planned to become the default in version 4.0.0.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
||||
|
||||
## `JSON_DISABLE_UNLOCKED_FILE_IO`
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, which locks the file each
|
||||
time, instead of locking the file once and reading it with the unlocked stdio functions. Results are the same either
|
||||
way. The default value is `0`.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## `JSON_NO_AUTOMATIC_UDLS`
|
||||
|
||||
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
||||
@@ -158,6 +174,13 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
|
||||
|
||||
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
||||
|
||||
## `JSON_NO_SIMD`
|
||||
|
||||
When defined, the library uses only portable code instead of vector instructions (SSE2, SSSE3, NEON) for scanning
|
||||
strings and writing floating-point numbers. Results are the same either way. It is not defined by default.
|
||||
|
||||
See [full documentation of `JSON_NO_SIMD`](../api/macros/json_no_simd.md).
|
||||
|
||||
## `JSON_NO_THREAD_LOCAL`
|
||||
|
||||
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
||||
|
||||
@@ -23,6 +23,11 @@ The complete default namespace name is derived as follows:
|
||||
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
|
||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero
|
||||
appends `_ekmo`.
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) defined non-zero appends
|
||||
`_dsbs`.
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends
|
||||
`_dufio`.
|
||||
- [`JSON_NO_SIMD`](../api/macros/json_no_simd.md) defined appends `_ns`.
|
||||
- [`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md) defined appends `_vns`.
|
||||
- [`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md) defined appends `_vssse3`.
|
||||
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
||||
@@ -108,6 +113,9 @@ follows:
|
||||
([`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md)), `_snul`
|
||||
([`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md)), `_sbu8`
|
||||
([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), `_ekmo`
|
||||
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_vns`
|
||||
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_dsbs`
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md)), `_dufio`
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), `_ns`
|
||||
([`JSON_NO_SIMD`](../api/macros/json_no_simd.md)), `_vns`
|
||||
([`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md)), and `_vssse3`
|
||||
([`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md)) in version 3.13.0.
|
||||
@@ -55,6 +55,34 @@ whitespace, because `operator>>` consumes the character that terminates a number
|
||||
[`JSON_PRECISE_STREAM_POSITION`](../../api/macros/json_precise_stream_position.md) is defined to `1` — see the
|
||||
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
|
||||
|
||||
## Input sources and performance
|
||||
|
||||
All inputs produce the same values and the same exceptions, but they are read in different ways, which makes some of
|
||||
them faster than others:
|
||||
|
||||
| input | how it is read |
|
||||
|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
|
||||
| strings, string views, string literals, contiguous containers, pointer or iterator ranges over contiguous memory | the whole input is scanned in bulk: runs of string characters, numbers, and indentation are recognized several bytes at a time |
|
||||
| `#!cpp std::istream` (`#!cpp std::ifstream`, `#!cpp std::istringstream`, ...) | the characters the stream buffer holds are scanned in bulk like a string; the stream buffer is asked for more when they are used up |
|
||||
| `#!cpp FILE*` | one character at a time, with the file locked once for the whole parse |
|
||||
| other iterator ranges (e.g., `#!cpp std::list<char>`, `#!cpp std::istreambuf_iterator<char>`), wide strings | one character at a time |
|
||||
|
||||
Parsing a file from a `#!cpp std::ifstream` is therefore only about 10 to 20 % slower than reading the whole file into a
|
||||
`#!cpp std::string` first and parsing that, and it needs no memory for a copy of the file. The stream is never read
|
||||
ahead: after parsing, it is positioned exactly as if every character had been read one at a time, so
|
||||
[`operator>>`](../../api/operator_gtgt.md) can still read several values from one stream (see also
|
||||
[JSON Lines](json_lines.md)).
|
||||
|
||||
Two macros switch these optimizations off without changing any result:
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not
|
||||
follow the usual `std::streambuf` contract, and
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
|
||||
thread must use the same `#!cpp FILE` object while it is parsed.
|
||||
|
||||
Where the target has them, strings are scanned with vector instructions: SSE2 on x86-64 (and SSSE3 for UTF-8 text, when
|
||||
the CPU has it) and NEON on AArch64. [`JSON_NO_SIMD`](../../api/macros/json_no_simd.md) selects portable code instead,
|
||||
again without changing any result.
|
||||
|
||||
## SAX vs. DOM parsing
|
||||
|
||||
The library offers two parsing models:
|
||||
|
||||
@@ -20,32 +20,39 @@ below apply on every supported standard. This adapter exposes two optimizations
|
||||
|
||||
- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is
|
||||
read, and
|
||||
- the lexer can scan ordinary string characters directly out of the buffer, several bytes at a time, rather than one
|
||||
character (and one function call) at a time.
|
||||
- the lexer can scan ordinary string characters, numbers, and indentation directly out of the buffer, several bytes at
|
||||
a time, rather than one character (and one function call) at a time.
|
||||
|
||||
A `#!cpp std::istream` (including `#!cpp std::ifstream`) or `#!cpp FILE*`, by contrast, is read through
|
||||
`input_stream_adapter` or `file_input_adapter`, which read one character (or one block, for binary formats) at a time
|
||||
and expose neither optimization -- the lexer falls back to the same byte-at-a-time path it uses for any
|
||||
non-contiguous, general-purpose iterator range. An iterator pair over non-contiguous but random-access storage (e.g.
|
||||
`#!cpp std::deque<char>::iterator`) gets the first optimization but not the second, since the byte-scanning fast path
|
||||
additionally requires contiguous storage.
|
||||
A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scanning: `input_stream_adapter` scans the
|
||||
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
|
||||
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
|
||||
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
|
||||
area is read one character at a time. A `#!cpp FILE*` is still read one character at a time, but `file_input_adapter`
|
||||
locks the file once per parse instead of once per character. See
|
||||
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. According to the
|
||||
description of [#5828](https://github.com/nlohmann/json/pull/5828), parsing from a stream is within about 10 to 20 % of
|
||||
parsing an equivalent `#!cpp std::string`.
|
||||
|
||||
Practically: if the JSON text is already in memory, or small enough to read into memory, prefer passing a
|
||||
`#!cpp std::string`, a `#!cpp std::vector<char>`, or a pointer range to `parse` over a `#!cpp std::istream`. For a
|
||||
file, that means reading it into a string first and then parsing the string, rather than passing a
|
||||
`#!cpp std::ifstream` directly to `parse` -- the latter never benefits from either optimization:
|
||||
Inputs that are still read one character at a time: iterator ranges that are not contiguous (e.g.
|
||||
`#!cpp std::list<char>` or `#!cpp std::istreambuf_iterator<char>`), wide strings, and user-defined adapters. An
|
||||
iterator pair over non-contiguous but random-access storage (e.g. `#!cpp std::deque<char>::iterator`) gets the first
|
||||
optimization but not the second, since the byte-scanning fast path requires contiguous storage.
|
||||
|
||||
Practically: a file can be passed directly to `parse`, which also avoids the memory for a copy of the file. Reading it
|
||||
into a string first is no longer necessary for speed:
|
||||
|
||||
```cpp
|
||||
// gets the contiguous fast paths
|
||||
// fast: the stream buffer's get area is scanned in bulk
|
||||
std::ifstream f("example.json");
|
||||
std::string contents((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
json j = json::parse(contents);
|
||||
|
||||
// does not: input_stream_adapter has no fast path
|
||||
std::ifstream f2("example.json");
|
||||
json j2 = json::parse(f2);
|
||||
json j = json::parse(f);
|
||||
```
|
||||
|
||||
Two macros switch the stream optimizations off without changing any result:
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not follow
|
||||
the usual `#!cpp std::streambuf` contract, and
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) for programs in which another thread
|
||||
must use the same `#!cpp FILE` object while it is parsed.
|
||||
|
||||
For contiguous input with many non-ASCII characters, [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) can
|
||||
additionally speed up UTF-8 validation by using the [simdutf](https://github.com/simdutf/simdutf) library instead of
|
||||
the built-in scalar validator; streaming inputs (files, `#!cpp std::istream`, wide strings, user-defined adapters)
|
||||
|
||||
@@ -64,7 +64,7 @@ below are relative to `include/nlohmann`.
|
||||
|-----------|----------|
|
||||
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
|
||||
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
|
||||
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp) |
|
||||
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp), [`detail/input/simd_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/simd_scan.hpp) |
|
||||
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
|
||||
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
|
||||
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
|
||||
@@ -194,8 +194,8 @@ packet-beta
|
||||
|
||||
| Bytes | Field | Type | Contents |
|
||||
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below) |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bits 3 and 4: moved and new (see below) |
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below). There is no node for a discarded value: a [discarded](../api/basic_json_view/is_discarded.md) view has none |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bit 3: moved, bit 4: new, bit 5: linked (see below) |
|
||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 |
|
||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||
@@ -257,9 +257,19 @@ edited:
|
||||
sequence (a header node, then the entries), whose number is in `off`. The entries are links (`kind` 10), whose bytes
|
||||
8-15 hold the address of the value's node, so values never move.
|
||||
- A node written by an edit gets the flag *new* (bit 4): it has no position in the source text.
|
||||
- A value that an entry of such a sequence links to gets the flag *linked* (bit 5): its extent in the parsed layout
|
||||
(`next`) no longer matters, so a scalar can replace an array or object there in place.
|
||||
- Views of read-only documents compile without any of this: how views walk the index is a template parameter
|
||||
(`navigation<Editable>`).
|
||||
|
||||
Images ([`save`](../api/basic_json_document/save.md) and [`load`](../api/basic_json_document/load.md),
|
||||
[`detail/view/image.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/image.hpp)) store
|
||||
the nodes as they are: a 64-byte header (the magic bytes `NJVI`, a format version, the sizes, and reserved bytes that
|
||||
must be zero), the nodes, the text, and the decoded strings. An edited document is first written in document order, as
|
||||
the parser would have written it (without links), and the numbers of the hash indexes are cleared, since `load`
|
||||
rebuilds the indexes. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
||||
and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them.
|
||||
|
||||
## Input adapters
|
||||
|
||||
Input is read via **input adapters** that abstract a source. Every input adapter provides this interface:
|
||||
@@ -329,6 +339,11 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
|
||||
- `output_stream_adapter` writes to a `std::ostream`.
|
||||
- `output_string_adapter` appends to a string.
|
||||
|
||||
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
|
||||
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
|
||||
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
|
||||
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
|
||||
|
||||
## Value conversion
|
||||
|
||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||
|
||||
@@ -10,6 +10,29 @@ at its root, a [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/
|
||||
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
||||
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
||||
|
||||
The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more
|
||||
ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration:
|
||||
|
||||
1. Check out the release tag of the library version you use.
|
||||
2. List the ABI tags and the macros that enable them:
|
||||
|
||||
```shell
|
||||
make natvis NATVIS_ARGS="--list-tags"
|
||||
```
|
||||
|
||||
3. Generate a Natvis file with the tags of your configuration, for example for `JSON_DIAGNOSTICS`,
|
||||
`JSON_DIAGNOSTIC_POSITIONS`, and `JSON_VIEW_NO_SIMD`:
|
||||
|
||||
```shell
|
||||
make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my_nlohmann_json.natvis"
|
||||
```
|
||||
|
||||
4. Use the generated file instead of `nlohmann_json.natvis`.
|
||||
|
||||
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 12
|
||||
ABI tags, doubling with each further tag). See
|
||||
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
|
||||
|
||||
Debug engines that wrap LLDB instead of the MSVC debug engine (for example, `codelldb` in VS Code) only have
|
||||
partial/experimental Natvis support, and commonly fall back to showing raw internal fields even with the
|
||||
`.natvis` file present. Switching to `cppvsdbg` where available, or checking your debug extension's own Natvis
|
||||
|
||||
@@ -395,6 +395,23 @@ A UBJSON high-precision number could not be parsed.
|
||||
[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A
|
||||
```
|
||||
|
||||
### json.exception.parse_error.116
|
||||
|
||||
[`basic_json_document::load()`](../api/basic_json_document/load.md) rejected an
|
||||
[image](../features/json_view.md#images): either the bytes are not one [`save()`](../api/basic_json_document/save.md)
|
||||
could have written (too short, an unknown magic number or format version, or sizes that do not fit the buffer), or
|
||||
they are, but fail the requested [`image_check`](../api/basic_json_document/load.md#image_check).
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: the check failed
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
|
||||
|
||||
## Iterator errors
|
||||
|
||||
This exception is thrown if iterators passed to a library function do not match
|
||||
@@ -831,6 +848,26 @@ from JSON text.
|
||||
|
||||
This exception was added in version 3.13.0, together with editable [`json_document`s](../features/json_view.md).
|
||||
|
||||
### json.exception.type_error.320
|
||||
|
||||
[`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an
|
||||
[image](../features/json_view.md#images) of a [discarded](../api/basic_json_document/is_discarded.md) document.
|
||||
[`save()`](../api/basic_json_document/save.md) and [`load()`](../api/basic_json_document/load.md) also throw this
|
||||
exception on a big-endian target, since the image format is little-endian only.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.type_error.320] cannot save a discarded json_document
|
||||
```
|
||||
```
|
||||
[json.exception.type_error.320] json_document images need a little-endian target
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
|
||||
|
||||
### json.exception.type_error.321
|
||||
|
||||
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
|
||||
@@ -935,9 +972,19 @@ double-precision number when `number_float_t` is `#!cpp float`.
|
||||
|
||||
### json.exception.out_of_range.407
|
||||
|
||||
An unsigned integer number greater than 9223372036854775807 cannot be represented in the binary format. This exception
|
||||
is only thrown by [`to_bon8`](../api/basic_json/to_bon8.md), as [BON8](../features/binary_formats/bon8.md) has no
|
||||
unsigned 64-bit integer type.
|
||||
An integer number cannot be represented by the binary format it is serialized to:
|
||||
|
||||
- [BON8](../features/binary_formats/bon8.md) only stores integers that fit into int64, so
|
||||
[`to_bon8`](../api/basic_json/to_bon8.md) throws for unsigned integers greater than 9223372036854775807.
|
||||
- [CBOR](../features/binary_formats/cbor.md), [MessagePack](../features/binary_formats/messagepack.md), and
|
||||
[BSON](../features/binary_formats/bson.md) store integers in at most 64 bits. With the default number types, every
|
||||
integer fits, but a [`number_integer_t`](../api/basic_json/number_integer_t.md) or
|
||||
[`number_unsigned_t`](../api/basic_json/number_unsigned_t.md) wider than 64 bits (e.g., `__int128`) can hold values
|
||||
outside the range of the format: [-2^64, 2^64-1] for CBOR, [-2^63, 2^64-1] for MessagePack, and the range of int64
|
||||
(signed integers) or uint64 (unsigned integers) for BSON.
|
||||
|
||||
[UBJSON](../features/binary_formats/ubjson.md) and [BJData](../features/binary_formats/bjdata.md) never throw this
|
||||
exception, because they serialize integers beyond 64 bits as high-precision numbers.
|
||||
|
||||
!!! note "Former occurrences"
|
||||
|
||||
@@ -946,11 +993,17 @@ unsigned 64-bit integer type.
|
||||
- Since version 3.9.0, integer numbers beyond int64 are serialized as high-precision UBJSON numbers.
|
||||
- Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers.
|
||||
|
||||
!!! failure "Example message"
|
||||
Before version 3.13.0, CBOR, MessagePack, and BSON silently truncated integers wider than 64 bits, and BJData
|
||||
truncated unsigned integers wider than 64 bits.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
integer number 18446744073709551615 cannot be represented by BON8 as it does not fit int64
|
||||
```
|
||||
```
|
||||
integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]
|
||||
```
|
||||
|
||||
### json.exception.out_of_range.408
|
||||
|
||||
@@ -1088,7 +1141,9 @@ MessagePack's ext type and BSON's binary subtype are each stored in a single byt
|
||||
so they do not support an input of 4294967280 bytes (4 GiB minus 16 bytes) or more. The same 32-bit limit applies to an **editable** document's own
|
||||
storage: [`set`](../api/basic_json_document/set.md) and [`push_back`](../api/basic_json_document/push_back.md) throw
|
||||
this exception once the strings and number tokens written by edits reach 4 GiB in total, or once more than
|
||||
4294967295 arrays/objects have had an element set or appended to them.
|
||||
4294967295 arrays/objects have had an element set or appended to them. The same limit applies to an
|
||||
[image](../features/json_view.md#images): [`save()`](../api/basic_json_document/save.md) throws it if the node
|
||||
count, the text, or the decoded strings it would write would individually reach 4 GiB.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
@@ -1098,6 +1153,9 @@ this exception once the strings and number tokens written by edits reach 4 GiB i
|
||||
```
|
||||
[json.exception.out_of_range.416] edits of 4 GiB or more are not supported by json_document
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
|
||||
@@ -242,6 +242,7 @@ nav:
|
||||
- 'erase': api/basic_json_document/erase.md
|
||||
- 'insert': api/basic_json_document/insert.md
|
||||
- 'is_discarded': api/basic_json_document/is_discarded.md
|
||||
- 'load': api/basic_json_document/load.md
|
||||
- 'memory_usage': api/basic_json_document/memory_usage.md
|
||||
- 'node_count': api/basic_json_document/node_count.md
|
||||
- 'owns_source': api/basic_json_document/owns_source.md
|
||||
@@ -250,6 +251,7 @@ nav:
|
||||
- 'push_back': api/basic_json_document/push_back.md
|
||||
- 'read': api/basic_json_document/read.md
|
||||
- 'root': api/basic_json_document/root.md
|
||||
- 'save': api/basic_json_document/save.md
|
||||
- 'set': api/basic_json_document/set.md
|
||||
- 'shrink_to_fit': api/basic_json_document/shrink_to_fit.md
|
||||
- 'source': api/basic_json_document/source.md
|
||||
@@ -368,7 +370,9 @@ nav:
|
||||
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
|
||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
||||
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
||||
- 'JSON_DISABLE_STREAM_BULK_SCAN': api/macros/json_disable_stream_bulk_scan.md
|
||||
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
||||
- 'JSON_DISABLE_UNLOCKED_FILE_IO': api/macros/json_disable_unlocked_file_io.md
|
||||
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20, JSON_HAS_CPP_23, JSON_HAS_CPP_26': api/macros/json_has_cpp_11.md
|
||||
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
||||
- 'JSON_HAS_RANGE_VIEW_CONVERSION': api/macros/json_has_range_view_conversion.md
|
||||
@@ -379,6 +383,7 @@ nav:
|
||||
- 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md
|
||||
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
||||
- 'JSON_NO_IO': api/macros/json_no_io.md
|
||||
- 'JSON_NO_SIMD': api/macros/json_no_simd.md
|
||||
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
||||
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
|
||||
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
||||
|
||||
+24
-23
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "check-mermaid",
|
||||
"dependencies": {
|
||||
"jsdom": "30.1.2",
|
||||
"jsdom": "30.1.3",
|
||||
"mermaid": "11.17.2"
|
||||
}
|
||||
},
|
||||
@@ -40,9 +40,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
|
||||
"integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
|
||||
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bidi-js": "^1.1.0",
|
||||
@@ -1105,9 +1105,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.16",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz",
|
||||
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==",
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
|
||||
"integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
@@ -1202,13 +1202,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "30.1.2",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
|
||||
"integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
|
||||
"version": "30.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
|
||||
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/css-color": "^7.1.3",
|
||||
"@asamuzakjp/dom-selector": "^9.2.4",
|
||||
"@asamuzakjp/dom-selector": "^10.0.3",
|
||||
"@bramus/specificity": "^2.4.2",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
||||
"@exodus/bytes": "^1.16.0",
|
||||
@@ -1221,7 +1221,7 @@
|
||||
"parse5": "^8.0.1",
|
||||
"saxes": "^6.0.0",
|
||||
"tough-cookie": "^6.0.2",
|
||||
"undici": "^8.11.2",
|
||||
"undici": "^8.12.1",
|
||||
"w3c-xmlserializer": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.1",
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
@@ -1241,28 +1241,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.47",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz",
|
||||
"integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==",
|
||||
"version": "0.18.11",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.18.11.tgz",
|
||||
"integrity": "sha512-yvyz/2VMKqoho/35Hat5w37wIEdWJ5ElVMidTAlWrxr5uW4aBGsEpveTmKh18/zw08v/7lGswCPon4OhqmqrTA==",
|
||||
"deprecated": "Accidentally published with breaking changes. Use 0.19.0 instead.",
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
"https://github.com/sponsors/katex"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^8.3.0"
|
||||
"commander": "^15.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"katex": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/katex/node_modules/commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz",
|
||||
"integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
"node": ">=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/khroma": {
|
||||
@@ -1521,9 +1522,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "8.11.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
|
||||
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
|
||||
"version": "8.12.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
|
||||
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"jsdom": "30.1.2",
|
||||
"jsdom": "30.1.3",
|
||||
"mermaid": "11.17.2"
|
||||
},
|
||||
"overrides": {
|
||||
"katex": "0.18.11"
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,14 @@
|
||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -102,6 +110,24 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN _dsbs
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO _dufio
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||
#endif
|
||||
|
||||
#ifdef JSON_NO_SIMD
|
||||
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD _ns
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||
#else
|
||||
@@ -119,9 +145,9 @@
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l, m) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
@@ -133,6 +159,9 @@
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||
NLOHMANN_JSON_ABI_TAG_NO_SIMD, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||
|
||||
|
||||
@@ -14,11 +14,24 @@
|
||||
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
||||
#endif
|
||||
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
|
||||
#include <intrin.h> // _BitScanForward, _BitScanReverse
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
|
||||
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
|
||||
#endif
|
||||
|
||||
// Portable bit-level helpers for the number and string scanners. They use
|
||||
// compiler builtins or platform-specific intrinsics where available and plain
|
||||
// C++ otherwise, so they work regardless of byte order.
|
||||
// compiler builtins or platform-specific intrinsics where available, the
|
||||
// C++20 <bit> functions next, and plain C++ otherwise, so they work regardless
|
||||
// of byte order. (The builtins and intrinsics come first: MSVC's
|
||||
// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.)
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -33,6 +46,16 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||
_BitScanReverse64(&index, x);
|
||||
return 63 - static_cast<int>(index);
|
||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||
if (_BitScanReverse(&index, static_cast<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
|
||||
{
|
||||
return 31 - static_cast<int>(index);
|
||||
}
|
||||
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
|
||||
return 63 - static_cast<int>(index);
|
||||
#elif defined(__cpp_lib_bitops)
|
||||
return std::countl_zero(x);
|
||||
#else
|
||||
int n = 0;
|
||||
for (int shift = 32; shift != 0; shift >>= 1)
|
||||
@@ -56,6 +79,16 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||
_BitScanForward64(&index, x);
|
||||
return static_cast<int>(index);
|
||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||
if (_BitScanForward(&index, static_cast<unsigned long>(x))) // NOLINT(runtime/int)
|
||||
{
|
||||
return static_cast<int>(index);
|
||||
}
|
||||
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
|
||||
return 32 + static_cast<int>(index);
|
||||
#elif defined(__cpp_lib_bitops)
|
||||
return std::countr_zero(x);
|
||||
#else
|
||||
int n = 0;
|
||||
for (int shift = 32; shift != 0; shift >>= 1)
|
||||
@@ -70,6 +103,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
|
||||
template<typename To, typename From>
|
||||
JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept
|
||||
{
|
||||
static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size");
|
||||
#if defined(__cpp_lib_bit_cast)
|
||||
return std::bit_cast<To>(from);
|
||||
#else
|
||||
To to {};
|
||||
std::memcpy(&to, &from, sizeof(To));
|
||||
return to;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower
|
||||
/// it to a single bswap/rev/movbe
|
||||
inline std::uint16_t byteswap(std::uint16_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_bswap16(x);
|
||||
#elif defined(_MSC_VER)
|
||||
return _byteswap_ushort(x);
|
||||
#elif defined(__cpp_lib_byteswap)
|
||||
return std::byteswap(x);
|
||||
#else
|
||||
return static_cast<std::uint16_t>((x >> 8u) | (x << 8u));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||
inline std::uint32_t byteswap(std::uint32_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_bswap32(x);
|
||||
#elif defined(_MSC_VER)
|
||||
return _byteswap_ulong(x);
|
||||
#elif defined(__cpp_lib_byteswap)
|
||||
return std::byteswap(x);
|
||||
#else
|
||||
return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u)
|
||||
| ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||
inline std::uint64_t byteswap(std::uint64_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_bswap64(x);
|
||||
#elif defined(_MSC_VER)
|
||||
return _byteswap_uint64(x);
|
||||
#elif defined(__cpp_lib_byteswap)
|
||||
return std::byteswap(x);
|
||||
#else
|
||||
x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u);
|
||||
x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u);
|
||||
x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u);
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the 128-bit product of two 64-bit numbers
|
||||
struct uint128_parts
|
||||
{
|
||||
@@ -107,8 +201,8 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
|
||||
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
||||
{
|
||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
// the byte order already matches (all MSVC targets are little-endian)
|
||||
#if JSON_LITTLE_ENDIAN_TARGET
|
||||
// the byte order already matches
|
||||
std::uint64_t result = 0;
|
||||
std::memcpy(&result, b, sizeof(result));
|
||||
return result;
|
||||
|
||||
@@ -18,17 +18,14 @@
|
||||
#include <limits> // numeric_limits
|
||||
#include <type_traits> // conditional
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <cstdlib> // _byteswap_uint64
|
||||
#endif
|
||||
|
||||
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
||||
// digits of a double at once
|
||||
#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
|
||||
// digits of a double at once (unless JSON_NO_SIMD selects the portable code)
|
||||
#if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)))
|
||||
#include <emmintrin.h>
|
||||
#define JSON_DTOA_SSE2 1
|
||||
#define JSON_DTOA_NEON 0
|
||||
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
||||
#elif !defined(JSON_NO_SIMD) && (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
||||
#include <arm_neon.h>
|
||||
#define JSON_DTOA_SSE2 0
|
||||
#define JSON_DTOA_NEON 1
|
||||
@@ -37,6 +34,7 @@
|
||||
#define JSON_DTOA_NEON 0
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/conversions/zmij.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
@@ -66,16 +64,6 @@ For a detailed description of the algorithm see:
|
||||
namespace dtoa_impl
|
||||
{
|
||||
|
||||
template<typename Target, typename Source>
|
||||
Target reinterpret_bits(const Source source)
|
||||
{
|
||||
static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
|
||||
|
||||
Target target;
|
||||
std::memcpy(&target, &source, sizeof(Source));
|
||||
return target;
|
||||
}
|
||||
|
||||
struct diyfp // f * 2^e
|
||||
{
|
||||
static constexpr int kPrecision = 64; // = q
|
||||
@@ -230,7 +218,7 @@ boundaries compute_boundaries(FloatType value)
|
||||
|
||||
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
|
||||
|
||||
const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value));
|
||||
const auto bits = static_cast<std::uint64_t>(bit_cast<bits_type>(value));
|
||||
const std::uint64_t E = bits >> (kPrecision - 1);
|
||||
const std::uint64_t F = bits & (kHiddenBit - 1);
|
||||
|
||||
@@ -1084,13 +1072,10 @@ inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept
|
||||
/// the byte stores once this is inlined)
|
||||
inline void store_msb_first(char* p, std::uint64_t v) noexcept
|
||||
{
|
||||
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
v = __builtin_bswap64(v);
|
||||
#if JSON_LITTLE_ENDIAN_TARGET
|
||||
v = byteswap(v);
|
||||
std::memcpy(p, &v, sizeof(v));
|
||||
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
std::memcpy(p, &v, sizeof(v));
|
||||
#elif defined(_MSC_VER) // (little-endian on all its targets)
|
||||
v = _byteswap_uint64(v);
|
||||
#elif JSON_BIG_ENDIAN_TARGET
|
||||
std::memcpy(p, &v, sizeof(v));
|
||||
#else
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
@@ -1342,6 +1327,66 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep
|
||||
return end + (three ? 5 : 4);
|
||||
}
|
||||
|
||||
/// the powers of ten up to 10^19 (all that fit in std::uint64_t)
|
||||
inline const std::array<std::uint64_t, 20>& powers_of_ten() noexcept
|
||||
{
|
||||
static const std::array<std::uint64_t, 20> powers =
|
||||
{
|
||||
{
|
||||
1u,
|
||||
10u,
|
||||
100u,
|
||||
1000u,
|
||||
10000u,
|
||||
100000u,
|
||||
1000000u,
|
||||
10000000u,
|
||||
100000000u,
|
||||
1000000000u,
|
||||
10000000000u,
|
||||
100000000000u,
|
||||
1000000000000u,
|
||||
10000000000000u,
|
||||
100000000000000u,
|
||||
1000000000000000u,
|
||||
10000000000000000u,
|
||||
100000000000000000u,
|
||||
1000000000000000000u,
|
||||
10000000000000000000u
|
||||
}
|
||||
};
|
||||
return powers;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief digits * 10^exp, as write_decimal() writes it, for the digits of a
|
||||
double that need no conversion (count digits, at most 15, the first not 0;
|
||||
trailing zeros allowed): extended to 16 digits and written by write_shortest()
|
||||
|
||||
@return a pointer past the text; up to 41 bytes at @a first are written
|
||||
(some beyond the returned end)
|
||||
*/
|
||||
JSON_HEDLEY_NON_NULL(1)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
inline char* write_short_decimal(char* first, std::uint64_t digits, int count, int exp) noexcept
|
||||
{
|
||||
JSON_ASSERT(digits >= powers_of_ten()[static_cast<std::size_t>(count - 1)] && count <= 15);
|
||||
const int scale = 16 - count;
|
||||
return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false});
|
||||
}
|
||||
|
||||
/// as write_short_decimal(), counting the digits (not 0, less than 10^15)
|
||||
JSON_HEDLEY_NON_NULL(1)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
inline char* write_short_decimal(char* first, std::uint64_t digits, int exp) noexcept
|
||||
{
|
||||
JSON_ASSERT(digits != 0 && digits < 1000000000000000u);
|
||||
// floor(log10(2^bits)) + 1 digits, or one less
|
||||
const int log2_bound = ((64 - count_leading_zeros(digits)) * 1233) >> 12;
|
||||
const int count = log2_bound + (digits >= powers_of_ten()[static_cast<std::size_t>(log2_bound)] ? 1 : 0);
|
||||
return write_short_decimal(first, digits, count, exp);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether FloatType is an IEEE 754 binary64 type (a double, or a long double
|
||||
that has the same format, as with MSVC and on Apple's Arm CPUs)
|
||||
@@ -1402,8 +1447,7 @@ char* write_positive_zmij(char* first, const char* last, FloatType value)
|
||||
{
|
||||
static_assert(is_binary64<FloatType>::value,
|
||||
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
|
||||
std::uint64_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
const auto bits = bit_cast<std::uint64_t>(value);
|
||||
const zmij::shortest_decimal d = zmij::to_shortest(bits);
|
||||
if (JSON_HEDLEY_LIKELY(last - first >= 41))
|
||||
{
|
||||
|
||||
@@ -8,16 +8,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // count, find, min
|
||||
#include <algorithm> // count, find, max, min
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // strlen
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||
#include <limits> // numeric_limits
|
||||
#include <memory> // unique_ptr
|
||||
#include <streambuf> // streambuf
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||
#include <utility> // pair, declval
|
||||
#include <utility> // declval, move, pair
|
||||
#include <vector> // vector
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
#include <cstdio> // FILE *
|
||||
@@ -30,6 +33,33 @@
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
// Which unlocked stdio functions file_input_adapter uses (see
|
||||
// JSON_DISABLE_UNLOCKED_FILE_IO): 1 = POSIX flockfile/getc_unlocked,
|
||||
// 2 = Microsoft CRT _lock_file/_fgetc_nolock, 0 = none (std::fgetc, which
|
||||
// locks the file for every character). Only C libraries known to declare
|
||||
// the functions are listed; the macros are tested after <cstdio>, which
|
||||
// defines __GLIBC__. MemorySanitizer builds always use std::fgetc: glibc's
|
||||
// inline getc_unlocked reads the stdio buffer directly, which glibc (not
|
||||
// instrumented) filled, so MemorySanitizer would report a false positive.
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(memory_sanitizer)
|
||||
#define JSON_UNLOCKED_FILE_IO_MSAN 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO || defined(JSON_UNLOCKED_FILE_IO_MSAN)
|
||||
#define JSON_UNLOCKED_FILE_IO 0
|
||||
#elif defined(_MSC_VER)
|
||||
#define JSON_UNLOCKED_FILE_IO 2
|
||||
#elif defined(__GLIBC__) || defined(__APPLE__) || defined(__ANDROID__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
#define JSON_UNLOCKED_FILE_IO 1
|
||||
#else
|
||||
#define JSON_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
@@ -43,6 +73,82 @@ enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/*!
|
||||
A growable array of characters for the part of a token that an input adapter
|
||||
copied out of its window: unlike std::vector<char>::push_back(), appending a
|
||||
character takes no reference to a local, and is a few instructions when there
|
||||
is room.
|
||||
*/
|
||||
class token_carry_buffer
|
||||
{
|
||||
public:
|
||||
token_carry_buffer() = default;
|
||||
token_carry_buffer(const token_carry_buffer&) = delete;
|
||||
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
|
||||
token_carry_buffer(token_carry_buffer&& rhs) noexcept
|
||||
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
|
||||
{
|
||||
rhs.m_end = rhs.m_capacity_end = nullptr;
|
||||
}
|
||||
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
|
||||
~token_carry_buffer() = default;
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
m_end = m_data.get();
|
||||
}
|
||||
|
||||
void push_back(char c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
*m_end++ = c;
|
||||
}
|
||||
|
||||
void append(const char* first, const char* last)
|
||||
{
|
||||
const auto n = static_cast<std::size_t>(last - first);
|
||||
const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
|
||||
if (n > room)
|
||||
{
|
||||
grow(n);
|
||||
}
|
||||
if (n != 0)
|
||||
{
|
||||
std::memcpy(m_end, first, n);
|
||||
}
|
||||
m_end += n;
|
||||
}
|
||||
|
||||
/// replace the contents of @a out
|
||||
void copy_to(std::vector<char>& out) const
|
||||
{
|
||||
out.assign(m_data.get(), m_end);
|
||||
}
|
||||
|
||||
private:
|
||||
/// make room for @a extra more characters
|
||||
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
|
||||
{
|
||||
const auto size = static_cast<std::size_t>(m_end - m_data.get());
|
||||
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
|
||||
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
|
||||
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
if (size != 0)
|
||||
{
|
||||
std::memcpy(data.get(), m_data.get(), size);
|
||||
}
|
||||
m_data = std::move(data);
|
||||
m_end = m_data.get() + size;
|
||||
m_capacity_end = m_data.get() + new_capacity;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
|
||||
char* m_end = nullptr;
|
||||
char* m_capacity_end = nullptr;
|
||||
};
|
||||
/*!
|
||||
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
||||
buffer. This adapter is a very low level adapter.
|
||||
*/
|
||||
@@ -56,18 +162,35 @@ class file_input_adapter
|
||||
: m_file(f)
|
||||
{
|
||||
JSON_ASSERT(m_file != nullptr);
|
||||
lock();
|
||||
}
|
||||
|
||||
// make class move-only
|
||||
file_input_adapter(const file_input_adapter&) = delete;
|
||||
file_input_adapter(file_input_adapter&&) noexcept = default;
|
||||
file_input_adapter(file_input_adapter&& rhs) noexcept
|
||||
: m_file(rhs.m_file)
|
||||
{
|
||||
// the lock is owned by the thread, not the adapter: hand it over
|
||||
rhs.m_file = nullptr;
|
||||
}
|
||||
file_input_adapter& operator=(const file_input_adapter&) = delete;
|
||||
file_input_adapter& operator=(file_input_adapter&&) = delete;
|
||||
~file_input_adapter() = default;
|
||||
~file_input_adapter()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
|
||||
std::char_traits<char>::int_type get_character() noexcept
|
||||
{
|
||||
// the file is locked for the lifetime of the adapter, so the
|
||||
// per-character lock of std::fgetc can be skipped
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
return getc_unlocked(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
return _fgetc_nolock(m_file);
|
||||
#else
|
||||
return std::fgetc(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns the number of characters successfully read
|
||||
@@ -78,6 +201,32 @@ class file_input_adapter
|
||||
}
|
||||
|
||||
private:
|
||||
// Lock the file once for the whole parse instead of once per character.
|
||||
// The lock is recursive, so fread() and the user's own stdio calls on the
|
||||
// same thread keep working.
|
||||
void lock() noexcept
|
||||
{
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
flockfile(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
_lock_file(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
void unlock() noexcept
|
||||
{
|
||||
// a moved-from adapter has no file and holds no lock
|
||||
if (m_file == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
funlockfile(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
_unlock_file(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the file pointer to read from
|
||||
std::FILE* m_file;
|
||||
};
|
||||
@@ -127,15 +276,27 @@ class input_stream_adapter
|
||||
input_stream_adapter& operator=(input_stream_adapter&) = delete;
|
||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
, lookahead(rhs.lookahead)
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
, token_carry(std::move(rhs.token_carry))
|
||||
, token_segment(rhs.token_segment)
|
||||
, current_in_get_area(rhs.current_in_get_area)
|
||||
, tracking_token(rhs.tracking_token)
|
||||
, no_get_area(rhs.no_get_area)
|
||||
#endif
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
rhs.lookahead = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// Whether the character last returned by get_character() can be given back
|
||||
// to the input with release_lookahead().
|
||||
static constexpr bool supports_lookahead = true;
|
||||
@@ -155,17 +316,41 @@ class input_stream_adapter
|
||||
sb->sbumpc();
|
||||
}
|
||||
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sgetc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto res = sb->sgetc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
// there is nothing to step over next time
|
||||
lookahead = false;
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookahead = true;
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the peeked character is at gptr(), unless the streambuf has
|
||||
// no get area
|
||||
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_end(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -179,13 +364,6 @@ class input_stream_adapter
|
||||
lookahead = false;
|
||||
}
|
||||
#else
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
}
|
||||
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
@@ -194,13 +372,21 @@ class input_stream_adapter
|
||||
// stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
auto res = sb->sbumpc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// the character is in the get area: step over it, as sbumpc() does
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
|
||||
{
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
current_in_get_area = true;
|
||||
streambuf_access::bump(sb, 1);
|
||||
return std::char_traits<char>::to_int_type(*next);
|
||||
}
|
||||
return res;
|
||||
if (no_get_area)
|
||||
{
|
||||
return get_character_no_get_area();
|
||||
}
|
||||
#endif
|
||||
return get_character_slow();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -209,6 +395,18 @@ class input_stream_adapter
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
commit_lookahead();
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// the elements are in the get area: copy them, as sgetn() does, but
|
||||
// without the virtual call
|
||||
const char* const first = streambuf_access::get_ptr(sb);
|
||||
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
|
||||
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
|
||||
{
|
||||
std::memcpy(dest, first, count * sizeof(T));
|
||||
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
|
||||
return count * sizeof(T);
|
||||
}
|
||||
#endif
|
||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||
@@ -218,7 +416,248 @@ class input_stream_adapter
|
||||
return res;
|
||||
}
|
||||
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// The lexer may scan the characters in the get area of the streambuf
|
||||
// directly (SWAR fast paths). The get area is only a window into the
|
||||
// input: it ends wherever the streambuf's buffer ends, and is refilled by
|
||||
// the next get_character(). The lexer therefore keeps counting the
|
||||
// position itself and treats the end of the window as "unknown", not as
|
||||
// the end of the input.
|
||||
static constexpr bool supports_bulk_scan = true;
|
||||
static constexpr bool bulk_window = true;
|
||||
|
||||
// Pointer to the next unread character; only valid when
|
||||
// bulk_remaining() > 0. The character before it is the one last returned
|
||||
// by get_character().
|
||||
const char* bulk_data() const
|
||||
{
|
||||
return current_end();
|
||||
}
|
||||
|
||||
// Number of unread characters in the get area.
|
||||
std::size_t bulk_remaining() const
|
||||
{
|
||||
if (!current_in_get_area)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return static_cast<std::size_t>(streambuf_access::get_end(sb) - current_end());
|
||||
}
|
||||
|
||||
// Consume @a n characters previously inspected via bulk_data(); the last
|
||||
// of them becomes the character last returned by get_character().
|
||||
void bulk_skip(std::size_t n)
|
||||
{
|
||||
JSON_ASSERT(n <= bulk_remaining());
|
||||
// gbump() takes an int, but a get area (e.g., of a std::stringbuf) can
|
||||
// be larger
|
||||
constexpr auto max_bump = static_cast<std::size_t>((std::numeric_limits<int>::max)());
|
||||
for (; n > max_bump; n -= max_bump)
|
||||
{
|
||||
streambuf_access::bump(sb, static_cast<int>(max_bump));
|
||||
}
|
||||
streambuf_access::bump(sb, static_cast<int>(n));
|
||||
}
|
||||
|
||||
// The adapter keeps the current token for the lexer's error messages: the
|
||||
// part in the get area is only remembered as a pointer, and copied out
|
||||
// when the get area is about to be refilled.
|
||||
static constexpr bool supports_token_tracking = true;
|
||||
|
||||
// Start a token with the characters read from now on. Until this is
|
||||
// called, nothing is tracked (the binary readers never call it).
|
||||
void begin_token() noexcept
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
token_segment = current_end();
|
||||
}
|
||||
|
||||
// Start a new token with the character @a c last returned by
|
||||
// get_character().
|
||||
void mark_token_start(std::char_traits<char>::int_type c)
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
if (current_in_get_area)
|
||||
{
|
||||
token_segment = current_end() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_segment = nullptr;
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the characters read since mark_token_start() to @a out, without the
|
||||
// last one if @a drop_last.
|
||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||
{
|
||||
token_carry.copy_to(out);
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
out.insert(out.end(), token_segment, current_end());
|
||||
}
|
||||
if (drop_last && !out.empty())
|
||||
{
|
||||
out.pop_back();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if !JSON_PRECISE_STREAM_POSITION
|
||||
// the get area is exhausted, or there is none
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
#endif
|
||||
std::char_traits<char>::int_type get_character_slow()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// Reads the protected get-area pointers of any std::streambuf. Naming the
|
||||
// members through a derived class makes forming the member pointers
|
||||
// accessible; calling them on another streambuf object is then ordinary
|
||||
// member access through a pointer to member.
|
||||
struct streambuf_access : std::streambuf
|
||||
{
|
||||
static const char* get_ptr(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::gptr))();
|
||||
}
|
||||
static const char* get_end(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::egptr))();
|
||||
}
|
||||
static const char* get_begin(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::eback))();
|
||||
}
|
||||
static void bump(std::streambuf* s, int n)
|
||||
{
|
||||
(s->*(&streambuf_access::gbump))(n);
|
||||
}
|
||||
// sbumpc() for an exhausted get area
|
||||
static std::streambuf::int_type next_character(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::uflow))();
|
||||
}
|
||||
};
|
||||
|
||||
// one past the character last returned by get_character(), if that is in
|
||||
// the get area
|
||||
const char* current_end() const
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// the character is peeked at gptr() and not stepped over yet, unless
|
||||
// it was given back with release_lookahead()
|
||||
return streambuf_access::get_ptr(sb) + (lookahead ? 1 : 0);
|
||||
#else
|
||||
return streambuf_access::get_ptr(sb);
|
||||
#endif
|
||||
}
|
||||
|
||||
// the get area is about to be replaced: copy the token's part in it
|
||||
void save_token_segment()
|
||||
{
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
token_carry.append(token_segment, streambuf_access::get_end(sb));
|
||||
token_segment = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#if !JSON_PRECISE_STREAM_POSITION
|
||||
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
|
||||
// every character comes from uflow(), and the token is kept by value.
|
||||
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
|
||||
{
|
||||
// the get area is exhausted, so this is what sbumpc() does
|
||||
const auto res = streambuf_access::next_character(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
|
||||
{
|
||||
return get_character_no_get_area_other(res);
|
||||
}
|
||||
// current_in_get_area is false: only the first character of a get
|
||||
// area, or the end of the input, is read here
|
||||
if (tracking_token)
|
||||
{
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(res));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// @a res came from uflow() of a streambuf without a get area: it is the
|
||||
// end of the input, or the streambuf has a get area now
|
||||
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
|
||||
{
|
||||
if (res == std::char_traits<char>::eof())
|
||||
{
|
||||
current_in_get_area = false;
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
return res;
|
||||
}
|
||||
// the consumed character is at gptr() - 1
|
||||
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
// character @a c was just read into a new get area (@a in_get_area) or
|
||||
// returned without one
|
||||
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
|
||||
{
|
||||
current_in_get_area = in_get_area;
|
||||
no_get_area = !in_get_area;
|
||||
if (!tracking_token)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (in_get_area)
|
||||
{
|
||||
token_segment = current_end() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// Step over the character last returned by get_character(). The character
|
||||
// has already been peeked successfully, so for every streambuf with a get
|
||||
@@ -240,6 +679,19 @@ class input_stream_adapter
|
||||
/// whether get_character() peeked a character that is not consumed yet
|
||||
bool lookahead = false;
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
/// the part of the current token that was in earlier get areas
|
||||
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||
/// start of the current token's part in the get area, or nullptr
|
||||
const char* token_segment = nullptr;
|
||||
/// whether the character last returned by get_character() is in the get area
|
||||
bool current_in_get_area = false;
|
||||
/// whether a token is tracked at all; the binary readers never start one
|
||||
bool tracking_token = false;
|
||||
/// whether the streambuf had no get area when it was last asked for a
|
||||
/// character
|
||||
bool no_get_area = false;
|
||||
#endif
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
|
||||
@@ -418,8 +418,9 @@ class json_sax_dom_parser
|
||||
JSON_ASSERT(!ref_stack.empty());
|
||||
JSON_ASSERT(ref_stack.back()->is_object());
|
||||
|
||||
// add null at the given key and store the reference for later
|
||||
object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val));
|
||||
// add null at the given key and store the reference for later; the
|
||||
// key is moved, as the lexer starts the next token with reset()
|
||||
object_element = &(ref_stack.back()->m_data.m_value.object->operator[](std::move(val)));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <cstring> // memcmp
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <string> // char_traits, string
|
||||
#include <utility> // move
|
||||
@@ -166,6 +167,43 @@ constexpr bool input_adapter_supports_bulk_scan(std::false_type /*detected*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter keeps the current token itself and can
|
||||
// hand it out on demand (see input_stream_adapter::supports_token_tracking),
|
||||
// so the lexer need not copy every scanned character into token_string.
|
||||
template<typename InputAdapterType>
|
||||
using detect_supports_token_tracking = decltype(InputAdapterType::supports_token_tracking);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_token_tracking(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::supports_token_tracking;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_token_tracking(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether the contiguous block of a bulk-scan adapter is only a window
|
||||
// into the input (see input_stream_adapter::bulk_window): its end is not the
|
||||
// end of the input, so a token that touches it must be finished character by
|
||||
// character.
|
||||
template<typename InputAdapterType>
|
||||
using detect_bulk_window = decltype(InputAdapterType::bulk_window);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_bulk_window(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::bulk_window;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_bulk_window(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief lexical analysis
|
||||
|
||||
@@ -184,9 +222,20 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// whether the last read token can be reconstructed from the input adapter
|
||||
/// on demand (in error paths) instead of being copied on every scanned
|
||||
/// character; see input_adapter_supports_seek
|
||||
static constexpr bool lazy_token_string =
|
||||
static constexpr bool seekable =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether the adapter keeps the token itself (mark_token_start() /
|
||||
/// copy_token()); see input_adapter_supports_token_tracking
|
||||
static constexpr bool adapter_token =
|
||||
!seekable && input_adapter_supports_token_tracking<InputAdapterType>(is_detected<detect_supports_token_tracking, InputAdapterType> {});
|
||||
|
||||
/// whether the last read token is obtained from the input adapter on
|
||||
/// demand (in error paths) instead of being copied on every scanned
|
||||
/// character: seekable adapters reconstruct it from the consumed input,
|
||||
/// token-tracking adapters keep it themselves
|
||||
static constexpr bool lazy_token_string = seekable || adapter_token;
|
||||
|
||||
/// whether the read position is derived from the input adapter on demand
|
||||
/// instead of being counted on every scanned character. Seekable adapters
|
||||
/// know how much input they consumed and can revisit it, so
|
||||
@@ -194,7 +243,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// and line and column are recomputed from the consumed input only when
|
||||
/// get_position() is called (errors). Counting per character kept two
|
||||
/// increments and a newline check in the hot path of every get().
|
||||
static constexpr bool lazy_position = lazy_token_string;
|
||||
static constexpr bool lazy_position = seekable;
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
@@ -203,14 +252,17 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||
|
||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||
/// the per-character capture in get() cannot lose error diagnostics. It
|
||||
/// also implies lazy_position, so the bulk paths need not advance the
|
||||
/// position counters.
|
||||
/// directly from a contiguous input buffer (SWAR fast path). Without
|
||||
/// lazy_token_string / lazy_position, the bulk paths copy the consumed
|
||||
/// characters into token_string and advance the position counters
|
||||
/// themselves (see bulk_consume).
|
||||
static constexpr bool bulk_scan =
|
||||
lazy_token_string
|
||||
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
|
||||
/// whether the contiguous block is only a window into the input, so that
|
||||
/// its end does not end a token; see input_adapter_bulk_window
|
||||
static constexpr bool bulk_window =
|
||||
input_adapter_bulk_window<InputAdapterType>(is_detected<detect_bulk_window, InputAdapterType> {});
|
||||
|
||||
public:
|
||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||
@@ -219,7 +271,11 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
: ia(std::move(adapter))
|
||||
, ignore_comments(ignore_comments_)
|
||||
, discard_number_values(discard_number_values_)
|
||||
{}
|
||||
{
|
||||
// the token string of an error before the first token is everything
|
||||
// read so far
|
||||
begin_token(std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
|
||||
// deleted because of pointer members
|
||||
lexer(const lexer&) = delete;
|
||||
@@ -236,8 +292,8 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// contiguous input: try to decode the 4 hex digits following `\\u`
|
||||
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
||||
/// to get(). On success, advances the adapter exactly as those 4 get()
|
||||
/// calls would (bulk_scan implies lazy_position, so there are no position
|
||||
/// counters to advance) and leaves @a current holding the last of
|
||||
/// calls would (including the position and token bookkeeping of a
|
||||
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
|
||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
||||
/// written to @a out. Makes no state change and returns false for a
|
||||
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
||||
@@ -256,7 +312,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ia.bulk_skip(4);
|
||||
bulk_consume(reinterpret_cast<const char*>(raw), 4);
|
||||
current = char_traits<char_type>::to_int_type(raw[3]);
|
||||
out = codepoint;
|
||||
return true;
|
||||
@@ -362,34 +418,79 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
}
|
||||
|
||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||
/// well-formed UTF-8 sequences starting at the current read position, leaving
|
||||
/// the first byte that needs individual handling (the closing quote, an
|
||||
/// escape, a control character, or an ill-formed UTF-8 byte) for get()
|
||||
void scan_string_bulk(std::true_type /*bulk*/)
|
||||
/// well-formed UTF-8 sequences starting at the current read position, then
|
||||
/// read the first byte that needs individual handling (the closing quote, an
|
||||
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||
/// Apple Clang merged this read with the other get() calls of the scan
|
||||
/// function, which load the read position again right after it was stored
|
||||
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
||||
{
|
||||
// a pending unget must be consumed through the normal path first
|
||||
if (next_unget)
|
||||
{
|
||||
return;
|
||||
return get();
|
||||
}
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining == 0)
|
||||
{
|
||||
return;
|
||||
return get();
|
||||
}
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const auto* const raw = ia.bulk_data();
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(raw);
|
||||
|
||||
const std::size_t pos = string_bulk_run(data, remaining);
|
||||
if (pos == 0)
|
||||
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||
if (pos != 0)
|
||||
{
|
||||
return;
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||
}
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||
ia.bulk_skip(pos);
|
||||
if (pos == remaining)
|
||||
{
|
||||
return get(); // end of the contiguous input
|
||||
}
|
||||
// what get() does, with the byte at hand
|
||||
advance_position();
|
||||
ia.bulk_skip(1);
|
||||
current = char_traits<char_type>::to_int_type(raw[pos]);
|
||||
return track_after_read();
|
||||
}
|
||||
|
||||
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
||||
/// the bookkeeping get() would have done for them; the characters are
|
||||
/// never newlines
|
||||
void bulk_consume(const char* data, std::size_t n)
|
||||
{
|
||||
bulk_track_position(n, std::integral_constant<bool, lazy_position> {});
|
||||
bulk_track_token(data, n, std::integral_constant<bool, lazy_token_string> {});
|
||||
ia.bulk_skip(n);
|
||||
}
|
||||
|
||||
/// seekable adapter: the position is derived from the adapter
|
||||
void bulk_track_position(std::size_t /*n*/, std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: count the characters
|
||||
void bulk_track_position(std::size_t n, std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
position.chars_read_total += n;
|
||||
position.chars_read_current_line += n;
|
||||
}
|
||||
|
||||
/// the token is obtained from the adapter
|
||||
void bulk_track_token(const char* /*data*/, std::size_t /*n*/, std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: capture the characters
|
||||
void bulk_track_token(const char* data, std::size_t n, std::false_type /*lazy*/)
|
||||
{
|
||||
token_string.insert(token_string.end(), data, data + n);
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {}
|
||||
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
||||
{
|
||||
return get();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan a string literal
|
||||
@@ -417,11 +518,8 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
while (true)
|
||||
{
|
||||
// bulk-consume ordinary characters from contiguous input, then
|
||||
// handle the next special byte through the switch below
|
||||
scan_string_bulk(std::integral_constant<bool, bulk_scan> {});
|
||||
|
||||
// get the next character
|
||||
switch (get())
|
||||
// get the next character, the one that ends the run
|
||||
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
||||
{
|
||||
// end of file while parsing the string
|
||||
case char_traits<char_type>::eof():
|
||||
@@ -1541,9 +1639,12 @@ scan_number_done:
|
||||
const std::size_t avail = rem + 1;
|
||||
|
||||
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
||||
// and accumulate the mantissa digits on the way, so that tokens with up
|
||||
// to 19 digits need no second pass for their value
|
||||
std::size_t i = 0;
|
||||
std::size_t dot_index = std::string::npos;
|
||||
token_type number_type = token_type::value_unsigned;
|
||||
std::uint64_t w = 0;
|
||||
if (data[0] == '-')
|
||||
{
|
||||
number_type = token_type::value_integer;
|
||||
@@ -1553,22 +1654,21 @@ scan_number_done:
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
}
|
||||
const std::size_t integer_begin = i;
|
||||
if (data[i] == '0')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
else if (data[i] >= '1' && data[i] <= '9')
|
||||
{
|
||||
++i;
|
||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
i = accumulate_digits(data, i, avail, w);
|
||||
}
|
||||
else
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
std::size_t digit_count = i - integer_begin;
|
||||
std::size_t fraction_digits = 0;
|
||||
if (i < avail && data[i] == '.')
|
||||
{
|
||||
number_type = token_type::value_float;
|
||||
@@ -1578,31 +1678,45 @@ scan_number_done:
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
const std::size_t fraction_begin = i;
|
||||
i = accumulate_digits(data, i, avail, w);
|
||||
fraction_digits = i - fraction_begin;
|
||||
digit_count += fraction_digits;
|
||||
}
|
||||
// the mantissa ends here, whether or not an exponent part follows
|
||||
const std::size_t mantissa_end = i;
|
||||
std::int64_t exponent = 0;
|
||||
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||
{
|
||||
number_type = token_type::value_float;
|
||||
++i;
|
||||
bool negative_exponent = false;
|
||||
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||
{
|
||||
negative_exponent = data[i] == '-';
|
||||
++i;
|
||||
}
|
||||
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
// saturated far beyond every range, see parse_float_exponent()
|
||||
constexpr std::int64_t saturation = 100000000000000000; // 10^17
|
||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||
{
|
||||
if (exponent < saturation)
|
||||
{
|
||||
exponent = (exponent * 10) + (data[i] - '0');
|
||||
}
|
||||
++i;
|
||||
}
|
||||
exponent = negative_exponent ? -exponent : exponent;
|
||||
}
|
||||
const std::size_t len = i;
|
||||
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
|
||||
// reset() records where this token starts (for diagnostics), so it has
|
||||
// to run before the input position advances below
|
||||
@@ -1617,10 +1731,14 @@ scan_number_done:
|
||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||
if (number_type != token_type::value_float)
|
||||
{
|
||||
const token_type integer_result = convert_integer(number_type, data, data + len);
|
||||
// up to 18 digits always fit into std::uint64_t and std::int64_t
|
||||
const token_type integer_result = (digit_count <= 18)
|
||||
? store_integer(number_type, w)
|
||||
: convert_integer(number_type, data, data + len);
|
||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||
{
|
||||
ia.bulk_skip(len - 1);
|
||||
bulk_consume(data + 1, len - 1);
|
||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||
return integer_result;
|
||||
}
|
||||
// The value does not fit an integer, so this token converts as a
|
||||
@@ -1630,17 +1748,99 @@ scan_number_done:
|
||||
}
|
||||
#endif
|
||||
|
||||
decimal_point_position = dot_index;
|
||||
|
||||
bulk_consume(data + 1, len - 1);
|
||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||
|
||||
// a float whose digits all fit into w converts from the accumulated
|
||||
// value; longer ones (and other float formats) take the general path
|
||||
if (number_type == token_type::value_float && digit_count <= 19)
|
||||
{
|
||||
float_significand s;
|
||||
s.w = w;
|
||||
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
|
||||
s.negative = data[0] == '-';
|
||||
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
|
||||
{
|
||||
// the token text is only copied into token_buffer when
|
||||
// get_string() asks for it; the library's own SAX handlers
|
||||
// ignore it. A window into a stream may be refilled by the
|
||||
// next read, so there the text is copied right away.
|
||||
if (bulk_window)
|
||||
{
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||
}
|
||||
else
|
||||
{
|
||||
pending_number_text = data;
|
||||
pending_number_length = len;
|
||||
}
|
||||
return token_type::value_float;
|
||||
}
|
||||
}
|
||||
|
||||
// materialize the token exactly as scan_number() would. reset() already
|
||||
// cleared token_buffer, so append() fills it (assign() is avoided
|
||||
// because custom string_t types need not provide it)
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||
decimal_point_position = dot_index;
|
||||
|
||||
ia.bulk_skip(len - 1);
|
||||
|
||||
return convert_number(number_type, mantissa_end);
|
||||
}
|
||||
|
||||
/// window adapter: a number that reaches the end of the window may continue
|
||||
/// after it, so scan_number() has to read it character by character
|
||||
static constexpr bool number_may_continue(bool at_end, std::true_type /*window*/) noexcept
|
||||
{
|
||||
return at_end;
|
||||
}
|
||||
|
||||
/// the end of the block is the end of the input, which ends the number
|
||||
static constexpr bool number_may_continue(bool /*at_end*/, std::false_type /*window*/) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// window adapter: read the character after a bulk-scanned number and unget
|
||||
/// it, as scan_number() does. The adapter is a stream that is handed back to
|
||||
/// the user, who must find the same input consumed either way.
|
||||
void read_number_terminator(std::true_type /*window*/)
|
||||
{
|
||||
get();
|
||||
unget();
|
||||
}
|
||||
|
||||
/// the input is not handed back, so the terminator can stay unread
|
||||
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
||||
|
||||
/// binary32/binary64: the value of a float token from its accumulated
|
||||
/// significand (see scan_number_bulk_contiguous())
|
||||
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
||||
{
|
||||
value_float = decimal_to_float<number_float_t>(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// other formats (e.g. x87 long double): not instantiated for them, as
|
||||
/// decimal_to_float() only supports binary32/binary64
|
||||
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// the value of an integer token with at most 18 digits, accumulated in @a w
|
||||
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
|
||||
{
|
||||
if (number_type == token_type::value_unsigned)
|
||||
{
|
||||
value_unsigned = static_cast<number_unsigned_t>(w);
|
||||
// reject values that do not round-trip into a narrower type
|
||||
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
|
||||
}
|
||||
const std::int64_t x = -static_cast<std::int64_t>(w);
|
||||
value_integer = static_cast<number_integer_t>(x);
|
||||
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
|
||||
}
|
||||
|
||||
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||
{
|
||||
@@ -1655,18 +1855,23 @@ scan_number_done:
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] literal_text the literal text to expect
|
||||
@param[in] length the length of the passed literal text
|
||||
@param[in] return_type the token type to return on success
|
||||
@param[in] rest the literal text after its first character, which
|
||||
is current (its length is a template parameter, so
|
||||
that the comparison of the contiguous fast path is
|
||||
a fixed-size load, not a call of memcmp)
|
||||
@param[in] return_type the token type to return on success
|
||||
*/
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
token_type scan_literal(const char_type* literal_text, const std::size_t length,
|
||||
token_type return_type)
|
||||
template<std::size_t N>
|
||||
JSON_HEDLEY_NEVER_INLINE token_type scan_literal(const char (&rest)[N], token_type return_type) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);
|
||||
for (std::size_t i = 1; i < length; ++i)
|
||||
constexpr std::size_t length = N - 1;
|
||||
if (scan_literal_bulk(rest, length, std::integral_constant<bool, bulk_scan> {}))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i]))
|
||||
return return_type;
|
||||
}
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != static_cast<char_type>(rest[i])))
|
||||
{
|
||||
error_message = "invalid literal";
|
||||
return token_type::parse_error;
|
||||
@@ -1675,6 +1880,27 @@ scan_number_done:
|
||||
return return_type;
|
||||
}
|
||||
|
||||
/// contiguous input: compare the rest of a literal at once; false (and
|
||||
/// nothing consumed) if it does not match, so that the byte path reports
|
||||
/// the error at the right character
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
||||
{
|
||||
if (next_unget || ia.bulk_remaining() < length
|
||||
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bulk_consume(reinterpret_cast<const char*>(ia.bulk_data()), length);
|
||||
current = static_cast<char_int_type>(static_cast<unsigned char>(rest[length - 1]));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
bool scan_literal_bulk(const char* /*rest*/, const std::size_t /*length*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// input management
|
||||
/////////////////////
|
||||
@@ -1683,6 +1909,7 @@ scan_number_done:
|
||||
void reset() noexcept
|
||||
{
|
||||
token_buffer.clear();
|
||||
pending_number_text = nullptr;
|
||||
decimal_point_position = std::string::npos;
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
@@ -1696,11 +1923,30 @@ scan_number_done:
|
||||
/// seekable adapter: remember where the current token starts so it can be
|
||||
/// reconstructed from the input on error; current has already been
|
||||
/// consumed, hence the -1
|
||||
void note_token_start(std::true_type /*lazy*/) noexcept
|
||||
void note_token_start(std::true_type /*lazy*/)
|
||||
{
|
||||
note_token_start_lazy(std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
|
||||
void note_token_start_lazy(std::false_type /*adapter_token*/) noexcept
|
||||
{
|
||||
token_string_start = ia.get_consumed_count() - 1;
|
||||
}
|
||||
|
||||
/// token-tracking adapter: start tracking the token at the first character
|
||||
void begin_token(std::true_type /*adapter_token*/) noexcept
|
||||
{
|
||||
ia.begin_token();
|
||||
}
|
||||
|
||||
void begin_token(std::false_type /*adapter_token*/) const noexcept {}
|
||||
|
||||
/// token-tracking adapter: the token starts with the current character
|
||||
void note_token_start_lazy(std::true_type /*adapter_token*/)
|
||||
{
|
||||
ia.mark_token_start(current);
|
||||
}
|
||||
|
||||
/// streaming adapter: start copying the token eagerly, beginning with the
|
||||
/// already-read first character
|
||||
void note_token_start(std::false_type /*lazy*/) noexcept
|
||||
@@ -1923,10 +2169,30 @@ scan_number_done:
|
||||
/// return current string value
|
||||
string_t& get_string()
|
||||
{
|
||||
if (pending_number_text != nullptr)
|
||||
{
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
|
||||
pending_number_text = nullptr;
|
||||
}
|
||||
// a number token holds '.' regardless of the locale (#4084)
|
||||
return token_buffer;
|
||||
}
|
||||
|
||||
/// the value of a string token (key or string), which never has pending
|
||||
/// number text, so get_string()'s check is not needed
|
||||
string_t& get_string_token() noexcept
|
||||
{
|
||||
JSON_ASSERT(pending_number_text == nullptr);
|
||||
return token_buffer;
|
||||
}
|
||||
|
||||
/// the text of a float token for a SAX handler that ignores it (the
|
||||
/// library's own handlers): empty, without copying the token
|
||||
const string_t& get_ignored_number_text() const noexcept
|
||||
{
|
||||
return token_buffer;
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// diagnostics
|
||||
/////////////////////
|
||||
@@ -2021,6 +2287,20 @@ scan_number_done:
|
||||
public:
|
||||
/// seekable adapter: rebuild the last read token from the input on demand
|
||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||
{
|
||||
return collect_token_chars_lazy(out, std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
|
||||
/// token-tracking adapter: the adapter hands out everything read since
|
||||
/// mark_token_start(), except a pending unget (see below)
|
||||
const std::vector<char_type>& collect_token_chars_lazy(std::vector<char_type>& out, std::true_type /*adapter_token*/) const
|
||||
{
|
||||
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
|
||||
ia.copy_token(out, pending_real_unget);
|
||||
return out;
|
||||
}
|
||||
|
||||
const std::vector<char_type>& collect_token_chars_lazy(std::vector<char_type>& out, std::false_type /*adapter_token*/) const
|
||||
{
|
||||
// a pending unget of a real (non-EOF) character means that character
|
||||
// was consumed from the input but is not part of the token; EOF is
|
||||
@@ -2142,9 +2422,8 @@ scan_number_done:
|
||||
Pretty-printed JSON indents every line with a run of spaces, which the loop
|
||||
in skip_whitespace() would otherwise read one character at a time. The
|
||||
spaces are skipped 8 bytes at a time; the last 7 bytes of the input and any
|
||||
other whitespace are left to that loop. Skipping bypasses get(), which is
|
||||
fine because bulk_scan implies lazy_position: the position is derived from
|
||||
the adapter, not counted per character.
|
||||
other whitespace are left to that loop. Skipping bypasses get();
|
||||
bulk_consume() does its bookkeeping.
|
||||
*/
|
||||
void skip_indentation(std::true_type /*bulk*/)
|
||||
{
|
||||
@@ -2167,7 +2446,7 @@ scan_number_done:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ia.bulk_skip(pos);
|
||||
bulk_consume(data, pos);
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
@@ -2247,21 +2526,14 @@ scan_number_done:
|
||||
return token_type::value_separator;
|
||||
|
||||
// literals
|
||||
// (the text after the first character; no local arrays, which
|
||||
// would give this function a stack canary on every token)
|
||||
case 't':
|
||||
{
|
||||
std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};
|
||||
return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true);
|
||||
}
|
||||
return scan_literal("rue", token_type::literal_true);
|
||||
case 'f':
|
||||
{
|
||||
std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};
|
||||
return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false);
|
||||
}
|
||||
return scan_literal("alse", token_type::literal_false);
|
||||
case 'n':
|
||||
{
|
||||
std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};
|
||||
return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null);
|
||||
}
|
||||
return scan_literal("ull", token_type::literal_null);
|
||||
|
||||
// string
|
||||
case '\"':
|
||||
@@ -2309,6 +2581,11 @@ scan_number_done:
|
||||
/// whether the next get() call should just return current
|
||||
bool next_unget = false;
|
||||
|
||||
/// the text of the current float token in the input, not yet copied into
|
||||
/// token_buffer (see get_string()), or nullptr
|
||||
const char* pending_number_text = nullptr;
|
||||
std::size_t pending_number_length = 0;
|
||||
|
||||
/// the start position of the current token
|
||||
position_t position {};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint64_t
|
||||
#include <cstdlib> // strtof, strtod, strtold
|
||||
#include <cstring> // memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
||||
@@ -275,13 +274,77 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
|
||||
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
||||
}
|
||||
|
||||
/// whether [first, last) contains a digit other than '0'
|
||||
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
|
||||
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
|
||||
/// digits (after fast_float)
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
|
||||
{
|
||||
return std::any_of(first, last, [](char c) noexcept
|
||||
return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip the ASCII digits starting at data[i] and accumulate their value
|
||||
|
||||
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
|
||||
around after 19 digits; the caller only uses it for shorter digit runs.
|
||||
|
||||
@return the index of the first byte at or after @a i that is not a digit
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
|
||||
{
|
||||
while (avail - i >= 8)
|
||||
{
|
||||
return c != '0';
|
||||
});
|
||||
const std::uint64_t v = read_eight_bytes(data + i);
|
||||
if (!is_eight_digits(v))
|
||||
{
|
||||
break;
|
||||
}
|
||||
w = (w * 100000000u) + parse_eight_digits(v);
|
||||
i += 8;
|
||||
}
|
||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||
{
|
||||
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
|
||||
++i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/// whether [first, last) contains a digit other than '0'; a plain loop, as
|
||||
/// GCC calls std::any_of out of line even for the usual empty range
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
{
|
||||
if (*first != '0')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief accumulate the validated digits [p, last) into @a w, as long as
|
||||
@a remaining digits still fit into it
|
||||
|
||||
Eight digits are taken at a time while at least eight remain, and @a remaining
|
||||
is decreased by the number of digits taken.
|
||||
|
||||
@return a pointer past the last digit taken
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE const char* accumulate_significant_digits(const char* p, const char* last, int& remaining, std::uint64_t& w) noexcept
|
||||
{
|
||||
while (remaining >= 8 && last - p >= 8)
|
||||
{
|
||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
||||
p += 8;
|
||||
remaining -= 8;
|
||||
}
|
||||
for (; remaining > 0 && p != last; ++p, --remaining)
|
||||
{
|
||||
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
||||
@@ -327,7 +390,7 @@ truncated is set.
|
||||
std::string::npos if there is none
|
||||
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
||||
*/
|
||||
inline float_significand parse_float_significand(const char* first, const char* last,
|
||||
JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
|
||||
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||
{
|
||||
float_significand s;
|
||||
@@ -342,16 +405,7 @@ inline float_significand parse_float_significand(const char* first, const char*
|
||||
int remaining = 19; // digits that still fit into w
|
||||
if (*p != '0') // the integer part is "0" or [1-9][0-9]*
|
||||
{
|
||||
while (remaining >= 8 && integer_last - p >= 8)
|
||||
{
|
||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
||||
p += 8;
|
||||
remaining -= 8;
|
||||
}
|
||||
for (; remaining > 0 && p != integer_last; ++p, --remaining)
|
||||
{
|
||||
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
||||
}
|
||||
p = accumulate_significant_digits(p, integer_last, remaining, w);
|
||||
s.exponent = integer_last - p;
|
||||
s.truncated = has_nonzero_digit(p, integer_last);
|
||||
}
|
||||
@@ -370,16 +424,7 @@ inline float_significand parse_float_significand(const char* first, const char*
|
||||
s.exponent -= p - zeros;
|
||||
}
|
||||
const char* const digits = p;
|
||||
while (remaining >= 8 && mantissa_last - p >= 8)
|
||||
{
|
||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
||||
p += 8;
|
||||
remaining -= 8;
|
||||
}
|
||||
for (; remaining > 0 && p != mantissa_last; ++p, --remaining)
|
||||
{
|
||||
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
||||
}
|
||||
p = accumulate_significant_digits(p, mantissa_last, remaining, w);
|
||||
s.exponent -= p - digits;
|
||||
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
|
||||
}
|
||||
@@ -712,15 +757,14 @@ std::uint64_t digit_comparison(const char* first, const char* last, std::uint64_
|
||||
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
|
||||
{
|
||||
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
||||
std::memcpy(&value, &bits, sizeof(value));
|
||||
value = bit_cast<double>(bits);
|
||||
}
|
||||
|
||||
/// the float with the IEEE-754 bits @a bits (the lower 32)
|
||||
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
|
||||
{
|
||||
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||
const auto bits32 = static_cast<std::uint32_t>(bits);
|
||||
std::memcpy(&value, &bits32, sizeof(bits32));
|
||||
value = bit_cast<float>(static_cast<std::uint32_t>(bits));
|
||||
}
|
||||
|
||||
/// the powers of ten that are exact in binary64 (up to 10^22)
|
||||
|
||||
@@ -51,6 +51,20 @@ template<typename BasicJsonType>
|
||||
using parser_callback_t =
|
||||
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
||||
|
||||
/// whether a SAX handler ignores the text of float tokens (the library's own
|
||||
/// handlers do), so that the parser need not materialize it
|
||||
template<typename SAX>
|
||||
struct sax_ignores_number_text : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||
|
||||
template<typename BasicJsonType>
|
||||
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
|
||||
|
||||
/*!
|
||||
@brief syntax analysis
|
||||
|
||||
@@ -171,6 +185,23 @@ class parser
|
||||
}
|
||||
|
||||
private:
|
||||
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||
template<typename SAX>
|
||||
const string_t& number_float_text(SAX* /*sax*/)
|
||||
{
|
||||
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
|
||||
}
|
||||
|
||||
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
|
||||
{
|
||||
return m_lexer.get_ignored_number_text();
|
||||
}
|
||||
|
||||
string_t& number_float_text(std::false_type /*ignored*/)
|
||||
{
|
||||
return m_lexer.get_string();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief run a DOM SAX parser to completion and position the lexer
|
||||
|
||||
@@ -213,8 +244,9 @@ class parser
|
||||
bool sax_parse_internal(SAX* sax)
|
||||
{
|
||||
// stack to remember the hierarchy of structured values we are parsing
|
||||
// true = array; false = object
|
||||
std::vector<bool> states;
|
||||
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||
// push_back() is a call with GCC)
|
||||
std::vector<std::uint8_t> states;
|
||||
// value to avoid a goto (see comment where set to true)
|
||||
bool skip_to_state_evaluation = false;
|
||||
|
||||
@@ -247,7 +279,7 @@ class parser
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -259,7 +291,7 @@ class parser
|
||||
}
|
||||
|
||||
// remember we are now inside an object
|
||||
states.push_back(false);
|
||||
states.push_back(0);
|
||||
|
||||
// parse values
|
||||
get_token();
|
||||
@@ -284,7 +316,7 @@ class parser
|
||||
}
|
||||
|
||||
// remember we are now inside an array
|
||||
states.push_back(true);
|
||||
states.push_back(1);
|
||||
|
||||
// parse values (no need to call get_token)
|
||||
continue;
|
||||
@@ -301,7 +333,7 @@ class parser
|
||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -347,7 +379,7 @@ class parser
|
||||
|
||||
case token_type::value_string:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -401,7 +433,7 @@ class parser
|
||||
return true;
|
||||
}
|
||||
|
||||
if (states.back()) // array
|
||||
if (states.back() != 0) // array
|
||||
{
|
||||
// comma -> next value
|
||||
// or end of array (ignore_trailing_commas = true)
|
||||
@@ -456,7 +488,7 @@ class parser
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 <atomic> // atomic
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint64_t
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
// Vector code for runs of string bytes, used by the lexer for contiguous input
|
||||
// (see string_bulk_run()). 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. SSSE3 is not part of x86-64, and the code 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): the check is compiled for SSSE3 with a function attribute and
|
||||
// used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the
|
||||
// portable check. Code compiled for SSSE3 anyway (-mssse3 or more) skips the
|
||||
// CPU check. JSON_NO_SIMD selects the portable code everywhere.
|
||||
// NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target
|
||||
// attribute and then reject the SSSE3 intrinsics) keeps the check portable
|
||||
// without disabling the SSE2 code.
|
||||
#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
#include <arm_neon.h>
|
||||
#define JSON_SCAN_NEON 1
|
||||
#else
|
||||
#define JSON_SCAN_NEON 0
|
||||
#endif
|
||||
// (x86 only: other targets can define __SSE2__ as well, e.g., WebAssembly with -msse2, but have no <cpuid.h>)
|
||||
#if !defined(JSON_NO_SIMD) && !JSON_SCAN_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && !defined(_M_ARM64EC) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
|
||||
#include <emmintrin.h>
|
||||
#define JSON_SCAN_SSE2 1
|
||||
#else
|
||||
#define JSON_SCAN_SSE2 0
|
||||
#endif
|
||||
#if JSON_SCAN_SSE2 && defined(__SSSE3__)
|
||||
#include <tmmintrin.h>
|
||||
#define JSON_SCAN_SSSE3 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#else
|
||||
#define JSON_SCAN_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#endif
|
||||
#if JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && !defined(NLOHMANN_JSON_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && !defined(__cpp_modules)))
|
||||
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3; GCC with C++20
|
||||
// modules drops the target attribute from functions it reads from a module
|
||||
// interface and then rejects the intrinsics: "needs isa option -mssse3")
|
||||
// <cpuid.h> of GCC before 11 has no include guard, and json.hpp and
|
||||
// json_view.hpp both include it
|
||||
#ifndef NLOHMANN_JSON_CPUID_INCLUDED
|
||||
#define NLOHMANN_JSON_CPUID_INCLUDED
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
#include <tmmintrin.h>
|
||||
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#define JSON_SCAN_SSSE3_TARGET __attribute__((target("ssse3")))
|
||||
#elif JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && defined(_MSC_VER)
|
||||
// (MSVC compiles intrinsics of any instruction set)
|
||||
#include <intrin.h>
|
||||
#include <tmmintrin.h>
|
||||
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#define JSON_SCAN_SSSE3_TARGET
|
||||
#else
|
||||
#define JSON_SCAN_SSSE3_DISPATCH 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#define JSON_SCAN_SSSE3_TARGET
|
||||
#endif
|
||||
#define JSON_SCAN_VECTOR (JSON_SCAN_NEON || JSON_SCAN_SSE2)
|
||||
#define JSON_SCAN_VECTOR_UTF8 (JSON_SCAN_NEON || JSON_SCAN_SSSE3 || JSON_SCAN_SSSE3_DISPATCH)
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
#if JSON_SCAN_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.
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
while (e - p >= 16)
|
||||
{
|
||||
#if JSON_SCAN_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))); // NOLINT(bugprone-casting-through-void)
|
||||
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 JSON_SCAN_VECTOR
|
||||
/// vector_plain_run() for runs that are already longer than a few words:
|
||||
/// inline with SSE2, out of line with NEON, where a larger string loop slows
|
||||
/// down the rest of the lexer
|
||||
#if JSON_SCAN_NEON
|
||||
JSON_HEDLEY_NEVER_INLINE inline
|
||||
#else
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
#endif
|
||||
const unsigned char* vector_plain_run_long(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
return vector_plain_run(p, e);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_SCAN_SSSE3_DISPATCH
|
||||
/// whether the CPU has SSSE3 (CPUID leaf 1, ECX bit 9)
|
||||
inline bool cpu_ssse3() noexcept
|
||||
{
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
std::array<int, 4> regs {{}};
|
||||
__cpuid(regs.data(), 1);
|
||||
return (static_cast<unsigned>(regs[2]) & (1u << 9u)) != 0;
|
||||
#else
|
||||
unsigned eax = 0;
|
||||
unsigned ebx = 0;
|
||||
unsigned ecx = 0;
|
||||
unsigned edx = 0;
|
||||
return __get_cpuid(1, &eax, &ebx, &ecx, &edx) != 0 && (ecx & (1u << 9u)) != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// whether the CPU has SSSE3, asked once: the answer is kept in an atomic
|
||||
/// that is initialized at compile time, so that neither a guard of a local
|
||||
/// static nor a global constructor is needed (threads that ask at the same
|
||||
/// time all store the same answer)
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool cpu_has_ssse3() noexcept
|
||||
{
|
||||
static std::atomic<int> known{0}; // 0: not asked yet, 1: no, 2: yes
|
||||
int state = known.load(std::memory_order_relaxed);
|
||||
if (JSON_HEDLEY_UNLIKELY(state == 0))
|
||||
{
|
||||
state = cpu_ssse3() ? 2 : 1;
|
||||
known.store(state, std::memory_order_relaxed);
|
||||
}
|
||||
return state == 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_SCAN_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
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief the run of plain string bytes and well-formed UTF-8 at 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 of J. Keiser and D. Lemire covers the bytes up
|
||||
to it. Returns true with @a block at that byte; returns false with @a block at
|
||||
the start of the 16 bytes where the check found ill-formed UTF-8 or where
|
||||
fewer than 16 bytes are left, for the caller to finish one sequence at a time.
|
||||
Out of line, so that no constants of the check occupy registers in the
|
||||
caller's loop. On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()).
|
||||
*/
|
||||
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(const unsigned char* p, const unsigned char* e, const unsigned char*& block_out) noexcept
|
||||
{
|
||||
using lookup = utf8_lookup4<>;
|
||||
const unsigned char* block = p;
|
||||
#if JSON_SCAN_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)
|
||||
{
|
||||
block_out = block + k;
|
||||
return true;
|
||||
}
|
||||
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()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i t2h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
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))); // NOLINT(bugprone-casting-through-void)
|
||||
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 auto k = static_cast<unsigned>(count_trailing_zeros(static_cast<std::uint64_t>(special_bits)));
|
||||
if ((err_bits & ((2u << k) - 1u)) == 0)
|
||||
{
|
||||
block_out = block + k;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (err_bits != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
#endif
|
||||
block_out = block;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <cstring> // memcpy
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/simd_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
||||
@@ -34,7 +35,7 @@
|
||||
// This file contains the byte-level string-scanning helpers used by the lexer's
|
||||
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
||||
// lexer's template parameters) so they are free functions, keeping the lexer
|
||||
// itself focused on the state machine; see lexer::scan_string_bulk().
|
||||
// itself focused on the state machine; see lexer::get_after_string_run().
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -66,7 +67,7 @@ inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
||||
|
||||
// return the index of the first is_string_special() byte in [data, data+n), or
|
||||
// n if every byte is ordinary; scans 8 bytes at a time
|
||||
inline std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
@@ -235,35 +236,143 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Scalar (C++11) computation of the bulk run length: the number of leading
|
||||
// bytes in [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
||||
#if JSON_SCAN_VECTOR_UTF8
|
||||
/// 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) 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 (is_string_special(*p))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/// the rest of a string from p (a character boundary), with the vector UTF-8
|
||||
/// check and the scalar check for the end of the block where it stopped
|
||||
inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
const unsigned char* block = p;
|
||||
if (vector_utf8_run(p, e, block))
|
||||
{
|
||||
return block;
|
||||
}
|
||||
return scan_string_finish(p, block, e);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_SCAN_VECTOR
|
||||
/// the end of the run of plain string characters at @a p: the first quote,
|
||||
/// backslash, control character, or non-ASCII byte, or @a e. The first words
|
||||
/// are checked with SWAR (one; four with NEON, where the vector mask costs
|
||||
/// more), as most keys and short values end there; longer runs continue 16
|
||||
/// bytes at a time.
|
||||
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* plain_run_end(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
constexpr int prefix_words = JSON_SCAN_NEON ? 4 : 1;
|
||||
for (int words = prefix_words; words > 0 && e - p >= 8; --words)
|
||||
{
|
||||
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
||||
if (special != 0)
|
||||
{
|
||||
return p + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
||||
}
|
||||
p += 8;
|
||||
}
|
||||
// the vector loop only for 16 bytes or more: the serializer passes a
|
||||
// string without its closing quote, so most of its runs end at e, and a
|
||||
// call of the out-of-line NEON loop for a shorter rest made dump() up to
|
||||
// 10% slower
|
||||
if (e - p >= 16)
|
||||
{
|
||||
p = vector_plain_run_long(p, e);
|
||||
if (e - p >= 16)
|
||||
{
|
||||
return p; // the vector loop stopped at a special byte
|
||||
}
|
||||
}
|
||||
return p + find_string_special(p, static_cast<std::size_t>(e - p));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Computation of the bulk run length: the number of leading bytes in
|
||||
// [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
||||
// sequences, stopping before the first byte that needs individual handling (the
|
||||
// closing quote, an escape, a control character, or an ill-formed/truncated
|
||||
// sequence). ASCII is skipped 8 bytes at a time.
|
||||
inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||
// sequence). ASCII is skipped 16 bytes at a time with SSE2 and 8 bytes at a
|
||||
// time otherwise; runs of non-ASCII text use the vector UTF-8 check where
|
||||
// available.
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
std::size_t pos = 0;
|
||||
while (pos < n)
|
||||
const unsigned char* const e = data + n;
|
||||
const unsigned char* p = data;
|
||||
while (p != e)
|
||||
{
|
||||
pos += find_string_special(data + pos, n - pos);
|
||||
if (pos >= n || data[pos] < 0x80u)
|
||||
#if JSON_SCAN_VECTOR
|
||||
p = plain_run_end(p, e);
|
||||
#else
|
||||
p += find_string_special(p, static_cast<std::size_t>(e - p));
|
||||
#endif
|
||||
if (p == e || *p < 0x80u)
|
||||
{
|
||||
break; // end of buffer, or a quote/escape/control byte
|
||||
}
|
||||
#if JSON_SCAN_VECTOR_UTF8
|
||||
#if JSON_SCAN_SSSE3_DISPATCH
|
||||
if (JSON_HEDLEY_LIKELY(cpu_has_ssse3()))
|
||||
#endif
|
||||
{
|
||||
return static_cast<std::size_t>(scan_string_vector(p, e) - data);
|
||||
}
|
||||
#endif
|
||||
#if !JSON_SCAN_VECTOR_UTF8 || JSON_SCAN_SSSE3_DISPATCH
|
||||
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
||||
// by sequence without searching for the next special byte in between
|
||||
do
|
||||
{
|
||||
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
||||
const std::size_t seq = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
||||
if (seq == 0)
|
||||
{
|
||||
return pos; // ill-formed or truncated: let the byte path diagnose it
|
||||
return static_cast<std::size_t>(p - data); // ill-formed or truncated: let the byte path diagnose it
|
||||
}
|
||||
pos += seq;
|
||||
p += seq;
|
||||
}
|
||||
while (pos < n && data[pos] >= 0x80u);
|
||||
while (p != e && *p >= 0x80u);
|
||||
#endif
|
||||
}
|
||||
return pos;
|
||||
return static_cast<std::size_t>(p - data);
|
||||
}
|
||||
|
||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||
@@ -304,7 +413,7 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
|
||||
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
||||
// scalar helper recomputes the exact valid prefix so the byte path still
|
||||
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
||||
inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||
const std::size_t run = find_string_delimiter(data, n);
|
||||
@@ -316,6 +425,42 @@ inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noe
|
||||
return scalar_string_bulk_run(data, n);
|
||||
}
|
||||
|
||||
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
||||
/// scalar_string_bulk_run() from @a start on, out of line (see
|
||||
/// lexer_string_bulk_run())
|
||||
JSON_HEDLEY_NEVER_INLINE inline std::size_t string_bulk_run_from(const unsigned char* data, std::size_t start, std::size_t n) noexcept
|
||||
{
|
||||
return start + scalar_string_bulk_run(data + start, n - start);
|
||||
}
|
||||
#endif
|
||||
|
||||
// string_bulk_run() for the lexer. With NEON, only the first four words are
|
||||
// checked inline (most keys and short values end there), and longer runs and
|
||||
// non-ASCII text continue out of line. The lexer's scan function runs once per
|
||||
// token, and the vector code inlined into it needs more registers, which every
|
||||
// token pays for: 5% of json::accept() on jeopardy.json with Apple Clang on
|
||||
// Apple M1, even though no string of that file reaches the vector code. The
|
||||
// serializer keeps string_bulk_run() inline, which is faster there.
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t lexer_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
||||
std::size_t i = 0;
|
||||
for (int words = 4; words > 0 && n - i >= 8; --words)
|
||||
{
|
||||
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
||||
if (special != 0)
|
||||
{
|
||||
const std::size_t pos = i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
||||
return data[pos] < 0x80u ? pos : string_bulk_run_from(data, pos, n);
|
||||
}
|
||||
i += 8;
|
||||
}
|
||||
return string_bulk_run_from(data, i, n);
|
||||
#else
|
||||
return string_bulk_run(data, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
||||
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
||||
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -187,6 +187,22 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// the byte order of the target where the compiler tells it (std::endian is
|
||||
// C++20 and cannot select code in #if): JSON_LITTLE_ENDIAN_TARGET or
|
||||
// JSON_BIG_ENDIAN_TARGET is 1, or both are 0 where it is unknown (code then
|
||||
// handles the bytes one at a time); MSVC and x86 targets are little-endian
|
||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) \
|
||||
|| (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
#define JSON_LITTLE_ENDIAN_TARGET 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#define JSON_BIG_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
#define JSON_LITTLE_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#define JSON_BIG_ENDIAN_TARGET 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#else
|
||||
#define JSON_LITTLE_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#define JSON_BIG_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#define JSON_INLINE_VARIABLE inline
|
||||
#else
|
||||
|
||||
@@ -21,8 +21,18 @@
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_UNLOCKED_FILE_IO
|
||||
#undef JSON_DTOA_SSE2
|
||||
#undef JSON_DTOA_NEON
|
||||
#undef JSON_LITTLE_ENDIAN_TARGET
|
||||
#undef JSON_BIG_ENDIAN_TARGET
|
||||
#undef JSON_SCAN_NEON
|
||||
#undef JSON_SCAN_SSE2
|
||||
#undef JSON_SCAN_SSSE3
|
||||
#undef JSON_SCAN_SSSE3_DISPATCH
|
||||
#undef JSON_SCAN_SSSE3_TARGET
|
||||
#undef JSON_SCAN_VECTOR
|
||||
#undef JSON_SCAN_VECTOR_UTF8
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
@@ -47,6 +57,8 @@
|
||||
#undef JSON_STRICT_BINARY_UTF8
|
||||
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#undef JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#undef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#endif
|
||||
|
||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||
@@ -10,17 +10,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
||||
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
||||
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <algorithm> // clamp
|
||||
#endif
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||
#ifdef __cpp_lib_remove_cvref
|
||||
template<typename T>
|
||||
using uncvref_t = std::remove_cvref_t<T>;
|
||||
#else
|
||||
template<typename T>
|
||||
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_14
|
||||
|
||||
@@ -160,5 +169,19 @@ struct static_const
|
||||
constexpr T static_const<T>::value;
|
||||
#endif
|
||||
|
||||
/// @a v bounded to [@a lo, @a hi] (requires !(hi < lo)); like std::clamp,
|
||||
/// but by value, so that static constexpr members can be passed without an
|
||||
/// out-of-line definition before C++17
|
||||
template<typename T, enable_if_t<std::is_arithmetic<T>::value, int> = 0>
|
||||
constexpr T clamp(T v, T lo, T hi) noexcept
|
||||
{
|
||||
#ifdef __cpp_lib_clamp
|
||||
return std::clamp(v, lo, hi);
|
||||
#else
|
||||
// NOLINTNEXTLINE(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
||||
return v < lo ? lo : (hi < v ? hi : v);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -313,6 +313,14 @@ struct char_traits<std::byte> : std::char_traits<char>
|
||||
// is_ functions //
|
||||
///////////////////
|
||||
|
||||
#ifdef __cpp_lib_logical_traits
|
||||
|
||||
using std::conjunction;
|
||||
using std::disjunction;
|
||||
using std::negation;
|
||||
|
||||
#else
|
||||
|
||||
// https://en.cppreference.com/w/cpp/types/conjunction
|
||||
template<class...> struct conjunction : std::true_type { };
|
||||
template<class B> struct conjunction<B> : B { };
|
||||
@@ -330,6 +338,8 @@ struct disjunction<B, Bn...>
|
||||
// https://en.cppreference.com/w/cpp/types/negation
|
||||
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
|
||||
|
||||
#endif
|
||||
|
||||
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
|
||||
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
|
||||
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
|
||||
@@ -1060,8 +1070,12 @@ constexpr bool value_in_range_of(T val)
|
||||
return value_in_range_of_impl1<OfType, T>::test(val);
|
||||
}
|
||||
|
||||
template<bool Value>
|
||||
using bool_constant = std::integral_constant<bool, Value>;
|
||||
#ifdef __cpp_lib_bool_constant
|
||||
using std::bool_constant;
|
||||
#else
|
||||
template<bool Value>
|
||||
using bool_constant = std::integral_constant<bool, Value>;
|
||||
#endif
|
||||
|
||||
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
||||
// number_integer_t, so that the unsigned overloads do not collide with the
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,7 +8,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // conditional, integral_constant, is_same
|
||||
@@ -48,32 +51,258 @@ template<typename CharType> struct output_adapter_protocol
|
||||
template<typename CharType>
|
||||
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
|
||||
/// the largest number of characters a writer may reserve() from a sink at once;
|
||||
/// output_vector_sink also appends writes of at least this size with insert()
|
||||
constexpr std::size_t max_reserve_size = 256;
|
||||
|
||||
/// @brief copy @a length characters, at most 32, without a call of memcpy()
|
||||
///
|
||||
/// Short strings are the typical payload of a binary value, and a call of
|
||||
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
|
||||
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
|
||||
/// so on down to a single character; each is a fixed-size load and store.
|
||||
/// Nothing outside of [src, src + length) is read or written.
|
||||
template<typename CharType>
|
||||
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
|
||||
{
|
||||
JSON_ASSERT(length <= 32);
|
||||
if (length >= 16)
|
||||
{
|
||||
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 16);
|
||||
std::memcpy(last.data(), src + (length - 16), 16);
|
||||
std::memcpy(dst, first.data(), 16);
|
||||
std::memcpy(dst + (length - 16), last.data(), 16);
|
||||
}
|
||||
else if (length >= 8)
|
||||
{
|
||||
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 8);
|
||||
std::memcpy(last.data(), src + (length - 8), 8);
|
||||
std::memcpy(dst, first.data(), 8);
|
||||
std::memcpy(dst + (length - 8), last.data(), 8);
|
||||
}
|
||||
else if (length >= 4)
|
||||
{
|
||||
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 4);
|
||||
std::memcpy(last.data(), src + (length - 4), 4);
|
||||
std::memcpy(dst, first.data(), 4);
|
||||
std::memcpy(dst + (length - 4), last.data(), 4);
|
||||
}
|
||||
else if (length != 0)
|
||||
{
|
||||
// 1 to 3: the first, the middle, and the last character
|
||||
const CharType a = src[0];
|
||||
const CharType b = src[length >> 1];
|
||||
const CharType c = src[length - 1];
|
||||
dst[0] = a;
|
||||
dst[length >> 1] = b;
|
||||
dst[length - 1] = c;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief copy @a length characters, of a character type wider than a byte
|
||||
template<typename CharType>
|
||||
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
|
||||
{
|
||||
std::memcpy(dst, src, length * sizeof(CharType));
|
||||
}
|
||||
|
||||
/// the number of characters an empty vector is grown to by output_vector_sink
|
||||
constexpr std::size_t min_vector_sink_capacity = 64;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
/// passed to binary_writer by value as a template parameter, so
|
||||
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
||||
/// vtable lookup and no shared_ptr. It is used for the common
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below
|
||||
/// wraps this same sink to provide the virtual interface.
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector.
|
||||
///
|
||||
/// Most writes are a few bytes - a type marker, or a number of 1, 2, 4, or 8
|
||||
/// bytes - and appending those with push_back() or insert() cost more than
|
||||
/// encoding them: insert() is an out-of-line call into a routine that can insert
|
||||
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
|
||||
/// its own write position: it grows the vector geometrically ahead of the
|
||||
/// position and copies each write to it, so a write is a capacity check and a
|
||||
/// short copy. The destructor shrinks the vector to the bytes actually written,
|
||||
/// also when a write throws; shrinking a vector never allocates. The sink owns
|
||||
/// that trim, so it can be moved but not copied, and the vector must not be used
|
||||
/// while the sink is alive.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor)
|
||||
{}
|
||||
|
||||
output_vector_sink(const output_vector_sink&) = delete;
|
||||
output_vector_sink& operator=(const output_vector_sink&) = delete;
|
||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||
|
||||
output_vector_sink(output_vector_sink&& other) noexcept
|
||||
: v(other.v), cursor(other.cursor), limit(other.limit)
|
||||
{
|
||||
other.v = nullptr;
|
||||
}
|
||||
|
||||
~output_vector_sink()
|
||||
{
|
||||
if (v != nullptr)
|
||||
{
|
||||
v->resize(position());
|
||||
}
|
||||
}
|
||||
|
||||
// always inlined: GCC otherwise keeps these out of line in a large
|
||||
// translation unit, and a call per byte or number costs more than the write
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
*cursor++ = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values; memcpy must not
|
||||
// see it.
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||
{
|
||||
if (length >= max_reserve_size)
|
||||
{
|
||||
append(s, length);
|
||||
return;
|
||||
}
|
||||
grow(length);
|
||||
}
|
||||
if (length <= 32)
|
||||
{
|
||||
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||
cursor += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(cursor, s, length * sizeof(CharType));
|
||||
cursor += length;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief space for writing up to @a length characters directly
|
||||
///
|
||||
/// Returns a pointer to at least @a length (at most @ref max_reserve_size)
|
||||
/// writable characters at the write position; @ref commit then keeps the
|
||||
/// first ones of them. A writer can store whole words there and keep only
|
||||
/// the bytes it needs, and keep its write pointer in a register across many
|
||||
/// values instead of updating the sink after each.
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||
{
|
||||
grow(length);
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
cursor += length;
|
||||
}
|
||||
|
||||
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
|
||||
void flush() noexcept
|
||||
{}
|
||||
|
||||
private:
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(cursor - v->data());
|
||||
}
|
||||
|
||||
/// number of characters that fit the vector behind the write position
|
||||
std::size_t space() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(limit - cursor);
|
||||
}
|
||||
|
||||
/// make room for at least @a length more characters after the position;
|
||||
/// kept out of line so that the write functions stay small enough for
|
||||
/// GCC to inline them into the writer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void grow(const std::size_t length)
|
||||
{
|
||||
// use the capacity the vector already has, else at least double it;
|
||||
// never less than min_vector_sink_capacity, so that a small value does not
|
||||
// reallocate for every few bytes
|
||||
const std::size_t written = position();
|
||||
const std::size_t needed = written + length;
|
||||
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2)));
|
||||
cursor = v->data() + written;
|
||||
limit = v->data() + v->size();
|
||||
}
|
||||
|
||||
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||
/// the space ahead of the position: insert() copies them once into the
|
||||
/// grown vector, where grow() would first zero the new space and then copy
|
||||
/// over it - noticeable for a large string or binary value
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void append(const CharType* s, const std::size_t length)
|
||||
{
|
||||
const std::size_t written = position();
|
||||
v->resize(written);
|
||||
if (v->capacity() - written < length)
|
||||
{
|
||||
// grow geometrically, with room for the writes that follow, so
|
||||
// that the next grow() need not reallocate right away
|
||||
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
|
||||
}
|
||||
v->insert(v->end(), s, s + length);
|
||||
cursor = v->data() + v->size();
|
||||
limit = cursor;
|
||||
}
|
||||
|
||||
std::vector<CharType, AllocatorType>* v;
|
||||
/// the write position: in *v, at the number of characters written so far
|
||||
CharType* cursor;
|
||||
/// the end of *v
|
||||
CharType* limit;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c)
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
v.push_back(c);
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values. Appending an empty
|
||||
// range is a no-op; the type-erased path tolerates this via the (unattributed)
|
||||
// virtual base, and the concrete sink must do the same.
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
// pointer with length 0 for empty strings/binary values; appending an empty
|
||||
// range is a no-op
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
v.insert(v.end(), s, s + length);
|
||||
}
|
||||
@@ -82,33 +311,6 @@ class output_vector_sink
|
||||
std::vector<CharType, AllocatorType>& v;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
///
|
||||
/// The appending itself lives in output_vector_sink; this class only adds the
|
||||
/// virtual output_adapter_protocol interface on top of it, so both the
|
||||
/// type-erased and the templated path share one implementation.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: sink(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
sink.write_character(c);
|
||||
}
|
||||
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
sink.write_characters(s, length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_vector_sink<CharType, AllocatorType> sink;
|
||||
};
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// output adapter for output streams
|
||||
template<typename CharType>
|
||||
@@ -163,9 +365,17 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
///
|
||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
||||
/// avoid it.
|
||||
/// adapters) via the `output_adapter`-based overloads.
|
||||
///
|
||||
/// A write through the adapter is a virtual call, and for a stream a call with
|
||||
/// a sentry on top, which costs more than encoding the few bytes of most values.
|
||||
/// The sink therefore collects what is written in a buffer and passes it on in
|
||||
/// one piece when it is full, a write is too large for it, or @ref flush is
|
||||
/// called. Everything written reaches the adapter in the same order and with
|
||||
/// the same bytes; the destructor passes on what is left, so that a write that
|
||||
/// throws leaves the adapter with exactly the bytes written before it. Only
|
||||
/// @ref flush can report an error of the adapter (the destructor must not
|
||||
/// throw), so it has to be called once the value is written.
|
||||
template<typename CharType>
|
||||
class output_adapter_sink
|
||||
{
|
||||
@@ -176,20 +386,121 @@ class output_adapter_sink
|
||||
JSON_ASSERT(oa);
|
||||
}
|
||||
|
||||
output_adapter_sink(const output_adapter_sink&) = delete;
|
||||
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
|
||||
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
|
||||
|
||||
output_adapter_sink(output_adapter_sink&& other) noexcept
|
||||
: oa(std::move(other.oa)), used(other.used)
|
||||
{
|
||||
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
|
||||
other.used = 0;
|
||||
}
|
||||
|
||||
~output_adapter_sink()
|
||||
{
|
||||
// a normal return has flushed already, so this is for a write that threw
|
||||
if (used != 0)
|
||||
{
|
||||
JSON_TRY
|
||||
{
|
||||
flush();
|
||||
}
|
||||
JSON_CATCH(...)
|
||||
{
|
||||
// nothing can be done about an adapter that throws here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
oa->write_character(c);
|
||||
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
buffer[used++] = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||
// the type-erased path already did before this sink existed
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||
{
|
||||
write_large(s, length);
|
||||
return;
|
||||
}
|
||||
if (length <= 32)
|
||||
{
|
||||
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||
used += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
|
||||
used += length;
|
||||
}
|
||||
}
|
||||
|
||||
/// space for writing up to @a length characters (see
|
||||
/// output_vector_sink::reserve)
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
return buffer.data() + used;
|
||||
}
|
||||
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
used += length;
|
||||
}
|
||||
|
||||
/// pass everything written so far on to the adapter
|
||||
void flush()
|
||||
{
|
||||
if (used != 0)
|
||||
{
|
||||
// reset first: the characters count as written even if the adapter throws
|
||||
const std::size_t length = used;
|
||||
used = 0;
|
||||
oa->write_characters(buffer.data(), length);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// a write that does not fit the space left in the buffer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_large(const CharType* s, const std::size_t length)
|
||||
{
|
||||
flush();
|
||||
if (length >= buffer_size)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buffer.data(), s, length * sizeof(CharType));
|
||||
used = length;
|
||||
}
|
||||
}
|
||||
|
||||
/// large enough for reserve() at any time
|
||||
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
|
||||
|
||||
output_adapter_t<CharType> oa;
|
||||
/// number of characters of @ref buffer not yet passed on
|
||||
std::size_t used = 0;
|
||||
std::array<CharType, buffer_size> buffer{};
|
||||
};
|
||||
|
||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -14,7 +14,6 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // is_signed
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
@@ -208,13 +207,16 @@ FloatType compare_integer_with_float(const IntegerType i, const FloatType f) noe
|
||||
|
||||
// values of IntegerType lie in [-bound, bound) when signed and in
|
||||
// [0, bound) when unsigned; digits excludes the sign bit, so bound is a
|
||||
// power of two that the float represents exactly
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), std::numeric_limits<IntegerType>::digits);
|
||||
// power of two that the float represents exactly; the signedness comes
|
||||
// from numeric_limits as well, because std::is_signed is false for class
|
||||
// types such as 128-bit or multiprecision integers
|
||||
using limits = std::numeric_limits<IntegerType>;
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), limits::digits);
|
||||
if (f >= bound)
|
||||
{
|
||||
return ordered(-1);
|
||||
}
|
||||
if (std::is_signed<IntegerType>::value ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
if (limits::is_signed ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
{
|
||||
return ordered(1);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
@@ -42,7 +42,9 @@ struct document_data
|
||||
std::size_t inline_capacity = 0;
|
||||
std::size_t max_depth = 0; ///< deepest nesting of arrays and objects (the root container is depth 1)
|
||||
std::vector<char> arena; ///< decoded strings that contained escapes (no terminator: they are handed out with their length)
|
||||
std::size_t arena_size = 0; ///< bytes of decoded strings at base[1] (the arena, or those of a loaded image)
|
||||
std::string owned; ///< owned copy of the input, if any (a std::string, so that its NUL serves as the parser's sentinel)
|
||||
std::vector<std::uint8_t> owned_image; ///< a loaded image the document owns (the text and the decoded strings point into it)
|
||||
|
||||
// hash indexes of the keys of large objects (see object_index.hpp)
|
||||
static constexpr std::uint32_t index_min_members = 128;
|
||||
@@ -54,14 +56,16 @@ struct document_data
|
||||
std::vector<object_index> indexes;
|
||||
std::vector<std::uint32_t> index_slots;
|
||||
std::vector<std::uint32_t> large_objects; ///< positions of the objects to index (noted while parsing)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena, unused (indexed by flags & node_flags::storage)
|
||||
bool discarded = true;
|
||||
|
||||
/// The storage of edits (editable documents only; see edit_storage.hpp).
|
||||
/// Edits never move or resize the parsed index, so views stay valid: an
|
||||
/// array/object whose elements change gets node_flags::moved, and its
|
||||
/// elements then live in a separate sequence (a header node, then the
|
||||
/// entries), whose entries link to the values.
|
||||
/// No edit moves or resizes the parsed index, and the nodes of values
|
||||
/// written by edits never move either, so a view stays valid whenever it
|
||||
/// was taken, before the first edit or between later ones: an array/object
|
||||
/// whose elements change gets node_flags::moved, and its elements then live
|
||||
/// in a separate sequence (a header node, then the entries), whose entries
|
||||
/// link to the values.
|
||||
struct edit_state
|
||||
{
|
||||
std::vector<node*> moved{}; ///< element sequences of moved arrays/objects (header node first) // NOLINT(readability-redundant-member-init)
|
||||
@@ -117,6 +121,7 @@ struct document_data
|
||||
document_data() noexcept
|
||||
: arena() // NOLINT(readability-redundant-member-init)
|
||||
, owned() // NOLINT(readability-redundant-member-init)
|
||||
, owned_image() // NOLINT(readability-redundant-member-init)
|
||||
, indexes() // NOLINT(readability-redundant-member-init)
|
||||
, index_slots() // NOLINT(readability-redundant-member-init)
|
||||
, large_objects() // NOLINT(readability-redundant-member-init)
|
||||
@@ -180,6 +185,12 @@ struct document_data
|
||||
return base[n.flags & node_flags::storage] + n.off;
|
||||
}
|
||||
|
||||
/// whether the string of node k (a key) is the n bytes at key
|
||||
bool key_equals(const node& k, const char* key, std::size_t n) const noexcept
|
||||
{
|
||||
return k.len == n && (n == 0 || std::memcmp(str(k), key, n) == 0);
|
||||
}
|
||||
|
||||
/// the node after n's subtree (containers span `next` nodes, scalars one)
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* after(const node* n) noexcept
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <cmath> // isinf, isnan
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memmove
|
||||
#include <cstring> // memmove
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_convertible, is_floating_point, is_same, is_signed
|
||||
@@ -313,7 +313,7 @@ class editor
|
||||
|
||||
bool key_equals(const node& k, string_view_t key) const noexcept
|
||||
{
|
||||
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0);
|
||||
return m_doc.key_equals(k, key.data(), key.size());
|
||||
}
|
||||
|
||||
/// remove the members with this key (all, or all but the first) from an object
|
||||
|
||||
@@ -50,7 +50,19 @@ inline node* alloc_nodes(document_data& d, std::size_t k)
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(e.chunk_end - e.chunk_cur) < k))
|
||||
{
|
||||
const std::size_t count = (std::max)(k, e.chunk_next);
|
||||
if (k >= e.chunk_next / 2)
|
||||
{
|
||||
// a large request (half of the next chunk or more) gets a chunk of
|
||||
// its own, so that the rest of the current chunk stays available
|
||||
// for smaller ones
|
||||
std::unique_ptr<node[]> own(new node[k]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
e.chunks.push_back(std::move(own));
|
||||
e.bytes += k * sizeof(node);
|
||||
return e.chunks.back().get();
|
||||
}
|
||||
// a smaller request leaves the rest of the current chunk unused
|
||||
// (fewer than k nodes, and k is less than half of the new chunk)
|
||||
const std::size_t count = e.chunk_next;
|
||||
std::unique_ptr<node[]> fresh(new node[count]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
e.chunks.push_back(std::move(fresh));
|
||||
e.chunk_cur = e.chunks.back().get();
|
||||
@@ -81,7 +93,8 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
|
||||
}
|
||||
|
||||
/// copy n bytes into the edit arena and return their offset; a new buffer
|
||||
/// leaves the old one alive, so that string views into it remain valid
|
||||
/// leaves the old one alive, so that string views into it remain valid; n is
|
||||
/// 0 for an empty string (written by an edit, or the key of a new member)
|
||||
inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
@@ -89,7 +102,7 @@ inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
|
||||
{
|
||||
const std::size_t cap = text_capacity(e.text_cap, e.text_used, n);
|
||||
std::unique_ptr<char[]> fresh(new char[cap]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
if (e.text_used != 0)
|
||||
if (NLOHMANN_VIEW_LIKELY(e.text_used != 0)) // (0 only for the first buffer, and then texts is empty)
|
||||
{
|
||||
std::memcpy(fresh.get(), e.texts.back().get(), e.text_used);
|
||||
}
|
||||
@@ -127,8 +140,10 @@ inline void reserve_moved(document_data& d)
|
||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||
{
|
||||
// both grow before either changes, so that the push_backs cannot throw
|
||||
e.moved.reserve((2 * e.moved.size()) + 16);
|
||||
e.moved_cap.reserve((2 * e.moved.size()) + 16);
|
||||
// (both conditions are needed: reserve() may allocate more than asked,
|
||||
// so the two capacities can differ)
|
||||
e.moved.reserve((e.moved.size() * 2) + 16);
|
||||
e.moved_cap.reserve((e.moved.size() * 2) + 16);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,744 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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> // min, sort
|
||||
#include <array> // array
|
||||
#include <cstddef> // offsetof, size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memcpy, memset
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#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>
|
||||
#include <nlohmann/detail/view/object_index.hpp>
|
||||
#include <nlohmann/detail/view/scan.hpp>
|
||||
|
||||
// Images: a document stored so that loading it needs no parsing.
|
||||
//
|
||||
// Layout (little-endian): a 64-byte header, the nodes, the text (the source,
|
||||
// followed by the number tokens written by edits), a NUL, the decoded strings
|
||||
// (followed by the strings written by edits), a NUL. The idea is that of
|
||||
// zero-copy formats such as FlatBuffers (https://github.com/google/flatbuffers)
|
||||
// and YaFF (https://github.com/yandex/yaff); no code is taken from them.
|
||||
// check_image follows the idea of FlatBuffers' Verifier (bounds and
|
||||
// structure) and also checks what the parser guarantees about strings and
|
||||
// numbers, so that reading and serializing a checked image is safe and yields
|
||||
// valid JSON.
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// how load() checks an image
|
||||
enum class image_check
|
||||
{
|
||||
/// everything the parser guarantees: structure and bounds, strings (valid
|
||||
/// UTF-8; source strings without quotes, backslashes, and control
|
||||
/// characters), and numbers (well-formed, matching the stored values)
|
||||
full,
|
||||
/// structure and bounds only: reading and serializing are safe, but a
|
||||
/// crafted image can yield invalid UTF-8, strings that serialize to
|
||||
/// invalid JSON, or numbers that differ from their text
|
||||
bounds,
|
||||
/// none: for images from a trusted source only (a damaged image is
|
||||
/// undefined behavior)
|
||||
none,
|
||||
};
|
||||
|
||||
struct image_header
|
||||
{
|
||||
std::array<char, 4> magic; ///< "NJVI"
|
||||
std::uint32_t version; ///< 1
|
||||
std::uint64_t node_count;
|
||||
std::uint64_t text_size;
|
||||
std::uint64_t arena_size;
|
||||
std::array<std::uint64_t, 4> reserved; ///< zero (for later versions)
|
||||
};
|
||||
static_assert(sizeof(image_header) == 64, "the image header must be 64 bytes");
|
||||
|
||||
constexpr std::uint32_t image_version = 1;
|
||||
|
||||
/// the largest node count and text or string size of an image (as for parsed
|
||||
/// documents, offsets and counts must fit 32 bits)
|
||||
constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
|
||||
|
||||
/// Copy the current structure of an edited document into nodes in document
|
||||
/// order, as the parser would have written them. Text written by edits is
|
||||
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
||||
/// are not finite become null, as dump() writes them.
|
||||
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
|
||||
{
|
||||
struct frame
|
||||
{
|
||||
const node* cur;
|
||||
const node* end;
|
||||
std::size_t index; ///< the container's node in out
|
||||
std::uint32_t count;
|
||||
bool object;
|
||||
};
|
||||
std::vector<frame> stack;
|
||||
const auto string_node = [&](const node & s)
|
||||
{
|
||||
node r = s;
|
||||
r.extra = 0;
|
||||
r.flags = static_cast<std::uint8_t>(s.flags & node_flags::storage);
|
||||
if (r.flags == node_flags::edited)
|
||||
{
|
||||
r.off = static_cast<std::uint32_t>(arena_size + arena_tail.size());
|
||||
arena_tail.append(d.str(s), s.len);
|
||||
r.flags = node_flags::escaped;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
const auto copy_node = [&](const node * v)
|
||||
{
|
||||
node r = *v;
|
||||
switch (static_cast<value_t>(v->kind))
|
||||
{
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
r.flags = 0;
|
||||
r.extra = 0;
|
||||
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
||||
r.len = 0; // counted below
|
||||
r.next = 0; // set when the container is complete
|
||||
stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast<std::uint8_t>(value_t::object)});
|
||||
break;
|
||||
case value_t::string:
|
||||
r = string_node(*v);
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||
{
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(d.str(*v), number_length(*v));
|
||||
}
|
||||
r.flags = 0;
|
||||
break;
|
||||
case value_t::number_float:
|
||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||
{
|
||||
const char* const t = d.str(*v);
|
||||
if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i'))
|
||||
{
|
||||
r = node{}; // nan and infinity: null, as dump() writes them
|
||||
r.kind = static_cast<std::uint8_t>(value_t::null);
|
||||
break;
|
||||
}
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(t, v->len);
|
||||
r.extra = 0xFFFFu; // the digit layout is not recorded
|
||||
}
|
||||
r.flags = 0;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
|
||||
break;
|
||||
case value_t::null:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
r.flags = 0;
|
||||
break;
|
||||
}
|
||||
out.push_back(r);
|
||||
};
|
||||
copy_node(d.nodes);
|
||||
while (!stack.empty())
|
||||
{
|
||||
const frame top = stack.back();
|
||||
if (top.cur == top.end)
|
||||
{
|
||||
node& c = out[top.index];
|
||||
c.len = top.count;
|
||||
c.next = static_cast<std::uint32_t>(out.size() - top.index);
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
if (top.object)
|
||||
{
|
||||
out.push_back(string_node(*top.cur));
|
||||
}
|
||||
const node* const value = top.cur + (top.object ? 1 : 0);
|
||||
stack.back().cur = document_data::after(value);
|
||||
++stack.back().count;
|
||||
copy_node(document_data::deref(value)); // may grow the stack
|
||||
}
|
||||
}
|
||||
|
||||
/// the document as an image
|
||||
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
{
|
||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
const std::size_t arena_size = d.arena_size;
|
||||
std::vector<node> compacted;
|
||||
std::string text_tail;
|
||||
std::string arena_tail;
|
||||
if (d.edits)
|
||||
{
|
||||
compact_nodes(d, arena_size, compacted, text_tail, arena_tail);
|
||||
}
|
||||
const node* const nodes = d.edits ? compacted.data() : d.nodes;
|
||||
const std::size_t count = d.edits ? compacted.size() : d.node_count;
|
||||
const std::size_t text_size = d.size + text_tail.size();
|
||||
const std::size_t total_arena = arena_size + arena_tail.size();
|
||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||
{
|
||||
// LCOV_EXCL_START (4 GiB)
|
||||
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
image_header h{};
|
||||
h.magic = {{'N', 'J', 'V', 'I'}};
|
||||
h.version = image_version;
|
||||
h.node_count = count;
|
||||
h.text_size = text_size;
|
||||
h.arena_size = total_arena;
|
||||
std::vector<std::uint8_t> image(sizeof(h) + (count * sizeof(node)) + text_size + 1 + total_arena + 1);
|
||||
std::uint8_t* o = image.data();
|
||||
std::memcpy(o, &h, sizeof(h));
|
||||
o += sizeof(h);
|
||||
std::memcpy(o, nodes, count * sizeof(node));
|
||||
// the hash indexes are rebuilt by load(), whose check rejects any other
|
||||
// extra of an object, and the same document saves to the same bytes
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (nodes[i].kind == static_cast<std::uint8_t>(value_t::object) && nodes[i].extra != 0)
|
||||
{
|
||||
std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra));
|
||||
}
|
||||
}
|
||||
o += count * sizeof(node);
|
||||
const auto append = [&o](const char* s, std::size_t n)
|
||||
{
|
||||
if (n != 0)
|
||||
{
|
||||
std::memcpy(o, s, n);
|
||||
o += n;
|
||||
}
|
||||
};
|
||||
append(d.src, d.size);
|
||||
append(text_tail.data(), text_tail.size());
|
||||
*o++ = 0;
|
||||
append(d.base[1], arena_size);
|
||||
append(arena_tail.data(), arena_tail.size());
|
||||
*o = 0;
|
||||
return image;
|
||||
}
|
||||
|
||||
/// the parts of a number token that the checks need
|
||||
struct number_token
|
||||
{
|
||||
const unsigned char* int_start;
|
||||
std::size_t int_digits;
|
||||
std::size_t frac_digits;
|
||||
std::int64_t exponent;
|
||||
bool negative;
|
||||
bool is_float; ///< a fraction or an exponent
|
||||
};
|
||||
|
||||
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
|
||||
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
|
||||
{
|
||||
const unsigned char* const e = s + len;
|
||||
const unsigned char* p = s;
|
||||
t.negative = p != e && *p == '-';
|
||||
p += t.negative ? 1 : 0;
|
||||
if (p == e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
t.int_start = p;
|
||||
if (*p == '0')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (*p >= '1' && *p <= '9')
|
||||
{
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
t.int_digits = static_cast<std::size_t>(p - t.int_start);
|
||||
t.frac_digits = 0;
|
||||
t.is_float = false;
|
||||
if (p != e && *p == '.')
|
||||
{
|
||||
const unsigned char* const f0 = ++p;
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
if (p == f0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
t.frac_digits = static_cast<std::size_t>(p - f0);
|
||||
t.is_float = true;
|
||||
}
|
||||
t.exponent = 0;
|
||||
if (p != e && (*p | 0x20u) == 'e')
|
||||
{
|
||||
++p;
|
||||
const bool exp_negative = p != e && *p == '-';
|
||||
p += (p != e && (*p == '+' || *p == '-')) ? 1 : 0;
|
||||
if (p == e || !is_digit(*p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
do
|
||||
{
|
||||
t.exponent = t.exponent < 100000 ? (t.exponent * 10) + (*p - '0') : t.exponent;
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
t.exponent = exp_negative ? -t.exponent : t.exponent;
|
||||
t.is_float = true;
|
||||
}
|
||||
return p == e;
|
||||
}
|
||||
|
||||
/// the digit layout the parser records for a float token (compaction
|
||||
/// writes "many" instead; the caller accepts both)
|
||||
inline std::uint16_t float_layout(const number_token& t)
|
||||
{
|
||||
return static_cast<std::uint16_t>((std::min)(t.int_digits, std::size_t{255}) | ((std::min)(t.frac_digits, std::size_t{255}) << 8u));
|
||||
}
|
||||
|
||||
/// whether a float token (well-formed, per scan_number_token) is finite as
|
||||
/// double; parse() rejects floats that overflow, and as there, only a number
|
||||
/// whose magnitude could reach 1e308 needs the conversion
|
||||
inline bool float_token_finite(const unsigned char* s, std::size_t len, const number_token& t)
|
||||
{
|
||||
if (static_cast<std::int64_t>(t.int_digits) + t.exponent > 300)
|
||||
{
|
||||
node n{};
|
||||
n.kind = static_cast<std::uint8_t>(value_t::number_float);
|
||||
n.len = static_cast<std::uint32_t>(len);
|
||||
const auto v = float_value<double>(reinterpret_cast<const char*>(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
return v <= (std::numeric_limits<double>::max)() && v >= -(std::numeric_limits<double>::max)();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// whether an integer node matches its token the way the parser records it
|
||||
/// (after the bounds check; the token has at most 256 characters)
|
||||
inline bool check_integer(const node& n, const unsigned char* text)
|
||||
{
|
||||
const std::size_t len = number_length(n);
|
||||
const unsigned char* const s = text + n.off;
|
||||
number_token t{};
|
||||
if (!scan_number_token(s, len, t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the token's value is the stored one; number_integer nodes of edits can
|
||||
// be non-negative (as basic_json keeps the type of a value)
|
||||
const bool integer = n.kind == static_cast<std::uint8_t>(value_t::number_integer);
|
||||
if (t.is_float || t.int_digits > 20 || (t.negative && !integer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// (at most 19 digits cannot overflow; 20 digits are compared with 2^64 - 1)
|
||||
if (t.int_digits == 20 && std::memcmp(t.int_start, "18446744073709551615", 20) > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::uint64_t m = 0;
|
||||
for (const unsigned char* d = t.int_start; d != t.int_start + t.int_digits; ++d)
|
||||
{
|
||||
m = (m * 10) + static_cast<std::uint64_t>(*d - '0');
|
||||
}
|
||||
if (integer && m > (t.negative ? std::uint64_t{1} << 63u : (std::uint64_t{1} << 63u) - 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return integer_bits(n) == (t.negative ? 0 - m : m);
|
||||
}
|
||||
|
||||
/// a byte range of the text or of the decoded strings
|
||||
struct byte_range
|
||||
{
|
||||
std::uint32_t off;
|
||||
std::uint32_t len;
|
||||
};
|
||||
|
||||
/// a float token and the digit layout its node records
|
||||
struct float_range
|
||||
{
|
||||
std::uint32_t off;
|
||||
std::uint32_t len;
|
||||
std::uint16_t extra;
|
||||
};
|
||||
|
||||
/// Run check once for each distinct range of ranges (which are within bounds
|
||||
/// and not empty). Ranges that are not identical must not overlap: save()
|
||||
/// writes every string and token to a place of its own, and nodes that share
|
||||
/// a value (copies within a document) share the whole range. This bounds the
|
||||
/// work by the size of the text, however many nodes point to the same bytes.
|
||||
template<typename Check>
|
||||
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b)
|
||||
{
|
||||
return a.off != b.off ? a.off < b.off : a.len < b.len;
|
||||
});
|
||||
std::size_t end = 0;
|
||||
for (std::size_t i = 0; i < ranges.size(); ++i)
|
||||
{
|
||||
const byte_range r = ranges[i];
|
||||
if (i != 0 && r.off == ranges[i - 1].off && r.len == ranges[i - 1].len)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (r.off < end || !check(r))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
end = static_cast<std::size_t>(r.off) + r.len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Check the float nodes: each token once (nodes of the same token must
|
||||
/// record layouts that match it), tokens must not overlap.
|
||||
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b)
|
||||
{
|
||||
if (a.off != b.off)
|
||||
{
|
||||
return a.off < b.off;
|
||||
}
|
||||
if (a.len != b.len)
|
||||
{
|
||||
return a.len < b.len;
|
||||
}
|
||||
return a.extra < b.extra;
|
||||
});
|
||||
std::size_t end = 0;
|
||||
std::size_t i = 0;
|
||||
while (i < ranges.size())
|
||||
{
|
||||
const float_range r = ranges[i];
|
||||
if (r.off < end)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
number_token t{};
|
||||
const unsigned char* const s = text + r.off;
|
||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the digit layout the parser records (or "many", as compaction writes it)
|
||||
const std::uint16_t layout = float_layout(t);
|
||||
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
|
||||
{
|
||||
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
end = static_cast<std::size_t>(r.off) + r.len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// the content checks of check_image: source strings as the parser leaves
|
||||
/// them (no quotes, backslashes, or control characters), decoded strings
|
||||
/// (valid UTF-8), and float tokens
|
||||
inline bool check_contents(const unsigned char* text, const unsigned char* arena,
|
||||
std::vector<byte_range>& source_strings, std::vector<byte_range>& decoded_strings,
|
||||
std::vector<float_range>& floats)
|
||||
{
|
||||
return check_distinct_ranges(source_strings, [text](const byte_range & r)
|
||||
{
|
||||
const unsigned char* const b = text + r.off;
|
||||
return scan_string_run(b, b + r.len) == b + r.len;
|
||||
})
|
||||
&& check_distinct_ranges(decoded_strings, [arena](const byte_range & r)
|
||||
{
|
||||
return valid_utf8_prefix(arena + r.off, r.len) == r.len;
|
||||
})
|
||||
&& check_float_ranges(floats, text);
|
||||
}
|
||||
|
||||
/// Check the nodes of a loaded image against its text and decoded strings:
|
||||
/// kinds, flags, and `extra`; extents and element counts of arrays and
|
||||
/// objects; keys; bounds; string contents (source strings as the parser
|
||||
/// leaves them: no quotes, backslashes, or control characters; all strings
|
||||
/// valid UTF-8); and number tokens.
|
||||
///
|
||||
/// The structure and the bounds are checked node by node. The contents of
|
||||
/// strings and of float tokens are checked afterwards, once for each distinct
|
||||
/// range (see check_distinct_ranges), so that the full check is linear in the
|
||||
/// size of the image plus the sorting of the ranges, and not in the number of
|
||||
/// nodes times the size of the text.
|
||||
inline bool check_image(const node* nodes, std::size_t count, const unsigned char* text, std::size_t text_size,
|
||||
const unsigned char* arena, std::size_t arena_size, bool full)
|
||||
{
|
||||
struct frame
|
||||
{
|
||||
std::size_t end;
|
||||
std::uint32_t len;
|
||||
std::uint32_t seen;
|
||||
bool object;
|
||||
bool expect_key;
|
||||
};
|
||||
std::vector<frame> stack;
|
||||
std::vector<byte_range> source_strings;
|
||||
std::vector<byte_range> decoded_strings;
|
||||
std::vector<float_range> floats;
|
||||
const auto check_string = [&](const node & n) -> bool
|
||||
{
|
||||
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
||||
const std::size_t limit = decoded ? arena_size : text_size;
|
||||
if (n.off > limit || n.len > limit - n.off)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (full && n.len != 0)
|
||||
{
|
||||
(decoded ? decoded_strings : source_strings).push_back(byte_range{n.off, n.len});
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// bounds of a number token; the recorded digit layout must lie within it
|
||||
const auto number_in_bounds = [&](const node & n) -> bool
|
||||
{
|
||||
const std::size_t len = number_length(n);
|
||||
if (len == 0 || n.off > text_size || len > text_size - n.off)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (n.kind != static_cast<std::uint8_t>(value_t::number_float))
|
||||
{
|
||||
return (n.extra >> 8u) == 0;
|
||||
}
|
||||
// float_value() reads the sign, the integer digits, and the point and
|
||||
// fraction digits the layout records (a layout of more than 19 digits
|
||||
// means the general conversion, which stays within the token)
|
||||
const std::size_t int_digits = n.extra & 0xFFu;
|
||||
const std::size_t frac_digits = n.extra >> 8u;
|
||||
const std::size_t need = (text[n.off] == '-' ? 1u : 0u) + int_digits + (frac_digits != 0 ? frac_digits + 1 : 0);
|
||||
return int_digits + frac_digits > 19 || need <= len;
|
||||
};
|
||||
std::size_t i = 0;
|
||||
for (;;)
|
||||
{
|
||||
// close finished arrays and objects
|
||||
while (!stack.empty() && i == stack.back().end)
|
||||
{
|
||||
const frame f = stack.back();
|
||||
if (f.seen != f.len || (f.object && !f.expect_key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
stack.pop_back();
|
||||
if (!stack.empty())
|
||||
{
|
||||
++stack.back().seen;
|
||||
stack.back().expect_key = true;
|
||||
}
|
||||
}
|
||||
if (i == count)
|
||||
{
|
||||
return stack.empty() && (!full || check_contents(text, arena, source_strings, decoded_strings, floats));
|
||||
}
|
||||
if (i != 0 && stack.empty())
|
||||
{
|
||||
return false; // nodes after the root
|
||||
}
|
||||
const node& n = nodes[i];
|
||||
if (!stack.empty() && stack.back().object && stack.back().expect_key)
|
||||
{
|
||||
if (n.kind != static_cast<std::uint8_t>(value_t::string) || !check_string(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
stack.back().expect_key = false;
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
bool complete = true;
|
||||
switch (static_cast<value_t>(n.kind))
|
||||
{
|
||||
case value_t::null:
|
||||
// (the offset of a literal is read to size the output of dump())
|
||||
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::boolean:
|
||||
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::string:
|
||||
if (!check_string(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
if (n.flags != 0 || !number_in_bounds(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (full)
|
||||
{
|
||||
// (the contents of float tokens are checked later; the token of an
|
||||
// integer has at most 256 characters)
|
||||
if (n.kind == static_cast<std::uint8_t>(value_t::number_float))
|
||||
{
|
||||
floats.push_back(float_range{n.off, n.len, n.extra});
|
||||
}
|
||||
else if (!check_integer(n, text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case value_t::array:
|
||||
case value_t::object:
|
||||
{
|
||||
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
||||
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast<std::uint8_t>(value_t::object), true});
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
++i;
|
||||
if (complete && !stack.empty())
|
||||
{
|
||||
++stack.back().seen;
|
||||
stack.back().expect_key = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
||||
{
|
||||
throw_parse_error(116, concat("invalid json_document image: ", what));
|
||||
}
|
||||
|
||||
/// Read an image into d. The text and the decoded strings stay in the image;
|
||||
/// the nodes are copied (so that they are aligned, and edits can change them).
|
||||
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
||||
{
|
||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
if (image == nullptr || size < sizeof(image_header))
|
||||
{
|
||||
throw_invalid_image("too short");
|
||||
}
|
||||
image_header h{};
|
||||
std::memcpy(&h, image, sizeof(h));
|
||||
// (the reserved fields are for later versions)
|
||||
if (std::memcmp(h.magic.data(), "NJVI", 4) != 0 || h.version != image_version
|
||||
|| (h.reserved[0] | h.reserved[1] | h.reserved[2] | h.reserved[3]) != 0)
|
||||
{
|
||||
throw_invalid_image("unknown format");
|
||||
}
|
||||
const std::size_t room = size - sizeof(h);
|
||||
if (h.node_count == 0 || h.node_count > room / sizeof(node) || h.node_count >= image_limit || h.text_size >= image_limit || h.arena_size >= image_limit)
|
||||
{
|
||||
throw_invalid_image("sizes out of range");
|
||||
}
|
||||
const auto count = static_cast<std::size_t>(h.node_count);
|
||||
const auto text_size = static_cast<std::size_t>(h.text_size);
|
||||
const auto arena_size = static_cast<std::size_t>(h.arena_size);
|
||||
const std::size_t text_at = sizeof(h) + (count * sizeof(node));
|
||||
// the text, a NUL, the decoded strings, a NUL, and nothing after them
|
||||
if (size - text_at < 2 || text_size > size - text_at - 2 || arena_size != size - text_at - text_size - 2
|
||||
|| image[text_at + text_size] != 0 || image[size - 1] != 0)
|
||||
{
|
||||
throw_invalid_image("sizes out of range");
|
||||
}
|
||||
|
||||
d.discarded = true;
|
||||
d.edits.reset();
|
||||
d.base[2] = nullptr;
|
||||
d.owned.clear();
|
||||
if (d.owned_image.empty() || image != d.owned_image.data())
|
||||
{
|
||||
d.owned_image.clear();
|
||||
}
|
||||
d.arena.clear();
|
||||
d.indexes.clear();
|
||||
d.index_slots.clear();
|
||||
d.large_objects.clear();
|
||||
d.node_count = 0;
|
||||
d.reserve(count);
|
||||
std::memcpy(d.nodes, image + sizeof(h), count * sizeof(node));
|
||||
d.node_count = count;
|
||||
const std::uint8_t* const text = image + text_at;
|
||||
const std::uint8_t* const arena = text + text_size + 1;
|
||||
d.src = reinterpret_cast<const char*>(text); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
d.size = text_size;
|
||||
d.base[0] = d.src;
|
||||
d.base[1] = reinterpret_cast<const char*>(arena); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
d.arena_size = arena_size;
|
||||
if (check != image_check::none && !check_image(d.nodes, count, text, text_size, arena, arena_size, check == image_check::full))
|
||||
{
|
||||
throw_invalid_image("the check failed");
|
||||
}
|
||||
// the hash indexes of large objects, as after parsing
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
const node& n = d.nodes[i];
|
||||
if (n.kind == static_cast<std::uint8_t>(value_t::object) && n.len >= document_data::index_min_members)
|
||||
{
|
||||
d.large_objects.push_back(static_cast<std::uint32_t>(i));
|
||||
}
|
||||
}
|
||||
build_object_indexes(d);
|
||||
std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building)
|
||||
d.discarded = false;
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -112,7 +112,7 @@ const node* find_member(const document_data& d, const node* object, const char*
|
||||
}
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0)
|
||||
if (d.key_equals(*m, key, n))
|
||||
{
|
||||
return m;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ std::size_t count_members(const document_data& d, const node* object, const char
|
||||
const node* const end = nav::end(d, object);
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (m->len == n && (n == 0 || std::memcmp(d.str(*m), key, n) == 0))
|
||||
if (d.key_equals(*m, key, n))
|
||||
{
|
||||
++result;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct node_flags
|
||||
{
|
||||
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
|
||||
static constexpr std::uint8_t edited = 2; ///< string or number token lives in the edit arena (editable documents)
|
||||
static constexpr std::uint8_t storage = 3; ///< mask: where a string or number token lives (index into document_data::base)
|
||||
static constexpr std::uint8_t storage = escaped | edited; ///< mask: where a string or number token lives (index into document_data::base)
|
||||
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
||||
static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents)
|
||||
static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position
|
||||
|
||||
@@ -26,43 +26,79 @@ namespace detail
|
||||
namespace view
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief locate the decimal point and the end of the mantissa of a float token
|
||||
|
||||
Also checks that the token is a JSON number. Tokens of the parser and of edits
|
||||
always are; an image loaded with image_check::bounds can hold any bytes, which
|
||||
must not reach the conversion (it expects a well-formed token).
|
||||
*/
|
||||
inline bool float_token_layout(const char* first, const char* last, std::size_t& dot, std::size_t& mantissa_end) noexcept
|
||||
{
|
||||
const auto digit = [last](const char* q)
|
||||
{
|
||||
return q != last && is_digit(static_cast<unsigned char>(*q));
|
||||
};
|
||||
const char* p = first;
|
||||
p += (p != last && *p == '-') ? 1 : 0;
|
||||
if (!digit(p) || (*p == '0' && digit(p + 1)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
dot = std::string::npos;
|
||||
if (p != last && *p == '.')
|
||||
{
|
||||
dot = static_cast<std::size_t>(p - first);
|
||||
if (!digit(++p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
}
|
||||
mantissa_end = static_cast<std::size_t>(p - first);
|
||||
if (p != last && (*p == 'e' || *p == 'E'))
|
||||
{
|
||||
++p;
|
||||
p += (p != last && (*p == '+' || *p == '-')) ? 1 : 0;
|
||||
if (!digit(p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
}
|
||||
return p == last;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the value of the float token of a node, as parse() converts it
|
||||
|
||||
Uses the lexer's conversion (detail::convert_float), so that the values are
|
||||
bit-identical to parse(): float and double are converted without allocation
|
||||
and independent of the locale. The digit layout recorded while parsing locates
|
||||
the decimal point and the exponent without scanning the token.
|
||||
and independent of the locale. A token that is not a JSON number (only in a
|
||||
damaged image loaded with image_check::bounds) yields 0.
|
||||
*/
|
||||
template<typename FloatType>
|
||||
NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
||||
{
|
||||
const char* const last = first + n.len;
|
||||
const std::size_t neg = first[0] == '-' ? 1 : 0;
|
||||
const std::size_t int_digits = n.extra & 0xFFu;
|
||||
const std::size_t frac_digits = n.extra >> 8u;
|
||||
std::size_t dot = std::string::npos;
|
||||
std::size_t mantissa_end = n.len;
|
||||
if (int_digits != 255 && frac_digits != 255)
|
||||
std::size_t dot = 0;
|
||||
std::size_t mantissa_end = 0;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!float_token_layout(first, last, dot, mantissa_end)))
|
||||
{
|
||||
dot = frac_digits != 0 ? neg + int_digits : std::string::npos;
|
||||
mantissa_end = neg + int_digits + (frac_digits != 0 ? 1 + frac_digits : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// more digits than the layout records: locate them
|
||||
for (std::size_t i = 0; i < n.len; ++i)
|
||||
{
|
||||
if (first[i] == '.')
|
||||
{
|
||||
dot = i;
|
||||
}
|
||||
else if (first[i] == 'e' || first[i] == 'E')
|
||||
{
|
||||
mantissa_end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return FloatType{};
|
||||
}
|
||||
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
||||
}
|
||||
@@ -93,16 +129,20 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
||||
}
|
||||
if (p != e)
|
||||
{
|
||||
// [eE][+-]digits; huge exponents saturate (the parser rejected overflow)
|
||||
// [eE][+-]digits; huge exponents saturate (the parser rejected
|
||||
// overflow). The token is not read beyond e, and the digits are taken
|
||||
// as unsigned, so that a token that is not well-formed (a damaged
|
||||
// image loaded with image_check::bounds) yields a wrong value, but no
|
||||
// overflow.
|
||||
++p;
|
||||
const bool exp_negative = *p == '-';
|
||||
p += (*p == '-' || *p == '+') ? 1 : 0;
|
||||
const bool exp_negative = p != e && *p == '-';
|
||||
p += (p != e && (*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');
|
||||
exp_value = (exp_value * 10) + static_cast<unsigned char>(*p - '0');
|
||||
}
|
||||
}
|
||||
q += exp_negative ? -exp_value : exp_value;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t, uint64_t
|
||||
#include <cstring> // memcmp
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
@@ -94,7 +93,7 @@ inline void build_object_index(document_data& d, node* obj)
|
||||
while (table[slot] != 0)
|
||||
{
|
||||
const node* const other = obj + table[slot];
|
||||
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0))
|
||||
if (d.key_equals(*other, key, k->len))
|
||||
{
|
||||
duplicate = true; // keep the first
|
||||
break;
|
||||
@@ -141,7 +140,7 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
|
||||
return nullptr;
|
||||
}
|
||||
const node* const k = obj + s;
|
||||
if (k->len == n && (n == 0 || std::memcmp(d.str(*k), key, n) == 0))
|
||||
if (d.key_equals(*k, key, n))
|
||||
{
|
||||
return k;
|
||||
}
|
||||
|
||||
@@ -175,32 +175,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char
|
||||
/// powers of ten up to 10^19 as integers
|
||||
inline std::uint64_t int_pow10(unsigned k) noexcept
|
||||
{
|
||||
static const std::array<std::uint64_t, 20> table =
|
||||
{
|
||||
{
|
||||
1u,
|
||||
10u,
|
||||
100u,
|
||||
1000u,
|
||||
10000u,
|
||||
100000u,
|
||||
1000000u,
|
||||
10000000u,
|
||||
100000000u,
|
||||
1000000000u,
|
||||
10000000000u,
|
||||
100000000000u,
|
||||
1000000000000u,
|
||||
10000000000000u,
|
||||
100000000000000u,
|
||||
1000000000000000u,
|
||||
10000000000000000u,
|
||||
100000000000000000u,
|
||||
1000000000000000000u,
|
||||
10000000000000000000u
|
||||
}
|
||||
};
|
||||
return table[k];
|
||||
return ::nlohmann::detail::dtoa_impl::powers_of_ten()[k];
|
||||
}
|
||||
|
||||
/// value of 0 < k < 8 digits at p in one step if [p, p + 8) lies below
|
||||
|
||||
@@ -31,7 +31,11 @@ namespace view
|
||||
{
|
||||
|
||||
/// append-only output buffer: writes through a raw pointer into a string that
|
||||
/// is resized ahead, and trimmed by finish()
|
||||
/// is resized ahead, and trimmed by finish(). The estimate is reserved, and the
|
||||
/// string grows in steps of 64 KiB within it: resize() fills the new bytes
|
||||
/// with zeros (before C++23, a string cannot grow without), and a small step
|
||||
/// is filled while the writer is about to use it, in the cache, instead of
|
||||
/// filling the whole estimate in memory first.
|
||||
template<typename StringType>
|
||||
class output_buffer
|
||||
{
|
||||
@@ -83,17 +87,46 @@ class output_buffer
|
||||
m_pos += n;
|
||||
}
|
||||
|
||||
/// the write position and the end of the writable space, for a writer
|
||||
/// that keeps the position in a local variable (set_cursor() hands it back)
|
||||
char* cursor() const noexcept
|
||||
{
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
char* limit() const noexcept
|
||||
{
|
||||
return m_end;
|
||||
}
|
||||
|
||||
void set_cursor(char* p) noexcept
|
||||
{
|
||||
m_pos = p;
|
||||
}
|
||||
|
||||
private:
|
||||
/// the size of a growth step
|
||||
static constexpr std::size_t step = 0x10000;
|
||||
|
||||
static StringType& sized(StringType& out, std::size_t estimate)
|
||||
{
|
||||
out.resize((std::max)(estimate, static_cast<std::size_t>(64)));
|
||||
out.reserve(estimate);
|
||||
out.resize(detail::clamp(estimate, std::size_t{64}, step));
|
||||
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));
|
||||
// (a step does not go beyond the reserved estimate, so that a good
|
||||
// estimate is never copied to a larger allocation; not a clamp: the
|
||||
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||
if (size > m_out.capacity())
|
||||
{
|
||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||
}
|
||||
m_out.resize(size);
|
||||
m_pos = &m_out[0] + used;
|
||||
m_end = &m_out[0] + m_out.size();
|
||||
}
|
||||
@@ -103,6 +136,145 @@ class output_buffer
|
||||
char* m_end;
|
||||
};
|
||||
|
||||
/// The length of the run at s that dump() writes unchanged without
|
||||
/// ensure_ascii: all bytes but quotes, backslashes, and control characters.
|
||||
/// Unlike detail::string_bulk_run(), non-ASCII bytes are not validated: the
|
||||
/// strings of a document are valid UTF-8 (a damaged image loaded with
|
||||
/// image_check::bounds can have others, which are then written unchanged).
|
||||
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
const std::uint64_t v = read_eight_bytes(s + i);
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
||||
const std::uint64_t stop = (((q - ones) & ~q) | ((b - ones) & ~b) | ((v - 0x2020202020202020ull) & ~v)) & high;
|
||||
if (stop != 0)
|
||||
{
|
||||
// the lowest flagged byte is the first stop: borrows only flag bytes above a true one
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
{
|
||||
if (s[i] == '"' || s[i] == '\\' || s[i] < 0x20)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/// Whether dump() with ensure_ascii writes the string at s unchanged, for a
|
||||
/// string of the source without escape sequences (which has no quote,
|
||||
/// backslash, or control character): no byte is DEL (0x7F) or above. Adding 1
|
||||
/// to each byte sets its high bit exactly for 0x7F, and a carry into the next
|
||||
/// byte only comes from 0xFF, whose own high bit is set anyway.
|
||||
inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
const std::uint64_t v = read_eight_bytes(s + i);
|
||||
if (((v | (v + ones)) & high) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
{
|
||||
if (static_cast<unsigned char>(s[i]) >= 0x7F)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||
/// shallow documents need no allocation. The top is a pointer, as in
|
||||
/// std::vector. grow() is static and out of line: it gets the vector and the
|
||||
/// used range and returns the new storage, so the address of the stack never
|
||||
/// reaches a call. Otherwise the compiler would have to keep the three
|
||||
/// pointers in memory and load and store them on every push_back() and
|
||||
/// pop_back(), not just when growing.
|
||||
template<typename T>
|
||||
class small_stack
|
||||
{
|
||||
public:
|
||||
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
|
||||
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(heap)
|
||||
{}
|
||||
small_stack(const small_stack&) = delete;
|
||||
small_stack(small_stack&&) = delete;
|
||||
small_stack& operator=(const small_stack&) = delete;
|
||||
small_stack& operator=(small_stack&&) = delete;
|
||||
~small_stack() = default;
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void push_back(const T& x)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_top == m_end))
|
||||
{
|
||||
const std::size_t used = size();
|
||||
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
|
||||
m_begin = grow(m_heap, m_begin, used, capacity);
|
||||
m_top = m_begin + used;
|
||||
m_end = m_begin + capacity;
|
||||
}
|
||||
*m_top++ = x;
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T& back() noexcept
|
||||
{
|
||||
return m_top[-1];
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void pop_back() noexcept
|
||||
{
|
||||
--m_top;
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool empty() const noexcept
|
||||
{
|
||||
return m_top == m_begin;
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t size() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(m_top - m_begin);
|
||||
}
|
||||
|
||||
private:
|
||||
/// the used entries moved to heap storage of the given capacity
|
||||
NLOHMANN_VIEW_NOINLINE static T* grow(std::vector<T>& heap, const T* begin, std::size_t used, std::size_t capacity)
|
||||
{
|
||||
std::vector<T> bigger(capacity);
|
||||
std::copy(begin, begin + used, bigger.begin());
|
||||
heap.swap(bigger);
|
||||
return heap.data();
|
||||
}
|
||||
|
||||
T* m_begin;
|
||||
T* m_top;
|
||||
T* m_end;
|
||||
std::vector<T>& m_heap;
|
||||
};
|
||||
|
||||
/// a string literal at w, without its terminating NUL (after room())
|
||||
/// @return a pointer past it
|
||||
template<std::size_t N>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexcept // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
std::memcpy(w, s, N - 1);
|
||||
return w + N - 1;
|
||||
}
|
||||
|
||||
/// how the view's dump() writes a value
|
||||
struct dump_style
|
||||
{
|
||||
@@ -129,6 +301,8 @@ class view_serializer
|
||||
using nav = navigation<Editable>;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
/// doubles that are IEEE 754 binary64 are written from their bits (Zmij)
|
||||
using fast_double = std::integral_constant < bool, std::is_same<number_float_t, double>::value && ::nlohmann::detail::dtoa_impl::is_binary64<double>::value >;
|
||||
|
||||
public:
|
||||
view_serializer(const document_data& d, string_t& out, std::size_t estimate, const dump_style& style)
|
||||
@@ -137,6 +311,22 @@ class view_serializer
|
||||
|
||||
void dump(const node* root)
|
||||
{
|
||||
if (!m_style.pretty)
|
||||
{
|
||||
if (m_style.ensure_ascii)
|
||||
{
|
||||
dump_compact_ascii(root);
|
||||
}
|
||||
else if (m_style.source_numbers)
|
||||
{
|
||||
dump_compact<true, false>(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
dump_compact<false, false>(root);
|
||||
}
|
||||
return;
|
||||
}
|
||||
struct frame
|
||||
{
|
||||
const node* pos; ///< next element, or key of the next member
|
||||
@@ -144,7 +334,9 @@ class view_serializer
|
||||
bool object;
|
||||
bool first; ///< nothing written yet
|
||||
};
|
||||
std::vector<frame> stack;
|
||||
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::vector<frame> heap;
|
||||
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||
const node* n = root;
|
||||
for (;;)
|
||||
{
|
||||
@@ -175,15 +367,15 @@ class view_serializer
|
||||
m_out.finish();
|
||||
return;
|
||||
}
|
||||
frame& f = stack.back();
|
||||
if (f.pos == f.end)
|
||||
if (stack.back().pos == stack.back().end)
|
||||
{
|
||||
const bool object = f.object; // (f dangles after pop_back)
|
||||
const bool object = stack.back().object;
|
||||
stack.pop_back();
|
||||
newline(stack.size());
|
||||
m_out.put(object ? '}' : ']');
|
||||
continue;
|
||||
}
|
||||
frame& f = stack.back();
|
||||
if (!f.first)
|
||||
{
|
||||
m_out.put(',');
|
||||
@@ -215,6 +407,337 @@ class view_serializer
|
||||
}
|
||||
|
||||
private:
|
||||
/// a string that is not a plain string of the source (decoded, or written
|
||||
/// by an edit), without ensure_ascii: runs without characters to escape
|
||||
/// are copied
|
||||
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
|
||||
{
|
||||
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
m_out.put('"');
|
||||
for (std::size_t i = 0; i < n.len;)
|
||||
{
|
||||
const std::size_t run = plain_output_run(s + i, n.len - i);
|
||||
if (run != 0)
|
||||
{
|
||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
i += run;
|
||||
continue;
|
||||
}
|
||||
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||
++i;
|
||||
}
|
||||
m_out.put('"');
|
||||
}
|
||||
|
||||
/// dump_compact() with ensure_ascii, out of line: the walk without it is
|
||||
/// faster when it is not merged with this one into dump()
|
||||
NLOHMANN_VIEW_NOINLINE void dump_compact_ascii(const node* root)
|
||||
{
|
||||
if (m_style.source_numbers)
|
||||
{
|
||||
dump_compact<true, true>(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
dump_compact<false, true>(root);
|
||||
}
|
||||
}
|
||||
|
||||
/// whether dump_compact() copies a string or key from the source: one of
|
||||
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
||||
/// each, so that the walk without ensure_ascii is compiled exactly as
|
||||
/// before: with GCC, a condition that only folds away later changed its
|
||||
/// block layout and made it 4-6% slower)
|
||||
static bool copied(const node& n, const char* /*src*/, std::false_type /*ensure_ascii*/) noexcept
|
||||
{
|
||||
return (n.flags & node_flags::storage) == 0;
|
||||
}
|
||||
|
||||
static bool copied(const node& n, const char* src, std::true_type /*ensure_ascii*/) noexcept
|
||||
{
|
||||
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
||||
}
|
||||
|
||||
/// a string with ensure_ascii (dump_compact()), out of line
|
||||
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
|
||||
{
|
||||
write_string(n);
|
||||
}
|
||||
|
||||
/// the copies of dump_compact() that are not fixed-size moves (long
|
||||
/// strings, or near the end of the source); out of line, so that the
|
||||
/// compiler does not merge the fixed-size moves into this call
|
||||
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
||||
{
|
||||
std::memcpy(to, from, n);
|
||||
}
|
||||
|
||||
/// a compile-time option as a runtime condition: testing the template
|
||||
/// argument directly makes a condition like `Editable && c` constant when
|
||||
/// the option is off, which MSVC reports as C4127
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE bool enabled(bool option) noexcept
|
||||
{
|
||||
return option;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the compact output (the default dump(), or with ensure_ascii)
|
||||
|
||||
The same walk as dump(), with the write position in a local variable
|
||||
(stores through char pointers would otherwise force a reload of the
|
||||
buffer's members after each one), and with strings and number tokens of
|
||||
the source copied by fixed-size moves of 32 bytes where the source has
|
||||
that many bytes left, instead of a library call per token. The buffer
|
||||
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
||||
string of the source is copied only if it has no byte >= 0x7F (see
|
||||
is_ascii_without_delete()); any other string is written by write_string().
|
||||
*/
|
||||
template<bool SourceNumbers, bool Ascii>
|
||||
void dump_compact(const node* root)
|
||||
{
|
||||
struct frame
|
||||
{
|
||||
const node* pos; ///< (editable documents) next element, or key of the next member
|
||||
const node* end;
|
||||
bool object;
|
||||
};
|
||||
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::vector<frame> heap;
|
||||
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||
const char* const src = m_doc.src;
|
||||
const char* const src_end = src + m_doc.size;
|
||||
char* w = m_out.cursor();
|
||||
char* lim = m_out.limit();
|
||||
// room for n bytes and the slack
|
||||
const auto room = [&](std::size_t n)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(lim - w) < n + 64))
|
||||
{
|
||||
m_out.set_cursor(w);
|
||||
m_out.reserve(n + 64);
|
||||
w = m_out.cursor();
|
||||
lim = m_out.limit();
|
||||
}
|
||||
};
|
||||
// copy n bytes of the source (after room(n))
|
||||
const auto copy = [&](const char* from, std::size_t n)
|
||||
{
|
||||
if (n <= 32 && static_cast<std::size_t>(src_end - from) >= 32)
|
||||
{
|
||||
std::memcpy(w, from, 32);
|
||||
}
|
||||
else if (n <= 256 && static_cast<std::size_t>(src_end - from) >= n + 32)
|
||||
{
|
||||
for (std::size_t i = 0; i < n; i += 32)
|
||||
{
|
||||
std::memcpy(w + i, from + i, 32);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_long(w, from, n);
|
||||
}
|
||||
w += n;
|
||||
};
|
||||
// a string that is not a plain string of the source (out of line, so
|
||||
// that the cursor stays in a register here)
|
||||
const auto escaped = [&](const node & n)
|
||||
{
|
||||
m_out.set_cursor(w);
|
||||
if (enabled(Ascii))
|
||||
{
|
||||
write_ascii_string(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_decoded(n);
|
||||
}
|
||||
w = m_out.cursor();
|
||||
lim = m_out.limit();
|
||||
};
|
||||
|
||||
// Read-only documents: the elements of a container follow it in the
|
||||
// node array, so the walk goes through the array in order, and a
|
||||
// frame only needs the end of its container. Editable documents: the
|
||||
// elements of a moved container live elsewhere, so a frame keeps the
|
||||
// position of the next element (see navigation).
|
||||
// The innermost open container is kept in registers (cur; end ==
|
||||
// nullptr: none), the stack holds the ones around it.
|
||||
frame cur{nullptr, nullptr, false};
|
||||
const node* n = root;
|
||||
for (;;)
|
||||
{
|
||||
// write the value at n (read-only documents: and advance n)
|
||||
bool opened = false;
|
||||
// false positive: n comes from nav::value(), which never returns null for a valid index
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
{
|
||||
case value_t::string:
|
||||
if (copied(*n, src, std::integral_constant<bool, Ascii>()))
|
||||
{
|
||||
room(n->len + 2);
|
||||
*w++ = '"';
|
||||
copy(src + n->off, n->len);
|
||||
*w++ = '"';
|
||||
}
|
||||
else
|
||||
{
|
||||
escaped(*n);
|
||||
}
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
const std::uint32_t len = number_length(*n);
|
||||
room(len);
|
||||
if (enabled(Editable) && (n->flags & node_flags::storage) != 0)
|
||||
{
|
||||
copy_long(w, m_doc.str(*n), len); // a canonical token written by an edit
|
||||
w += len;
|
||||
break;
|
||||
}
|
||||
const char* const token = src + n->off;
|
||||
if (!enabled(SourceNumbers) && NLOHMANN_VIEW_UNLIKELY(len == 2 && token[0] == '-' && token[1] == '0'))
|
||||
{
|
||||
*w++ = '0'; // parse() reads -0 as the integer 0
|
||||
}
|
||||
else
|
||||
{
|
||||
copy(token, len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case value_t::number_float:
|
||||
if (enabled(SourceNumbers) && (n->flags & node_flags::storage) != node_flags::edited)
|
||||
{
|
||||
room(n->len);
|
||||
copy(src + n->off, n->len);
|
||||
}
|
||||
else if (enabled(fast_double::value))
|
||||
{
|
||||
room(64);
|
||||
w = write_double_at(w, *n);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_out.set_cursor(w);
|
||||
write_float_node(*n);
|
||||
w = m_out.cursor();
|
||||
lim = m_out.limit();
|
||||
}
|
||||
break;
|
||||
case value_t::boolean:
|
||||
room(8);
|
||||
if ((n->flags & node_flags::is_true) != 0)
|
||||
{
|
||||
w = put_literal(w, "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
w = put_literal(w, "false");
|
||||
}
|
||||
break;
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
{
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
room(8);
|
||||
if (n->len == 0)
|
||||
{
|
||||
w = object ? put_literal(w, "{}") : put_literal(w, "[]");
|
||||
}
|
||||
else
|
||||
{
|
||||
*w++ = object ? '{' : '[';
|
||||
stack.push_back(cur);
|
||||
if (Editable)
|
||||
{
|
||||
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
||||
}
|
||||
else
|
||||
{
|
||||
cur = frame{nullptr, n + n->next, object};
|
||||
}
|
||||
opened = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case value_t::null:
|
||||
room(8);
|
||||
w = put_literal(w, "null");
|
||||
break;
|
||||
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
|
||||
}
|
||||
if (!Editable)
|
||||
{
|
||||
++n; // the next node: the first element of an opened container, or the node after a scalar
|
||||
}
|
||||
|
||||
// go to the next value: close finished containers, then separate
|
||||
// (a container just opened has an element)
|
||||
if (!opened)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (cur.end == nullptr)
|
||||
{
|
||||
m_out.set_cursor(w);
|
||||
m_out.finish();
|
||||
return;
|
||||
}
|
||||
if ((Editable ? cur.pos : n) != cur.end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
room(1);
|
||||
*w++ = cur.object ? '}' : ']';
|
||||
cur = stack.back();
|
||||
stack.pop_back();
|
||||
}
|
||||
room(1);
|
||||
*w++ = ',';
|
||||
}
|
||||
const node* const at = Editable ? cur.pos : n;
|
||||
if (cur.object)
|
||||
{
|
||||
const node& key = *at;
|
||||
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
||||
{
|
||||
room(key.len + 3);
|
||||
*w++ = '"';
|
||||
copy(src + key.off, key.len);
|
||||
w[0] = '"';
|
||||
w[1] = ':';
|
||||
w += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
escaped(key);
|
||||
room(1);
|
||||
*w++ = ':';
|
||||
}
|
||||
if (Editable)
|
||||
{
|
||||
n = nav::value(at + 1);
|
||||
cur.pos = document_data::after(at + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
++n;
|
||||
}
|
||||
}
|
||||
else if (Editable)
|
||||
{
|
||||
n = nav::value(at);
|
||||
cur.pos = document_data::after(at);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void newline(std::size_t level)
|
||||
{
|
||||
if (m_style.pretty)
|
||||
@@ -266,7 +789,7 @@ class view_serializer
|
||||
}
|
||||
else
|
||||
{
|
||||
write_float(float_value<number_float_t>(m_doc, n));
|
||||
write_float_node(n);
|
||||
}
|
||||
break;
|
||||
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
||||
@@ -278,6 +801,87 @@ class view_serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// a float node as dump() writes it
|
||||
void write_float_node(const node& n)
|
||||
{
|
||||
write_float_node(n, fast_double {});
|
||||
}
|
||||
|
||||
void write_float_node(const node& n, std::false_type /*other*/)
|
||||
{
|
||||
write_float(float_value<number_float_t>(m_doc, n));
|
||||
}
|
||||
|
||||
void write_float_node(const node& n, std::true_type /*double*/)
|
||||
{
|
||||
m_out.reserve(64);
|
||||
m_out.set_cursor(write_double_at(m_out.cursor(), n));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
||||
|
||||
A token of at most 15 significant digits is written from its digits,
|
||||
without a conversion: two decimals of at most 15 digits are farther
|
||||
apart than the rounding interval of a (normal) double (the argument
|
||||
behind DBL_DIG), so the token's digits are the shortest ones of its
|
||||
double, which the library's conversion writes (Zmij). Other tokens are
|
||||
converted from the digits already read.
|
||||
*/
|
||||
char* write_double_at(char* w, const node& n)
|
||||
{
|
||||
const unsigned int_digits = n.extra & 0xFFu;
|
||||
const unsigned frac_digits = n.extra >> 8u;
|
||||
if ((n.flags & node_flags::storage) != node_flags::edited && int_digits + frac_digits <= 19)
|
||||
{
|
||||
const auto* const first = reinterpret_cast<const unsigned char*>(m_doc.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const float_significand d = layout_decimal(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
// (the exponent keeps the value far from subnormals and overflow)
|
||||
if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290)
|
||||
{
|
||||
*w = '-';
|
||||
w += d.negative ? 1 : 0;
|
||||
// (without leading zeros, all digits of the token count; the
|
||||
// check also keeps an image that was only checked for bounds,
|
||||
// whose token may not be made of digits, from the counted
|
||||
// overload)
|
||||
const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten();
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
return count - 1u < 15u && d.w >= powers[count - 1u] && d.w < powers[count]
|
||||
? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(count), static_cast<int>(d.exponent))
|
||||
: ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(d.exponent));
|
||||
}
|
||||
return write_double_value_at(w, decimal_to_float<double>(d)); // (without reading the token again)
|
||||
}
|
||||
return write_double_value_at(w, static_cast<double>(float_value<number_float_t>(m_doc, n)));
|
||||
}
|
||||
|
||||
/// n bytes of text at w
|
||||
static char* write_text_at(char* w, const char* text, std::size_t n) noexcept
|
||||
{
|
||||
std::memcpy(w, text, n);
|
||||
return w + n;
|
||||
}
|
||||
|
||||
/// a double as dump() writes it, at w (64 bytes of room)
|
||||
static char* write_double_value_at(char* w, double x)
|
||||
{
|
||||
// (from the bits: without the checks of to_chars())
|
||||
auto bits = bit_cast<std::uint64_t>(x);
|
||||
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||
{
|
||||
return write_text_at(w, "null", 4);
|
||||
}
|
||||
*w = '-';
|
||||
w += bits >> 63u;
|
||||
bits &= ~(std::uint64_t{1} << 63u);
|
||||
if (bits == 0)
|
||||
{
|
||||
return write_text_at(w, "0.0", 3);
|
||||
}
|
||||
return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits));
|
||||
}
|
||||
|
||||
/// as serializer::dump_float()
|
||||
void write_float(number_float_t x)
|
||||
{
|
||||
@@ -311,9 +915,10 @@ class view_serializer
|
||||
{
|
||||
const char* const s = m_doc.str(n);
|
||||
m_out.put('"');
|
||||
if ((n.flags & node_flags::storage) == 0 && !m_style.ensure_ascii)
|
||||
if ((n.flags & node_flags::storage) == 0 && (!m_style.ensure_ascii || is_ascii_without_delete(s, n.len)))
|
||||
{
|
||||
// a string of the source without escape sequences has nothing to escape
|
||||
// a string of the source without escape sequences has nothing to
|
||||
// escape (with ensure_ascii, if it is printable ASCII)
|
||||
m_out.put(s, n.len);
|
||||
}
|
||||
else if (m_style.ensure_ascii)
|
||||
@@ -327,7 +932,9 @@ class view_serializer
|
||||
m_out.put('"');
|
||||
}
|
||||
|
||||
/// as serializer::dump_escaped() for valid UTF-8 (the view has no other)
|
||||
/// as serializer::dump_escaped(); strings of a document are valid UTF-8,
|
||||
/// except in a damaged image loaded with image_check::bounds, for which
|
||||
/// this throws what basic_json::dump() throws for the string
|
||||
template<bool EnsureAscii>
|
||||
void write_escaped(const unsigned char* s, std::size_t n)
|
||||
{
|
||||
@@ -351,12 +958,13 @@ class view_serializer
|
||||
}
|
||||
std::uint32_t codepoint = s[i];
|
||||
std::size_t len = 1;
|
||||
if (codepoint >= 0xC0)
|
||||
if (codepoint >= 0x80)
|
||||
{
|
||||
len = 2;
|
||||
if (codepoint >= 0xE0)
|
||||
len = validate_one_utf8(s + i, n - i);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
|
||||
{
|
||||
len = codepoint >= 0xF0 ? 4 : 3;
|
||||
invalid_utf8(s, n);
|
||||
return;
|
||||
}
|
||||
codepoint &= 0xFFu >> (len + 1);
|
||||
for (std::size_t k = 1; k < len; ++k)
|
||||
@@ -369,6 +977,13 @@ class view_serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
|
||||
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
||||
{
|
||||
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
static_cast<void>(dumped);
|
||||
}
|
||||
|
||||
template<bool EnsureAscii>
|
||||
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
|
||||
{
|
||||
|
||||
@@ -50,7 +50,12 @@
|
||||
#endif
|
||||
#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
|
||||
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
|
||||
#include <cpuid.h>
|
||||
// <cpuid.h> of GCC before 11 has no include guard, and json.hpp and
|
||||
// json_view.hpp both include it
|
||||
#ifndef NLOHMANN_JSON_CPUID_INCLUDED
|
||||
#define NLOHMANN_JSON_CPUID_INCLUDED
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
#include <tmmintrin.h>
|
||||
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||
#define NLOHMANN_VIEW_SSSE3_TARGET __attribute__((target("ssse3")))
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <cstdint> // int64_t
|
||||
#include <map> // map
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // enable_if, is_constructible
|
||||
#include <type_traits> // decay, enable_if, is_arithmetic, is_constructible, is_integral, is_same
|
||||
#include <unordered_map> // unordered_map
|
||||
#include <vector> // vector
|
||||
|
||||
@@ -104,6 +104,20 @@ Map map_value(const View& v)
|
||||
template<typename Key>
|
||||
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
|
||||
|
||||
/// the arithmetic types arithmetic_value() converts to (bool has its own
|
||||
/// conversion)
|
||||
template<typename T>
|
||||
struct is_non_bool_arithmetic : conjunction<std::is_arithmetic<T>, negation<std::is_same<T, bool>>> {};
|
||||
|
||||
/// the default values of value() other than C strings, which have their own
|
||||
/// overload (returning string_t)
|
||||
template<typename T>
|
||||
struct is_non_c_string_default : negation<std::is_same<typename std::decay<T>::type, const char*>> {};
|
||||
|
||||
/// the integral types accepted as an array index by the edits of a document
|
||||
template<typename T>
|
||||
struct is_non_bool_integral : conjunction<std::is_integral<T>, negation<std::is_same<T, bool>>> {};
|
||||
|
||||
/// whether View::get<T>() converts to T without materialize(); get_to()
|
||||
/// assigns these, and passes the others to from_json
|
||||
template<typename T, typename BasicJsonType, typename View>
|
||||
|
||||
+439
-66
@@ -19,6 +19,7 @@
|
||||
#define INCLUDE_NLOHMANN_JSON_HPP_
|
||||
|
||||
#include <algorithm> // all_of, find, for_each, none_of
|
||||
#include <array> // array
|
||||
#include <cmath> // isnan
|
||||
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
||||
#include <cstdint> // uint8_t
|
||||
@@ -6321,7 +6322,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_cbor(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6331,7 +6332,9 @@ public:
|
||||
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_cbor(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_cbor(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a CBOR serialization of a given JSON value
|
||||
@@ -6339,7 +6342,9 @@ public:
|
||||
static void to_cbor(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_cbor(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_cbor(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a MessagePack serialization of a given JSON value
|
||||
@@ -6348,7 +6353,7 @@ public:
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_msgpack(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6358,7 +6363,9 @@ public:
|
||||
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_msgpack(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_msgpack(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a MessagePack serialization of a given JSON value
|
||||
@@ -6366,7 +6373,9 @@ public:
|
||||
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_msgpack(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_msgpack(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a UBJSON serialization of a given JSON value
|
||||
@@ -6377,7 +6386,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
|
||||
return result;
|
||||
}
|
||||
@@ -6388,7 +6397,9 @@ public:
|
||||
const bool use_size = false, const bool use_type = false,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a UBJSON serialization of a given JSON value
|
||||
@@ -6397,7 +6408,9 @@ public:
|
||||
const bool use_size = false, const bool use_type = false,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BJData serialization of a given JSON value
|
||||
@@ -6409,7 +6422,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
return result;
|
||||
}
|
||||
@@ -6421,7 +6434,9 @@ public:
|
||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BJData serialization of a given JSON value
|
||||
@@ -6431,7 +6446,9 @@ public:
|
||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BSON serialization of a given JSON value
|
||||
@@ -6440,7 +6457,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result, error_handler).write_bson(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6450,7 +6467,9 @@ public:
|
||||
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_bson(j);
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_bson(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BSON serialization of a given JSON value
|
||||
@@ -6458,7 +6477,9 @@ public:
|
||||
static void to_bson(const basic_json& j, detail::output_adapter<char> o,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char>(o, error_handler).write_bson(j);
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_bson(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
@@ -6466,7 +6487,7 @@ public:
|
||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
result.reserve(detail::binary_initial_capacity(j));
|
||||
vector_writer(result).write_bon8(j);
|
||||
return result;
|
||||
}
|
||||
@@ -6475,14 +6496,18 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o)
|
||||
{
|
||||
binary_writer<std::uint8_t>(o).write_bon8(j);
|
||||
binary_writer<std::uint8_t> writer(o);
|
||||
writer.write_bon8(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
||||
{
|
||||
binary_writer<char>(o).write_bon8(j);
|
||||
binary_writer<char> writer(o);
|
||||
writer.write_bon8(j);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in CBOR format
|
||||
@@ -6878,6 +6903,72 @@ public:
|
||||
/// @name JSON Patch functions
|
||||
/// @{
|
||||
|
||||
private:
|
||||
/// the members of an operation of a JSON patch that are of interest
|
||||
struct patch_members
|
||||
{
|
||||
basic_json* op = nullptr;
|
||||
basic_json* path = nullptr;
|
||||
basic_json* value = nullptr;
|
||||
basic_json* from = nullptr;
|
||||
};
|
||||
|
||||
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
|
||||
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
|
||||
{
|
||||
patch_members members;
|
||||
for (auto& member : *val.m_data.m_value.object)
|
||||
{
|
||||
const string_t& key = member.first;
|
||||
switch (key.size())
|
||||
{
|
||||
case 2:
|
||||
if (key == "op")
|
||||
{
|
||||
members.op = &member.second;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (key == "path")
|
||||
{
|
||||
members.path = &member.second;
|
||||
}
|
||||
else if (key == "from")
|
||||
{
|
||||
members.from = &member.second;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if (key == "value")
|
||||
{
|
||||
members.value = &member.second;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
/// @brief find the members by lookup for key types other than string_t
|
||||
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
|
||||
{
|
||||
const auto find_member = [&val](const char* name) -> basic_json *
|
||||
{
|
||||
const auto it = val.m_data.m_value.object->find(string_t(name));
|
||||
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
|
||||
};
|
||||
|
||||
patch_members members;
|
||||
members.op = find_member("op");
|
||||
members.path = find_member("path");
|
||||
members.value = find_member("value");
|
||||
members.from = find_member("from");
|
||||
return members;
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief applies a JSON patch in-place without copying the object
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
||||
void patch_inplace(const basic_json& json_patch)
|
||||
@@ -7045,31 +7136,155 @@ public:
|
||||
detail::throw_patch_not_an_array(json_patch);
|
||||
}
|
||||
|
||||
// the usual patch operations "add", "remove", "replace" and "test" on
|
||||
// a pointer without escapes that denotes existing array elements and
|
||||
// object members (as "add" does for the parent of the new value), as
|
||||
// a diff creates them: apply the operation and return true; return
|
||||
// false, with nothing changed, for all others
|
||||
string_t token;
|
||||
typename json_pointer::template plain_steps<basic_json> steps;
|
||||
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// a missing "value" is reported below
|
||||
const basic_json* const value_ptr = members.value;
|
||||
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
|
||||
if (operation != patch_operations::remove && value_ptr == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case patch_operations::replace:
|
||||
{
|
||||
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||
if (target == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*target = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
case patch_operations::test:
|
||||
{
|
||||
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||
// a failing test is reported below
|
||||
return target != nullptr && *target == value;
|
||||
}
|
||||
|
||||
case patch_operations::add:
|
||||
case patch_operations::remove:
|
||||
{
|
||||
// the parent of the pointer and its last reference token
|
||||
std::size_t slash = path.size();
|
||||
while (slash > 0 && path[slash - 1] != '/')
|
||||
{
|
||||
--slash;
|
||||
}
|
||||
if (slash == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
--slash;
|
||||
|
||||
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
|
||||
if (parent == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
token.clear();
|
||||
token.append(path.data() + slash + 1, path.size() - slash - 1);
|
||||
if (token.find_first_of('~') != string_t::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (operation == patch_operations::add)
|
||||
{
|
||||
if (parent->is_object())
|
||||
{
|
||||
(*parent)[token] = value;
|
||||
return true;
|
||||
}
|
||||
if (parent->is_array())
|
||||
{
|
||||
typename basic_json::size_type idx{};
|
||||
if (token == "-")
|
||||
{
|
||||
parent->push_back(value);
|
||||
return true;
|
||||
}
|
||||
if (json_pointer::template parse_array_index<basic_json_t>(token, idx) == json_pointer::array_index_status::ok && idx <= parent->size())
|
||||
{
|
||||
parent->insert(parent->begin() + static_cast<difference_type>(idx), value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent->is_object())
|
||||
{
|
||||
const auto it = parent->find(token);
|
||||
if (it == parent->end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
parent->erase(it);
|
||||
return true;
|
||||
}
|
||||
if (parent->is_array())
|
||||
{
|
||||
typename basic_json::size_type idx{};
|
||||
if (json_pointer::template parse_array_index<basic_json_t>(token, idx) == json_pointer::array_index_status::ok && idx < parent->size())
|
||||
{
|
||||
parent->erase(idx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case patch_operations::move:
|
||||
case patch_operations::copy:
|
||||
case patch_operations::invalid:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// the name of the first member to check
|
||||
const string_t op_member = "op";
|
||||
|
||||
// iterate and apply the operations
|
||||
for (const auto& val : json_patch)
|
||||
{
|
||||
// wrapper to get a value for an operation
|
||||
const auto get_value = [&val](const string_t& op,
|
||||
const string_t& member,
|
||||
const auto get_value = [&val](basic_json * found,
|
||||
const string_t& op,
|
||||
const char* member,
|
||||
bool string_type) -> basic_json &
|
||||
{
|
||||
// find value
|
||||
auto it = val.m_data.m_value.object->find(member);
|
||||
|
||||
// check if the desired value is present
|
||||
if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
|
||||
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
|
||||
{
|
||||
detail::throw_patch_missing_member(op, member, false, val);
|
||||
detail::throw_patch_missing_member(op, string_t(member), false, val);
|
||||
}
|
||||
|
||||
// check if the result is of type string
|
||||
if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
|
||||
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
|
||||
{
|
||||
detail::throw_patch_missing_member(op, member, true, val);
|
||||
detail::throw_patch_missing_member(op, string_t(member), true, val);
|
||||
}
|
||||
|
||||
// no error: return value
|
||||
return it->second;
|
||||
return *found;
|
||||
};
|
||||
|
||||
// type check: every element of the array must be an object
|
||||
@@ -7078,16 +7293,30 @@ public:
|
||||
detail::throw_patch_not_an_array(val);
|
||||
}
|
||||
|
||||
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
|
||||
|
||||
// collect mandatory members
|
||||
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
|
||||
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
||||
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
|
||||
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string;
|
||||
const patch_operations operation = get_op(op);
|
||||
|
||||
// the usual operations on a plain pointer are applied without
|
||||
// building the pointer's reference tokens; anything else (and
|
||||
// every error) is handled below
|
||||
if (try_plain(operation, path, members, val))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// the general operations may change any container
|
||||
steps.reset();
|
||||
json_pointer ptr(path);
|
||||
|
||||
switch (get_op(op))
|
||||
switch (operation)
|
||||
{
|
||||
case patch_operations::add:
|
||||
{
|
||||
operation_add(ptr, get_value("add", "value", false));
|
||||
operation_add(ptr, get_value(members.value, "add", "value", false));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7100,13 +7329,13 @@ public:
|
||||
case patch_operations::replace:
|
||||
{
|
||||
// the "path" location must exist - use at()
|
||||
result.at(ptr) = get_value("replace", "value", false);
|
||||
result.at(ptr) = get_value(members.value, "replace", "value", false);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::move:
|
||||
{
|
||||
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
|
||||
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string;
|
||||
json_pointer from_ptr(from_path);
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
||||
@@ -7129,7 +7358,7 @@ public:
|
||||
|
||||
case patch_operations::copy:
|
||||
{
|
||||
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
|
||||
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string;
|
||||
const json_pointer from_ptr(from_path);
|
||||
|
||||
// The copy is functionally identical to an "add"
|
||||
@@ -7147,7 +7376,7 @@ public:
|
||||
{
|
||||
// check if "value" matches the one at "path"
|
||||
// the "path" location must exist - use at()
|
||||
success = (result.at(ptr) == get_value("test", "value", false));
|
||||
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
|
||||
}
|
||||
JSON_INTERNAL_CATCH (out_of_range&)
|
||||
{
|
||||
@@ -7191,7 +7420,8 @@ public:
|
||||
{
|
||||
// the patch
|
||||
basic_json result(value_t::array);
|
||||
diff_recursively(result, source, target, path, 0);
|
||||
string_t buffer = path;
|
||||
diff_recursively(result, source, target, buffer, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -7235,31 +7465,130 @@ public:
|
||||
// function it appears in. In diff_recursively, which is on the call stack
|
||||
// once per nesting level, that made every level cost kilobytes of stack.
|
||||
|
||||
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
|
||||
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
|
||||
{
|
||||
// the keys are distinct and in the order of std::map; the members are
|
||||
// added without looking them up
|
||||
basic_json entry(value_t::object);
|
||||
auto& members = *entry.m_data.m_value.object;
|
||||
auto hint = members.end();
|
||||
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
|
||||
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
|
||||
if (value != nullptr)
|
||||
{
|
||||
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
|
||||
}
|
||||
entry.set_parents();
|
||||
result.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
/// @brief append a "replace" operation for @a path with @a value to @a result
|
||||
static void diff_replace(basic_json& result, const string_t& path, const basic_json& value)
|
||||
{
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "replace"}, {"path", path}, {"value", value}
|
||||
});
|
||||
diff_operation(result, "replace", path, &value);
|
||||
}
|
||||
|
||||
/// @brief append a "remove" operation for @a path to @a result
|
||||
static void diff_remove(basic_json& result, const string_t& path)
|
||||
{
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"}, {"path", path}
|
||||
}));
|
||||
diff_operation(result, "remove", path, nullptr);
|
||||
}
|
||||
|
||||
/// @brief append an "add" operation for @a path with @a value to @a result
|
||||
static void diff_add(basic_json& result, const string_t& path, const basic_json& value)
|
||||
{
|
||||
result.push_back(
|
||||
diff_operation(result, "add", path, &value);
|
||||
}
|
||||
|
||||
/// @brief the values in the target of the members of a source object, for the
|
||||
/// objects of up to 16 members without allocating
|
||||
///
|
||||
/// reserve() must be called with the number of values that are added.
|
||||
class diff_target_values
|
||||
{
|
||||
public:
|
||||
diff_target_values() noexcept
|
||||
: inline_items() // NOLINT(readability-redundant-member-init)
|
||||
{}
|
||||
diff_target_values(const diff_target_values&) = delete;
|
||||
diff_target_values& operator=(const diff_target_values&) = delete;
|
||||
diff_target_values(diff_target_values&&) = delete;
|
||||
diff_target_values& operator=(diff_target_values&&) = delete;
|
||||
~diff_target_values() = default;
|
||||
|
||||
void reserve(const std::size_t n)
|
||||
{
|
||||
{"op", "add"}, {"path", path}, {"value", value}
|
||||
});
|
||||
if (n > inline_size)
|
||||
{
|
||||
large.resize(n);
|
||||
items = large.data();
|
||||
}
|
||||
}
|
||||
|
||||
void push_back(const basic_json* value)
|
||||
{
|
||||
items[count++] = value;
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
const basic_json* operator[](const std::size_t i) const noexcept
|
||||
{
|
||||
return items[i];
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t inline_size = 16;
|
||||
/// the values of a small object, left uninitialized: they are written
|
||||
/// before they are read (the user-provided constructor keeps the
|
||||
/// member initialization, which -Weffc++ asks for, from zeroing them)
|
||||
struct uninitialized_items : std::array<const basic_json*, inline_size>
|
||||
{
|
||||
uninitialized_items() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
|
||||
};
|
||||
|
||||
std::size_t count = 0;
|
||||
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
|
||||
uninitialized_items inline_items;
|
||||
const basic_json** items = inline_items.data();
|
||||
};
|
||||
|
||||
/// @brief append "/" and the decimal representation of @a index to @a path
|
||||
static void diff_append_index(string_t& path, std::size_t index)
|
||||
{
|
||||
path.push_back('/');
|
||||
json_pointer::append_index(path, index);
|
||||
}
|
||||
|
||||
/// @brief append "/" and the escaped @a key to @a path
|
||||
static void diff_append_key(string_t& path, const string_t& key)
|
||||
{
|
||||
path.push_back('/');
|
||||
detail::escape_into(path, key);
|
||||
}
|
||||
|
||||
/// @brief the path of the member @a key of the value at @a path
|
||||
static string_t diff_key_path(const string_t& path, const string_t& key)
|
||||
{
|
||||
string_t result;
|
||||
result.reserve(path.size() + key.size() + 1);
|
||||
result.append(path.data(), path.size());
|
||||
diff_append_key(result, key);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief the path of the element @a index of the value at @a path
|
||||
static string_t diff_index_path(const string_t& path, const std::size_t index)
|
||||
{
|
||||
string_t result;
|
||||
result.reserve(path.size() + 8);
|
||||
result.append(path.data(), path.size());
|
||||
diff_append_index(result, index);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief append the "remove" operations for the elements of array
|
||||
@@ -7272,13 +7601,18 @@ public:
|
||||
// in that order avoids the quadratic reinsertion done before
|
||||
for (std::size_t j = source.size(); j > index; --j)
|
||||
{
|
||||
diff_remove(result, detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1)));
|
||||
diff_remove(result, diff_index_path(path, j - 1));
|
||||
}
|
||||
|
||||
// add other remaining elements
|
||||
for (std::size_t i = source.size(); i < target.size(); ++i)
|
||||
if (source.size() < target.size())
|
||||
{
|
||||
diff_add(result, detail::concat<string_t>(path, "/-"), target[i]);
|
||||
string_t tail_path = path;
|
||||
tail_path.append("/-", 2);
|
||||
for (std::size_t i = source.size(); i < target.size(); ++i)
|
||||
{
|
||||
diff_add(result, tail_path, target[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7287,7 +7621,7 @@ public:
|
||||
{
|
||||
for (const auto& it : added)
|
||||
{
|
||||
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
||||
diff_add(result, diff_key_path(path, it.key()), it.value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7303,15 +7637,31 @@ public:
|
||||
operations that remove every member of @a source and add every member of
|
||||
@a target to @a result, and returns false.
|
||||
*/
|
||||
template<typename TargetValues>
|
||||
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path, std::vector<const basic_json*>& target_values,
|
||||
const string_t& path, TargetValues& target_values,
|
||||
std::vector<const_iterator>& added)
|
||||
{
|
||||
// first pass: one lookup per source key
|
||||
// Objects of the same size usually have the same keys, in the same
|
||||
// order: walk them together as long as that holds, which takes no lookups.
|
||||
target_values.reserve(source.size());
|
||||
std::size_t common = 0;
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
bool together = source.size() == target.size();
|
||||
for (auto it = source.cbegin(), partner = target.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
if (together)
|
||||
{
|
||||
if (it.key() == partner.key())
|
||||
{
|
||||
target_values.push_back(&partner.value());
|
||||
++common;
|
||||
++partner;
|
||||
continue;
|
||||
}
|
||||
together = false;
|
||||
}
|
||||
|
||||
const auto found = target.find(it.key());
|
||||
if (found != target.cend())
|
||||
{
|
||||
@@ -7324,6 +7674,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// the same keys in the same order
|
||||
if (together)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// second pass: find the keys only target has. An object type that
|
||||
// keeps its members in insertion order, such as the one backing
|
||||
// ordered_json, also needs the common keys to be in the same order
|
||||
@@ -7389,6 +7745,13 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// equal objects, whose members are in a different order, need no
|
||||
// operations
|
||||
if (source == target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// slow path: the common keys are in a different relative
|
||||
// order in source and target (only possible for a
|
||||
// reorderable object_t like ordered_map). Building a
|
||||
@@ -7405,7 +7768,7 @@ public:
|
||||
// it moves it to the end, fixing its position.
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key())));
|
||||
diff_remove(result, diff_key_path(path, it.key()));
|
||||
}
|
||||
|
||||
// add every key that is either common (just removed
|
||||
@@ -7414,7 +7777,7 @@ public:
|
||||
// target exactly
|
||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||
{
|
||||
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
||||
diff_add(result, diff_key_path(path, it.key()), it.value());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -7423,6 +7786,9 @@ public:
|
||||
@brief @ref diff, for values at nesting level @a depth, appending the
|
||||
operations to @a result
|
||||
|
||||
@a path is the path of the values; it is a buffer that is extended and
|
||||
shortened again below, and is the same when the function returns.
|
||||
|
||||
Diffing two arrays or objects calls this function again, once per nesting
|
||||
level, so values nested deeply enough used to exhaust the call stack and
|
||||
terminate the process. The descent is bounded here: once @ref
|
||||
@@ -7430,7 +7796,7 @@ public:
|
||||
diff_iteratively diffs what is left without the call stack.
|
||||
*/
|
||||
static void diff_recursively(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path, const std::size_t depth)
|
||||
string_t& path, const std::size_t depth)
|
||||
{
|
||||
// if the values are the same, there is nothing to do
|
||||
if (source == target)
|
||||
@@ -7442,9 +7808,9 @@ public:
|
||||
}
|
||||
|
||||
/// @brief @ref diff_recursively for values known to be unequal; callers
|
||||
/// compare first so they build no path for equal ones
|
||||
/// compare first so they extend the path only for unequal ones
|
||||
static void diff_unequal(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path, const std::size_t depth)
|
||||
string_t& path, const std::size_t depth)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
@@ -7463,6 +7829,8 @@ public:
|
||||
{
|
||||
case value_t::array:
|
||||
{
|
||||
const std::size_t path_length = path.size();
|
||||
|
||||
// first pass: traverse common elements
|
||||
std::size_t i = 0;
|
||||
while (i < source.size() && i < target.size())
|
||||
@@ -7470,7 +7838,9 @@ public:
|
||||
// recursive call to compare array values at index i
|
||||
if (!(source[i] == target[i]))
|
||||
{
|
||||
diff_unequal(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
|
||||
diff_append_index(path, i);
|
||||
diff_unequal(result, source[i], target[i], path, depth + 1);
|
||||
path.resize(path_length);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
@@ -7483,7 +7853,7 @@ public:
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
std::vector<const basic_json*> target_values;
|
||||
diff_target_values target_values;
|
||||
std::vector<const_iterator> added;
|
||||
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||
{
|
||||
@@ -7492,6 +7862,7 @@ public:
|
||||
// lookup is needed. Deleted keys are interleaved here
|
||||
// too, in source's original order, to match the
|
||||
// historical (pre-reordering-aware) output order.
|
||||
const std::size_t path_length = path.size();
|
||||
std::size_t i = 0;
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
|
||||
{
|
||||
@@ -7499,13 +7870,15 @@ public:
|
||||
{
|
||||
if (!(it.value() == *target_values[i]))
|
||||
{
|
||||
diff_unequal(result, it.value(), *target_values[i], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
|
||||
diff_append_key(path, it.key());
|
||||
diff_unequal(result, it.value(), *target_values[i], path, depth + 1);
|
||||
path.resize(path_length);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// found a key that is not in target -> remove it
|
||||
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key())));
|
||||
diff_remove(result, diff_key_path(path, it.key()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7636,7 +8009,7 @@ public:
|
||||
if (i < source_array.size() && i < target_array.size())
|
||||
{
|
||||
++stack.back().index;
|
||||
detail::concat_into(current_path, '/', detail::to_string<string_t>(i));
|
||||
diff_append_index(current_path, i);
|
||||
enter(source_array[i], target_array[i]);
|
||||
if (stack.size() == depth)
|
||||
{
|
||||
@@ -7659,7 +8032,7 @@ public:
|
||||
if (target_value_ptr != nullptr)
|
||||
{
|
||||
const basic_json& target_value = *target_value_ptr;
|
||||
detail::concat_into(current_path, '/', detail::escape(it.key()));
|
||||
diff_append_key(current_path, it.key());
|
||||
enter(it.value(), target_value);
|
||||
if (stack.size() == depth)
|
||||
{
|
||||
@@ -7669,7 +8042,7 @@ public:
|
||||
else
|
||||
{
|
||||
// found a key that is not in target -> remove it
|
||||
diff_remove(result, detail::concat<string_t>(current_path, '/', detail::escape(it.key())));
|
||||
diff_remove(result, diff_key_path(current_path, it.key()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <algorithm> // all_of, min
|
||||
#include <array> // array
|
||||
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <istream> // istream // IWYU pragma: keep
|
||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||
@@ -71,6 +71,7 @@
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/edit.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/image.hpp>
|
||||
#include <nlohmann/detail/view/input.hpp>
|
||||
#include <nlohmann/detail/view/iterator.hpp>
|
||||
#include <nlohmann/detail/view/lookup.hpp>
|
||||
@@ -378,7 +379,7 @@ class basic_json_view
|
||||
/// 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 >
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
|
||||
T value(string_view_t key, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
@@ -397,7 +398,7 @@ class basic_json_view
|
||||
/// 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 >
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
|
||||
T value(const json_pointer& ptr, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
@@ -622,8 +623,10 @@ class basic_json_view
|
||||
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;
|
||||
// the compact text is about as long as the source text of the value;
|
||||
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||
// the buffer just before the end
|
||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||
return out;
|
||||
}
|
||||
@@ -747,8 +750,10 @@ class basic_json_view
|
||||
{
|
||||
if (editable() && m_doc->edits != nullptr)
|
||||
{
|
||||
// positions of moved and new values are not source offsets
|
||||
return m_node == m_doc->nodes ? m_doc->size + m_doc->edits->text_used : 64;
|
||||
// positions of moved and new values are not source offsets; the root
|
||||
// (the first node) spans the source text and every edit
|
||||
const bool is_root = m_node == m_doc->nodes;
|
||||
return is_root ? m_doc->size + m_doc->edits->text_used : 64;
|
||||
}
|
||||
const node* const end = m_doc->nodes + m_doc->node_count;
|
||||
if ((m_node->flags & detail::view::node_flags::storage) != 0)
|
||||
@@ -795,7 +800,7 @@ class basic_json_view
|
||||
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 >
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_bool_arithmetic<T>::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))
|
||||
@@ -1026,7 +1031,7 @@ class basic_json_document
|
||||
/// whether the document holds its own copy of the text
|
||||
bool owns_source() const noexcept
|
||||
{
|
||||
return m_data && !m_data->owned.empty() && m_data->src == m_data->owned.data();
|
||||
return m_data && ((!m_data->owned.empty() && m_data->src == m_data->owned.data()) || !m_data->owned_image.empty());
|
||||
}
|
||||
|
||||
/// number of index nodes (values plus object keys)
|
||||
@@ -1035,7 +1040,7 @@ class basic_json_document
|
||||
return m_data ? m_data->node_count : 0;
|
||||
}
|
||||
|
||||
/// bytes held by the document (index, decoded strings, owned text)
|
||||
/// bytes held by the document (index, decoded strings, and owned text or image)
|
||||
std::size_t memory_usage() const noexcept
|
||||
{
|
||||
if (!m_data)
|
||||
@@ -1044,7 +1049,7 @@ class basic_json_document
|
||||
}
|
||||
return sizeof(document_data) + (m_data->inline_capacity * sizeof(detail::view::node))
|
||||
+ (m_data->nodes != m_data->inline_nodes ? m_data->node_capacity * sizeof(detail::view::node) : 0)
|
||||
+ m_data->arena.capacity() + m_data->owned.capacity()
|
||||
+ m_data->arena.capacity() + m_data->owned.capacity() + m_data->owned_image.capacity()
|
||||
+ (m_data->indexes.capacity() * sizeof(document_data::object_index)) + (m_data->index_slots.capacity() * sizeof(std::uint32_t))
|
||||
+ (m_data->large_objects.capacity() * sizeof(std::uint32_t))
|
||||
+ (m_data->edits != nullptr ? m_data->edits->bytes : 0);
|
||||
@@ -1064,8 +1069,10 @@ class basic_json_document
|
||||
|
||||
// allocate everything first, so that an exception leaves the document
|
||||
// unchanged
|
||||
// (the decoded strings of a loaded image stay in the image)
|
||||
const bool arena_in_use = d.base[1] == d.arena.data();
|
||||
const bool shrink_arena = d.arena.capacity() > d.arena.size();
|
||||
std::vector<char> arena(shrink_arena ? d.arena : std::vector<char>());
|
||||
std::vector<char> arena(shrink_arena && arena_in_use ? d.arena : std::vector<char>());
|
||||
// (edits link to the nodes of the index, which then stays in place)
|
||||
const bool shrink_nodes = d.nodes != d.inline_nodes && d.node_count != d.node_capacity && d.edits == nullptr;
|
||||
const bool into_header = d.node_count <= d.inline_capacity;
|
||||
@@ -1086,7 +1093,10 @@ class basic_json_document
|
||||
if (shrink_arena)
|
||||
{
|
||||
d.arena.swap(arena);
|
||||
d.base[1] = d.arena.data();
|
||||
if (arena_in_use)
|
||||
{
|
||||
d.base[1] = d.arena.data();
|
||||
}
|
||||
}
|
||||
if (d.indexes.capacity() > d.indexes.size())
|
||||
{
|
||||
@@ -1098,6 +1108,55 @@ class basic_json_document
|
||||
}
|
||||
}
|
||||
|
||||
////////////
|
||||
// images //
|
||||
////////////
|
||||
|
||||
/// how load() checks an image (full, bounds, or none)
|
||||
using image_check = detail::view::image_check;
|
||||
|
||||
/// The document as an image that load() reads without parsing: the node
|
||||
/// index, the text, and the decoded strings. An edited document is
|
||||
/// written in its current state (floats that are not finite become null,
|
||||
/// as in dump()).
|
||||
std::vector<std::uint8_t> save() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||
{
|
||||
detail::view::throw_type_error(320, "cannot save a discarded json_document");
|
||||
}
|
||||
return detail::view::save_image(*m_data);
|
||||
}
|
||||
|
||||
/// Read an image written by save(). The image is borrowed: it must stay
|
||||
/// alive and unchanged while the document is used.
|
||||
NLOHMANN_VIEW_NODISCARD
|
||||
static basic_json_document load(const std::uint8_t* image, std::size_t size, const image_check check = image_check::full)
|
||||
{
|
||||
basic_json_document d;
|
||||
d.ensure_data(nullptr, 0);
|
||||
detail::view::load_image(*d.m_data, image, size, check);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// read an image (borrowed)
|
||||
NLOHMANN_VIEW_NODISCARD
|
||||
static basic_json_document load(const std::vector<std::uint8_t>& image, const image_check check = image_check::full)
|
||||
{
|
||||
return load(image.data(), image.size(), check);
|
||||
}
|
||||
|
||||
/// read an image and keep it (no copy)
|
||||
NLOHMANN_VIEW_NODISCARD
|
||||
static basic_json_document load(std::vector<std::uint8_t>&& image, const image_check check = image_check::full)
|
||||
{
|
||||
basic_json_document d;
|
||||
d.ensure_data(nullptr, 0);
|
||||
d.m_data->owned_image = std::move(image);
|
||||
detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check);
|
||||
return d;
|
||||
}
|
||||
|
||||
///////////
|
||||
// edits //
|
||||
///////////
|
||||
@@ -1129,7 +1188,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
/// assign an existing array element; returns a view of it
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
view_type set(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().set(array, index(idx), std::forward<V>(value));
|
||||
@@ -1151,14 +1210,14 @@ class basic_json_document
|
||||
const auto& token = ptr.back();
|
||||
if (parent.is_null())
|
||||
{
|
||||
const bool digits = std::all_of(token.begin(), token.end(), [](const char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
});
|
||||
if (token == "-")
|
||||
{
|
||||
return push_back(parent, std::forward<V>(value));
|
||||
}
|
||||
const bool digits = std::all_of(token.begin(), token.end(), [](const char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
});
|
||||
if (digits)
|
||||
{
|
||||
// (an invalid index is an error before the parent changes)
|
||||
@@ -1196,7 +1255,7 @@ class basic_json_document
|
||||
|
||||
/// insert into an array before position idx (idx <= size()); returns a
|
||||
/// view of the new element
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
view_type insert(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().insert(array, index(idx), std::forward<V>(value));
|
||||
@@ -1209,7 +1268,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
/// remove an array element
|
||||
template < typename I, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
void erase(view_type array, I idx)
|
||||
{
|
||||
editor().erase(array, index(idx));
|
||||
@@ -1332,6 +1391,7 @@ class basic_json_document
|
||||
{
|
||||
d.owned.clear();
|
||||
}
|
||||
d.owned_image.clear();
|
||||
d.src = src;
|
||||
d.size = size;
|
||||
d.node_count = 0;
|
||||
@@ -1356,6 +1416,7 @@ class basic_json_document
|
||||
{
|
||||
d.base[0] = d.src;
|
||||
d.base[1] = d.arena.data();
|
||||
d.arena_size = d.arena.size();
|
||||
detail::view::build_object_indexes(d);
|
||||
std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while parsing)
|
||||
d.discarded = false;
|
||||
|
||||
Generated
+5328
-143264
File diff suppressed because it is too large.
Load diff
Generated
+6078
-1025
File diff suppressed because it is too large.
Load diff
Generated
+32
-3
@@ -71,6 +71,14 @@
|
||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -119,6 +127,24 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN _dsbs
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO _dufio
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||
#endif
|
||||
|
||||
#ifdef JSON_NO_SIMD
|
||||
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD _ns
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||
#else
|
||||
@@ -136,9 +162,9 @@
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l, m) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
@@ -150,6 +176,9 @@
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||
NLOHMANN_JSON_ABI_TAG_NO_SIMD, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||
|
||||
|
||||
Generated
+1756
-269
File diff suppressed because it is too large.
Load diff
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user