mirror of
https://github.com/nlohmann/json.git
synced 2026-08-08 18:23:18 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a13902a33f | ||
|
|
c021a09b08 | ||
|
|
e4aaf46d38 | ||
|
|
5bc24e876b | ||
|
|
da7b9bdb3d | ||
|
|
634f49bc5b |
@@ -38,14 +38,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||||
with:
|
with:
|
||||||
languages: c-cpp
|
languages: c-cpp
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||||
|
|||||||
@@ -43,6 +43,6 @@ jobs:
|
|||||||
output: 'flawfinder_results.sarif'
|
output: 'flawfinder_results.sarif'
|
||||||
|
|
||||||
- name: Upload analysis results to GitHub Security tab
|
- name: Upload analysis results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||||
|
|||||||
@@ -76,6 +76,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload SARIF file generated in previous step
|
# Upload SARIF file generated in previous step
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||||
with:
|
with:
|
||||||
sarif_file: semgrep.sarif
|
sarif_file: semgrep.sarif
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -184,7 +184,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -202,7 +202,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -212,14 +212,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', 'latest']
|
||||||
container: silkeh/clang:${{ matrix.compiler }}
|
container: silkeh/clang:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Set env FORCE_STDCPPFS_FLAG for clang 7 / 8 / 9 / 10
|
- name: Set env FORCE_STDCPPFS_FLAG for clang 7 / 8 / 9 / 10
|
||||||
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
||||||
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
||||||
@@ -239,7 +239,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -259,7 +259,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build with libc++
|
- name: Build with libc++
|
||||||
@@ -286,7 +286,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -306,7 +306,7 @@ jobs:
|
|||||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||||
- name: Get pinned CMake and ninja
|
- name: Get pinned CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
with:
|
with:
|
||||||
cmakeVersion: 4.3.4
|
cmakeVersion: 4.3.4
|
||||||
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
||||||
@@ -332,7 +332,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -347,7 +347,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -359,7 +359,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -379,7 +379,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
- name: Set extra CXX_FLAGS for latest std_version
|
- name: Set extra CXX_FLAGS for latest std_version
|
||||||
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
|
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
|
||||||
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
|
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
|
||||||
@@ -153,16 +153,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platform: x64
|
platform: x64
|
||||||
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
|
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
|
||||||
# CMAKE_CXX_FLAGS_DEBUG is overridden to drop the default -g: linking
|
|
||||||
# test-regression2_cpp20 intermittently fails with "relocation truncated
|
|
||||||
# to fit: IMAGE_REL_AMD64_SECREL against `.debug_line'" because the
|
|
||||||
# MinGW linker cannot relocate the debug sections this test produces.
|
|
||||||
# The tests are only built and run here, so the debug info is not used.
|
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build ^
|
run: cmake -S . -B build ^
|
||||||
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
||||||
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
|
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
|
||||||
-DCMAKE_CXX_FLAGS_DEBUG="-g0" ^
|
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
|
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
|
||||||
-G"MinGW Makefiles" ^
|
-G"MinGW Makefiles" ^
|
||||||
-DCMAKE_BUILD_TYPE=Debug ^
|
-DCMAKE_BUILD_TYPE=Debug ^
|
||||||
@@ -199,7 +193,7 @@ jobs:
|
|||||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||||
- name: Get pinned CMake and ninja
|
- name: Get pinned CMake and ninja
|
||||||
uses: lukka/get-cmake@4a7d025fc60f00db0c7b44ebf783d19b52444830 # v4.4.1
|
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
||||||
with:
|
with:
|
||||||
cmakeVersion: 4.3.4
|
cmakeVersion: 4.3.4
|
||||||
- name: Run CMake (Debug)
|
- name: Run CMake (Debug)
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
- [Specializing enum conversion](#specializing-enum-conversion)
|
- [Specializing enum conversion](#specializing-enum-conversion)
|
||||||
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata)
|
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata)
|
||||||
- [Customers](#customers)
|
- [Customers](#customers)
|
||||||
- [Ecosystem](#ecosystem)
|
|
||||||
- [Supported compilers](#supported-compilers)
|
- [Supported compilers](#supported-compilers)
|
||||||
- [Integration](#integration)
|
- [Integration](#integration)
|
||||||
- [CMake](#cmake)
|
- [CMake](#cmake)
|
||||||
@@ -1187,11 +1186,6 @@ The library is used in multiple projects, applications, operating systems, etc.
|
|||||||
|
|
||||||
[](https://json.nlohmann.me/home/customers/)
|
[](https://json.nlohmann.me/home/customers/)
|
||||||
|
|
||||||
## Ecosystem
|
|
||||||
|
|
||||||
Beyond projects that use the library, there are third-party projects that build on top of it - schema validators,
|
|
||||||
language bindings, format converters, and the like. See the curated [Ecosystem](https://json.nlohmann.me/community/ecosystem/) page.
|
|
||||||
|
|
||||||
## Supported compilers
|
## Supported compilers
|
||||||
|
|
||||||
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
[`input_format_t`](input_format_t.md) for more information
|
[`input_format_t`](input_format_t.md) for more information
|
||||||
|
|
||||||
`strict` (in)
|
`strict` (in)
|
||||||
: whether the input has to be consumed completely (optional, `#!cpp true` by default)
|
: whether the input has to be consumed completely (optional, `#!cpp true` by default); when `#!cpp false` and the
|
||||||
|
input is a `#!cpp std::istream`, the stream is left positioned right after the parsed value
|
||||||
|
|
||||||
`ignore_comments` (in)
|
`ignore_comments` (in)
|
||||||
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
||||||
@@ -136,6 +137,8 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
- Changed in version 4.0.0 to leave a `#!cpp std::istream` positioned right after the parsed value when `strict` is
|
||||||
|
`#!cpp false`; see [`operator>>`](../operator_gtgt.md#notes).
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -33,41 +33,26 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||||
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
|
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
|
||||||
|
|
||||||
`operator>>` parses exactly one JSON value, so it can be called repeatedly to read a sequence of concatenated JSON
|
`operator>>` parses exactly one JSON value and leaves the stream positioned right after it, so it can be called
|
||||||
values from the same stream:
|
repeatedly to read a sequence of concatenated JSON values from the same stream:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
json j1, j2;
|
std::istringstream input("1true[2]");
|
||||||
input >> j1; // parses the first value
|
json j1, j2, j3;
|
||||||
input >> j2; // parses the next value
|
input >> j1; // j1 == 1, stream now positioned right after it
|
||||||
|
input >> j2; // j2 == true
|
||||||
|
input >> j3; // j3 == [2]
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! warning "A number must be followed by whitespace"
|
!!! note "Changed behavior for numbers"
|
||||||
|
|
||||||
A number is only terminated by the character that follows it. That character is read from the stream to detect the
|
A number is the only value whose end can be detected solely by reading the character that follows it. Up to
|
||||||
end of the number, and it is **not** put back. When a value that is a number is immediately followed by the next
|
version 3.13.0 that character was consumed and not put back, so the stream was left one byte too far whenever a
|
||||||
value, the first character of that next value is lost:
|
number was immediately followed by another value: reading `1true` yielded `1` and left the stream at `rue`.
|
||||||
|
Values had to be separated by whitespace to work around this.
|
||||||
|
|
||||||
```cpp
|
The terminating character is now only looked at and left in the stream, so no separator is required. Code that
|
||||||
std::istringstream input("1true");
|
relied on the extra byte being swallowed will observe it again.
|
||||||
json j1, j2;
|
|
||||||
input >> j1; // j1 == 1
|
|
||||||
input >> j2; // throws parse_error.101: the stream now starts at "rue"
|
|
||||||
```
|
|
||||||
|
|
||||||
Separating the values with whitespace avoids this, because the character that is eaten is then the separator:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
std::istringstream input("1 true");
|
|
||||||
json j1, j2;
|
|
||||||
input >> j1; // j1 == 1
|
|
||||||
input >> j2; // j2 == true
|
|
||||||
```
|
|
||||||
|
|
||||||
Only numbers are affected. Values ending in a self-delimiting character do not read past themselves, so
|
|
||||||
`truefalse`, `[1][2]`, `{"a":1}{"b":2}`, and `"a""b"` can be read back to back without a separator.
|
|
||||||
|
|
||||||
This is tracked in [#5340](https://github.com/nlohmann/json/issues/5340).
|
|
||||||
|
|
||||||
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
|
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
|
||||||
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
||||||
@@ -102,3 +87,5 @@ Note that reading concatenated values does **not** work for [JSON Lines](../feat
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 1.0.0.
|
- Added in version 1.0.0.
|
||||||
|
- Changed in version 4.0.0 to leave the character that terminates a number in the stream, so that the stream is
|
||||||
|
positioned right after the parsed value for every value type.
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
# Ecosystem
|
|
||||||
|
|
||||||
The projects below build on top of `nlohmann::json` rather than merely using it - schema validators, language
|
|
||||||
bindings, format converters, and similar building blocks. The list is not exhaustive, and is curated rather than
|
|
||||||
automatically generated. If you maintain or know of a project that belongs here,
|
|
||||||
[please let me know](mailto:mail@nlohmann.me).
|
|
||||||
|
|
||||||
For products, applications, and organizations that use the library, see [Customers](../home/customers.md) instead.
|
|
||||||
|
|
||||||
## Schema validation
|
|
||||||
|
|
||||||
- [**json-schema-validator**](https://github.com/pboettch/json-schema-validator), a JSON Schema (draft 7) validator
|
|
||||||
with human-readable error messages
|
|
||||||
|
|
||||||
## Serialization and reflection
|
|
||||||
|
|
||||||
- [**nlohmann_json_reflect**](https://github.com/1261385937/nlohmann_json_reflect), a reflection extension for
|
|
||||||
(de)serializing nested containers-in-structs-in-containers
|
|
||||||
|
|
||||||
## Encodings
|
|
||||||
|
|
||||||
- [**base-encode-decode**](https://github.com/saxonnicholls/base-encode-decode), a header-only Base64/32/16/8/4/2
|
|
||||||
(and DNA/RNA) encoding library, with an adapter that serializes binary data through `nlohmann::json`
|
|
||||||
|
|
||||||
## Language bindings and interop
|
|
||||||
|
|
||||||
- [**pybind11_json**](https://github.com/pybind/pybind11_json), a bidirectional type caster between
|
|
||||||
`nlohmann::json` and Python objects for [pybind11](https://github.com/pybind/pybind11) bindings
|
|
||||||
- [**nanobind_json**](https://github.com/ianhbell/nanobind_json), the same idea for
|
|
||||||
[nanobind](https://github.com/wjakob/nanobind) bindings
|
|
||||||
- [**nlohmann_json_qt**](https://github.com/dpurgin/nlohmann_json_qt), deserialization helpers for Qt types
|
|
||||||
(`QString`, `QUrl`, `QDateTime`, `QVector`, ...) from `nlohmann::json`
|
|
||||||
- [**vulkan2json**](https://github.com/Fadis/vulkan2json), serialization and deserialization of Vulkan API structs
|
|
||||||
|
|
||||||
## Format converters
|
|
||||||
|
|
||||||
- [**tojson**](https://github.com/mircodz/tojson), a header-only converter between YAML/XML documents and
|
|
||||||
`nlohmann::json`
|
|
||||||
- [**json2xml**](https://github.com/testillano/json2xml), a header-only converter from `nlohmann::json` to XML for
|
|
||||||
simple configuration documents
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
# Community
|
# Community
|
||||||
|
|
||||||
- [Ecosystem](ecosystem.md) - third-party projects built on top of this library
|
|
||||||
- [Code of Conduct](code_of_conduct.md) - the rules and norms of this project
|
- [Code of Conduct](code_of_conduct.md) - the rules and norms of this project
|
||||||
- [Contribution Guidelines](contribution_guidelines.md) - guidelines how to contribute to this project
|
- [Contribution Guidelines](contribution_guidelines.md) - guidelines how to contribute to this project
|
||||||
- [Governance](governance.md) - the governance model of this project
|
- [Governance](governance.md) - the governance model of this project
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
| Clang 20.1.1 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 20.1.1 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 20.1.8 with GNU-like command-line | x86_64 | Windows Server 2022 (Build 20348) | GitHub |
|
| Clang 20.1.8 with GNU-like command-line | x86_64 | Windows Server 2022 (Build 20348) | GitHub |
|
||||||
| Clang 21.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 21.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 22.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
|
||||||
| CUDA 11.8.0 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
| CUDA 11.8.0 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||||
| CUDA 12.1.1 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
| CUDA 12.1.1 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||||
| CUDA 12.6.3 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
| CUDA 12.6.3 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||||
|
|||||||
@@ -116,19 +116,9 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
```
|
```
|
||||||
|
|
||||||
Likewise, when a JSON object in the above form is serialized using
|
Likewise, when a JSON object in the above form is serialized using
|
||||||
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. When
|
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. The
|
||||||
the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two integers with one being 1, a
|
only exception is, that when the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two
|
||||||
regular 1-D optimized array is generated instead.
|
integers with one being 1, a regular 1-D optimized array is generated.
|
||||||
|
|
||||||
An object is only converted if the annotation actually describes a packed array; otherwise it is serialized as a
|
|
||||||
regular JSON object. This requires all of the following:
|
|
||||||
|
|
||||||
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
|
|
||||||
`double`, `char`, or `byte`,
|
|
||||||
- every entry of `"_ArraySize_"` is a non-negative integer, and their product is representable as a `std::size_t`,
|
|
||||||
- `"_ArrayData_"` holds exactly that many elements, and
|
|
||||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
|
|
||||||
`single` and `double`, an integer otherwise).
|
|
||||||
|
|
||||||
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
||||||
array input to a BJData ND-array.
|
array input to a BJData ND-array.
|
||||||
|
|||||||
@@ -308,7 +308,6 @@ nav:
|
|||||||
- 'NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH': api/macros/nlohmann_json_version_major.md
|
- 'NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH': api/macros/nlohmann_json_version_major.md
|
||||||
- Community:
|
- Community:
|
||||||
- community/index.md
|
- community/index.md
|
||||||
- community/ecosystem.md
|
|
||||||
- "Code of Conduct": community/code_of_conduct.md
|
- "Code of Conduct": community/code_of_conduct.md
|
||||||
- community/contribution_guidelines.md
|
- community/contribution_guidelines.md
|
||||||
- community/quality_assurance.md
|
- community/quality_assurance.md
|
||||||
|
|||||||
@@ -465,6 +465,15 @@ class binary_reader
|
|||||||
// CBOR //
|
// CBOR //
|
||||||
//////////
|
//////////
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@param[in] get_char whether a new character should be retrieved from the
|
||||||
|
input (true) or whether the last read character should
|
||||||
|
be considered instead (false)
|
||||||
|
@param[in] tag_handler how CBOR tags should be treated
|
||||||
|
|
||||||
|
@return whether a valid CBOR value was passed to the SAX parser
|
||||||
|
*/
|
||||||
|
|
||||||
template<typename NumberType>
|
template<typename NumberType>
|
||||||
bool get_cbor_negative_integer()
|
bool get_cbor_negative_integer()
|
||||||
{
|
{
|
||||||
@@ -483,14 +492,6 @@ class binary_reader
|
|||||||
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
|
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@param[in] get_char whether a new character should be retrieved from the
|
|
||||||
input (true) or whether the last read character should
|
|
||||||
be considered instead (false)
|
|
||||||
@param[in] tag_handler how CBOR tags should be treated
|
|
||||||
|
|
||||||
@return whether a valid CBOR value was passed to the SAX parser
|
|
||||||
*/
|
|
||||||
bool parse_cbor_internal(const bool get_char,
|
bool parse_cbor_internal(const bool get_char,
|
||||||
const cbor_tag_handler_t tag_handler)
|
const cbor_tag_handler_t tag_handler)
|
||||||
{
|
{
|
||||||
@@ -1987,11 +1988,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
if (get_char)
|
if (get_char)
|
||||||
{
|
{
|
||||||
// no get_ignore_noop() here: the byte read next must be a string
|
get(); // TODO(niels): may we ignore N here?
|
||||||
// length type specification, and a no-op ('N') is not valid in
|
|
||||||
// that position. No-ops at positions where a value may appear are
|
|
||||||
// already consumed by the callers via get_ignore_noop().
|
|
||||||
get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value")))
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value")))
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ class input_stream_adapter
|
|||||||
// maintain ifstream flags, except eof
|
// maintain ifstream flags, except eof
|
||||||
if (is != nullptr)
|
if (is != nullptr)
|
||||||
{
|
{
|
||||||
|
// consume the character last returned by get_character() unless it
|
||||||
|
// was given back with release_lookahead()
|
||||||
|
commit_lookahead();
|
||||||
is->clear(is->rdstate() & std::ios::eofbit);
|
is->clear(is->rdstate() & std::ios::eofbit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,29 +118,60 @@ class input_stream_adapter
|
|||||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||||
|
|
||||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||||
: is(rhs.is), sb(rhs.sb)
|
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||||
{
|
{
|
||||||
rhs.is = nullptr;
|
rhs.is = nullptr;
|
||||||
rhs.sb = nullptr;
|
rhs.sb = nullptr;
|
||||||
|
rhs.lookahead = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether the character last returned by get_character() can be given back
|
||||||
|
// to the input with release_lookahead().
|
||||||
|
static constexpr bool supports_lookahead = true;
|
||||||
|
|
||||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
// 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
|
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||||
|
//
|
||||||
|
// The character is peeked rather than consumed: it is only stepped over
|
||||||
|
// once the next character is requested, or when the adapter is destroyed.
|
||||||
|
// Until then, release_lookahead() can leave it in the input.
|
||||||
std::char_traits<char>::int_type get_character()
|
std::char_traits<char>::int_type get_character()
|
||||||
{
|
{
|
||||||
auto res = sb->sbumpc();
|
if (lookahead)
|
||||||
|
{
|
||||||
|
// step over the character returned by the previous call
|
||||||
|
sb->sbumpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = sb->sgetc();
|
||||||
// set eof manually, as we don't use the istream interface.
|
// set eof manually, as we don't use the istream interface.
|
||||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||||
{
|
{
|
||||||
|
// there is nothing to step over next time
|
||||||
|
lookahead = false;
|
||||||
is->clear(is->rdstate() | std::ios::eofbit);
|
is->clear(is->rdstate() | std::ios::eofbit);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lookahead = true;
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave the character last returned by get_character() in the input, so
|
||||||
|
// that the next read from the stream - by this adapter or by the caller
|
||||||
|
// once parsing is done - sees it again. Unlike putting a consumed
|
||||||
|
// character back, this cannot fail.
|
||||||
|
void release_lookahead() noexcept
|
||||||
|
{
|
||||||
|
lookahead = false;
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||||
{
|
{
|
||||||
|
commit_lookahead();
|
||||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
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)))
|
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||||
{
|
{
|
||||||
@@ -147,9 +181,23 @@ class input_stream_adapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Step over the character last returned by get_character(). The character
|
||||||
|
// has already been peeked successfully, so for every streambuf with a get
|
||||||
|
// area this is a pointer increment that cannot fail.
|
||||||
|
void commit_lookahead()
|
||||||
|
{
|
||||||
|
if (lookahead)
|
||||||
|
{
|
||||||
|
lookahead = false;
|
||||||
|
sb->sbumpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// the associated input stream
|
/// the associated input stream
|
||||||
std::istream* is = nullptr;
|
std::istream* is = nullptr;
|
||||||
std::streambuf* sb = nullptr;
|
std::streambuf* sb = nullptr;
|
||||||
|
/// whether get_character() peeked a character that is not consumed yet
|
||||||
|
bool lookahead = false;
|
||||||
};
|
};
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
@@ -345,12 +393,8 @@ struct wide_string_input_helper<BaseInputAdapter, 4>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// A code point above U+10FFFF has no UTF-8 encoding. Passing the
|
// unknown character
|
||||||
// unit through would narrow it to int, where 0xFFFFFFFF becomes
|
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
|
||||||
// char_traits<char>::eof() and would end the input silently, so
|
|
||||||
// emit a byte that is never valid UTF-8 and let the decoder
|
|
||||||
// reject it.
|
|
||||||
utf8_bytes[0] = 0xFF;
|
|
||||||
utf8_bytes_filled = 1;
|
utf8_bytes_filled = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -370,10 +370,8 @@ class json_sax_dom_parser
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
// escape sequences make the token longer than the value it
|
// include the length of the quotes, which is 2
|
||||||
// parses to, so the start position cannot be derived from
|
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
|
||||||
// the value; use the offset the lexer recorded instead
|
|
||||||
v.start_position = m_lexer_ref->get_token_start_position();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -771,10 +769,8 @@ class json_sax_dom_callback_parser
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
// escape sequences make the token longer than the value it
|
// include the length of the quotes, which is 2
|
||||||
// parses to, so the start position cannot be derived from
|
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
|
||||||
// the value; use the offset the lexer recorded instead
|
|
||||||
v.start_position = m_lexer_ref->get_token_start_position();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,24 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect whether an input adapter reads with one character of lookahead that
|
||||||
|
// can be left in the input (see input_stream_adapter::supports_lookahead),
|
||||||
|
// detected like supports_seek above.
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
|
||||||
|
{
|
||||||
|
return InputAdapterType::supports_lookahead;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -146,6 +164,12 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
static constexpr bool lazy_token_string =
|
static constexpr bool lazy_token_string =
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||||
|
|
||||||
|
/// whether a simulated unget can be passed on to the input adapter, which
|
||||||
|
/// then leaves the character in the input; see
|
||||||
|
/// input_adapter_supports_lookahead
|
||||||
|
static constexpr bool can_release_lookahead =
|
||||||
|
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
@@ -1357,11 +1381,6 @@ scan_number_done:
|
|||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
decimal_point_position = std::string::npos;
|
decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
// the first character of the token has already been read, hence the -1
|
|
||||||
token_start_position = position.chars_read_total - 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1461,6 +1480,21 @@ scan_number_done:
|
|||||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||||
|
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
|
||||||
|
|
||||||
|
/// adapter with lookahead: leave the character in the input instead
|
||||||
|
void release_lookahead_impl(std::true_type /*can_release*/)
|
||||||
|
{
|
||||||
|
if (next_unget)
|
||||||
|
{
|
||||||
|
// the character is read from the input again rather than replayed
|
||||||
|
// from current, so the adapter must not step over it
|
||||||
|
next_unget = false;
|
||||||
|
ia.release_lookahead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// seekable adapter: nothing was captured, so nothing to undo
|
/// seekable adapter: nothing was captured, so nothing to undo
|
||||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||||
|
|
||||||
@@ -1524,14 +1558,28 @@ scan_number_done:
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
/*!
|
||||||
/// return the offset of the first character of the last read token; unlike
|
@brief pass a pending simulated unget on to the input
|
||||||
/// the token's parsed value, this accounts for escape sequences
|
|
||||||
constexpr std::size_t get_token_start_position() const noexcept
|
unget() only rewinds the lexer's own bookkeeping, so the character that
|
||||||
|
terminated the last token (e.g. the character after a number) would still
|
||||||
|
be stepped over when the input adapter is done. Callers that hand the
|
||||||
|
input back to the user afterwards - operator>> and non-strict sax_parse -
|
||||||
|
call this once when scanning is done, so that the input is positioned
|
||||||
|
right after the value.
|
||||||
|
|
||||||
|
Adapters without lookahead (see input_adapter_supports_lookahead) are not
|
||||||
|
handed back to the user, so this is a no-op for them.
|
||||||
|
|
||||||
|
Scanning may continue after this call: @a next_unget is cleared, and the
|
||||||
|
character is read from the input again instead of being replayed from
|
||||||
|
@a current. A pending unget of EOF needs no special case, because reaching
|
||||||
|
EOF leaves no lookahead to release.
|
||||||
|
*/
|
||||||
|
void release_lookahead()
|
||||||
{
|
{
|
||||||
return token_start_position;
|
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/// seekable adapter: rebuild the last read token from the input on demand
|
/// 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
|
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||||
@@ -1733,12 +1781,6 @@ scan_number_done:
|
|||||||
/// the last read token on error for seekable adapters (see collect_token_chars)
|
/// the last read token on error for seekable adapters (see collect_token_chars)
|
||||||
std::size_t token_string_start = 0;
|
std::size_t token_string_start = 0;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
/// start offset of the current token within the input, used to report
|
|
||||||
/// diagnostic positions (see reset())
|
|
||||||
std::size_t token_start_position = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// buffer for variable-length tokens (numbers, strings)
|
/// buffer for variable-length tokens (numbers, strings)
|
||||||
string_t token_buffer {};
|
string_t token_buffer {};
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,14 @@ class parser
|
|||||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
|
if (!strict)
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after
|
||||||
|
// the value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// in strict mode, input must be completely read
|
// in strict mode, input must be completely read
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
else if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
sdp.parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -127,8 +133,13 @@ class parser
|
|||||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
|
if (!strict)
|
||||||
|
{
|
||||||
|
// see above
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// in strict mode, input must be completely read
|
// in strict mode, input must be completely read
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
else if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
sdp.parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -165,8 +176,14 @@ class parser
|
|||||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal(sax);
|
||||||
|
|
||||||
|
if (result && !strict)
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after the
|
||||||
|
// value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// strict mode: next byte must be EOF
|
// strict mode: next byte must be EOF
|
||||||
if (result && strict && (get_token() != token_type::end_of_input))
|
else if (result && strict && (get_token() != token_type::end_of_input))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
|
|||||||
@@ -1670,23 +1670,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
len *= static_cast<std::size_t>(el.template get<std::uint64_t>());
|
||||||
// a dimension that does not fit into std::size_t, or a product that
|
|
||||||
// overflows it, would wrap around and could match the size of
|
|
||||||
// _ArrayData_ by accident; the resulting header announces an
|
|
||||||
// element count that no reader can honor (the binary reader rejects
|
|
||||||
// it with out_of_range.408), so encode as a plain object instead
|
|
||||||
const auto dim = el.template get<std::uint64_t>();
|
|
||||||
if (!value_in_range_of<std::size_t>(dim))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const auto dim_size = static_cast<std::size_t>(dim);
|
|
||||||
if (dim_size != 0 && len > (std::numeric_limits<std::size_t>::max)() / dim_size)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
len *= dim_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
key = "_ArrayData_";
|
key = "_ArrayData_";
|
||||||
|
|||||||
@@ -7104,6 +7104,9 @@ class input_stream_adapter
|
|||||||
// maintain ifstream flags, except eof
|
// maintain ifstream flags, except eof
|
||||||
if (is != nullptr)
|
if (is != nullptr)
|
||||||
{
|
{
|
||||||
|
// consume the character last returned by get_character() unless it
|
||||||
|
// was given back with release_lookahead()
|
||||||
|
commit_lookahead();
|
||||||
is->clear(is->rdstate() & std::ios::eofbit);
|
is->clear(is->rdstate() & std::ios::eofbit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7118,29 +7121,60 @@ class input_stream_adapter
|
|||||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||||
|
|
||||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||||
: is(rhs.is), sb(rhs.sb)
|
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||||
{
|
{
|
||||||
rhs.is = nullptr;
|
rhs.is = nullptr;
|
||||||
rhs.sb = nullptr;
|
rhs.sb = nullptr;
|
||||||
|
rhs.lookahead = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether the character last returned by get_character() can be given back
|
||||||
|
// to the input with release_lookahead().
|
||||||
|
static constexpr bool supports_lookahead = true;
|
||||||
|
|
||||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
// 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
|
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||||
|
//
|
||||||
|
// The character is peeked rather than consumed: it is only stepped over
|
||||||
|
// once the next character is requested, or when the adapter is destroyed.
|
||||||
|
// Until then, release_lookahead() can leave it in the input.
|
||||||
std::char_traits<char>::int_type get_character()
|
std::char_traits<char>::int_type get_character()
|
||||||
{
|
{
|
||||||
auto res = sb->sbumpc();
|
if (lookahead)
|
||||||
|
{
|
||||||
|
// step over the character returned by the previous call
|
||||||
|
sb->sbumpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = sb->sgetc();
|
||||||
// set eof manually, as we don't use the istream interface.
|
// set eof manually, as we don't use the istream interface.
|
||||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||||
{
|
{
|
||||||
|
// there is nothing to step over next time
|
||||||
|
lookahead = false;
|
||||||
is->clear(is->rdstate() | std::ios::eofbit);
|
is->clear(is->rdstate() | std::ios::eofbit);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lookahead = true;
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave the character last returned by get_character() in the input, so
|
||||||
|
// that the next read from the stream - by this adapter or by the caller
|
||||||
|
// once parsing is done - sees it again. Unlike putting a consumed
|
||||||
|
// character back, this cannot fail.
|
||||||
|
void release_lookahead() noexcept
|
||||||
|
{
|
||||||
|
lookahead = false;
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||||
{
|
{
|
||||||
|
commit_lookahead();
|
||||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
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)))
|
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||||
{
|
{
|
||||||
@@ -7150,9 +7184,23 @@ class input_stream_adapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Step over the character last returned by get_character(). The character
|
||||||
|
// has already been peeked successfully, so for every streambuf with a get
|
||||||
|
// area this is a pointer increment that cannot fail.
|
||||||
|
void commit_lookahead()
|
||||||
|
{
|
||||||
|
if (lookahead)
|
||||||
|
{
|
||||||
|
lookahead = false;
|
||||||
|
sb->sbumpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// the associated input stream
|
/// the associated input stream
|
||||||
std::istream* is = nullptr;
|
std::istream* is = nullptr;
|
||||||
std::streambuf* sb = nullptr;
|
std::streambuf* sb = nullptr;
|
||||||
|
/// whether get_character() peeked a character that is not consumed yet
|
||||||
|
bool lookahead = false;
|
||||||
};
|
};
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
@@ -7348,12 +7396,8 @@ struct wide_string_input_helper<BaseInputAdapter, 4>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// A code point above U+10FFFF has no UTF-8 encoding. Passing the
|
// unknown character
|
||||||
// unit through would narrow it to int, where 0xFFFFFFFF becomes
|
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
|
||||||
// char_traits<char>::eof() and would end the input silently, so
|
|
||||||
// emit a byte that is never valid UTF-8 and let the decoder
|
|
||||||
// reject it.
|
|
||||||
utf8_bytes[0] = 0xFF;
|
|
||||||
utf8_bytes_filled = 1;
|
utf8_bytes_filled = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7843,6 +7887,24 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect whether an input adapter reads with one character of lookahead that
|
||||||
|
// can be left in the input (see input_stream_adapter::supports_lookahead),
|
||||||
|
// detected like supports_seek above.
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
|
||||||
|
{
|
||||||
|
return InputAdapterType::supports_lookahead;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -7864,6 +7926,12 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
static constexpr bool lazy_token_string =
|
static constexpr bool lazy_token_string =
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||||
|
|
||||||
|
/// whether a simulated unget can be passed on to the input adapter, which
|
||||||
|
/// then leaves the character in the input; see
|
||||||
|
/// input_adapter_supports_lookahead
|
||||||
|
static constexpr bool can_release_lookahead =
|
||||||
|
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
@@ -9075,11 +9143,6 @@ scan_number_done:
|
|||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
decimal_point_position = std::string::npos;
|
decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
// the first character of the token has already been read, hence the -1
|
|
||||||
token_start_position = position.chars_read_total - 1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9179,6 +9242,21 @@ scan_number_done:
|
|||||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||||
|
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
|
||||||
|
|
||||||
|
/// adapter with lookahead: leave the character in the input instead
|
||||||
|
void release_lookahead_impl(std::true_type /*can_release*/)
|
||||||
|
{
|
||||||
|
if (next_unget)
|
||||||
|
{
|
||||||
|
// the character is read from the input again rather than replayed
|
||||||
|
// from current, so the adapter must not step over it
|
||||||
|
next_unget = false;
|
||||||
|
ia.release_lookahead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// seekable adapter: nothing was captured, so nothing to undo
|
/// seekable adapter: nothing was captured, so nothing to undo
|
||||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||||
|
|
||||||
@@ -9242,14 +9320,28 @@ scan_number_done:
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
/*!
|
||||||
/// return the offset of the first character of the last read token; unlike
|
@brief pass a pending simulated unget on to the input
|
||||||
/// the token's parsed value, this accounts for escape sequences
|
|
||||||
constexpr std::size_t get_token_start_position() const noexcept
|
unget() only rewinds the lexer's own bookkeeping, so the character that
|
||||||
|
terminated the last token (e.g. the character after a number) would still
|
||||||
|
be stepped over when the input adapter is done. Callers that hand the
|
||||||
|
input back to the user afterwards - operator>> and non-strict sax_parse -
|
||||||
|
call this once when scanning is done, so that the input is positioned
|
||||||
|
right after the value.
|
||||||
|
|
||||||
|
Adapters without lookahead (see input_adapter_supports_lookahead) are not
|
||||||
|
handed back to the user, so this is a no-op for them.
|
||||||
|
|
||||||
|
Scanning may continue after this call: @a next_unget is cleared, and the
|
||||||
|
character is read from the input again instead of being replayed from
|
||||||
|
@a current. A pending unget of EOF needs no special case, because reaching
|
||||||
|
EOF leaves no lookahead to release.
|
||||||
|
*/
|
||||||
|
void release_lookahead()
|
||||||
{
|
{
|
||||||
return token_start_position;
|
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/// seekable adapter: rebuild the last read token from the input on demand
|
/// 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
|
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||||
@@ -9451,12 +9543,6 @@ scan_number_done:
|
|||||||
/// the last read token on error for seekable adapters (see collect_token_chars)
|
/// the last read token on error for seekable adapters (see collect_token_chars)
|
||||||
std::size_t token_string_start = 0;
|
std::size_t token_string_start = 0;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
/// start offset of the current token within the input, used to report
|
|
||||||
/// diagnostic positions (see reset())
|
|
||||||
std::size_t token_start_position = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// buffer for variable-length tokens (numbers, strings)
|
/// buffer for variable-length tokens (numbers, strings)
|
||||||
string_t token_buffer {};
|
string_t token_buffer {};
|
||||||
|
|
||||||
@@ -9833,10 +9919,8 @@ class json_sax_dom_parser
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
// escape sequences make the token longer than the value it
|
// include the length of the quotes, which is 2
|
||||||
// parses to, so the start position cannot be derived from
|
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
|
||||||
// the value; use the offset the lexer recorded instead
|
|
||||||
v.start_position = m_lexer_ref->get_token_start_position();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10234,10 +10318,8 @@ class json_sax_dom_callback_parser
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
// escape sequences make the token longer than the value it
|
// include the length of the quotes, which is 2
|
||||||
// parses to, so the start position cannot be derived from
|
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
|
||||||
// the value; use the offset the lexer recorded instead
|
|
||||||
v.start_position = m_lexer_ref->get_token_start_position();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11087,6 +11169,15 @@ class binary_reader
|
|||||||
// CBOR //
|
// CBOR //
|
||||||
//////////
|
//////////
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@param[in] get_char whether a new character should be retrieved from the
|
||||||
|
input (true) or whether the last read character should
|
||||||
|
be considered instead (false)
|
||||||
|
@param[in] tag_handler how CBOR tags should be treated
|
||||||
|
|
||||||
|
@return whether a valid CBOR value was passed to the SAX parser
|
||||||
|
*/
|
||||||
|
|
||||||
template<typename NumberType>
|
template<typename NumberType>
|
||||||
bool get_cbor_negative_integer()
|
bool get_cbor_negative_integer()
|
||||||
{
|
{
|
||||||
@@ -11105,14 +11196,6 @@ class binary_reader
|
|||||||
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
|
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@param[in] get_char whether a new character should be retrieved from the
|
|
||||||
input (true) or whether the last read character should
|
|
||||||
be considered instead (false)
|
|
||||||
@param[in] tag_handler how CBOR tags should be treated
|
|
||||||
|
|
||||||
@return whether a valid CBOR value was passed to the SAX parser
|
|
||||||
*/
|
|
||||||
bool parse_cbor_internal(const bool get_char,
|
bool parse_cbor_internal(const bool get_char,
|
||||||
const cbor_tag_handler_t tag_handler)
|
const cbor_tag_handler_t tag_handler)
|
||||||
{
|
{
|
||||||
@@ -12609,11 +12692,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
if (get_char)
|
if (get_char)
|
||||||
{
|
{
|
||||||
// no get_ignore_noop() here: the byte read next must be a string
|
get(); // TODO(niels): may we ignore N here?
|
||||||
// length type specification, and a no-op ('N') is not valid in
|
|
||||||
// that position. No-ops at positions where a value may appear are
|
|
||||||
// already consumed by the callers via get_ignore_noop().
|
|
||||||
get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value")))
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value")))
|
||||||
@@ -13996,8 +14075,14 @@ class parser
|
|||||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
|
if (!strict)
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after
|
||||||
|
// the value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// in strict mode, input must be completely read
|
// in strict mode, input must be completely read
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
else if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
sdp.parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -14024,8 +14109,13 @@ class parser
|
|||||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
|
if (!strict)
|
||||||
|
{
|
||||||
|
// see above
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// in strict mode, input must be completely read
|
// in strict mode, input must be completely read
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
else if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
sdp.parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -14062,8 +14152,14 @@ class parser
|
|||||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal(sax);
|
||||||
|
|
||||||
|
if (result && !strict)
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after the
|
||||||
|
// value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
// strict mode: next byte must be EOF
|
// strict mode: next byte must be EOF
|
||||||
if (result && strict && (get_token() != token_type::end_of_input))
|
else if (result && strict && (get_token() != token_type::end_of_input))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -18604,23 +18700,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
len *= static_cast<std::size_t>(el.template get<std::uint64_t>());
|
||||||
// a dimension that does not fit into std::size_t, or a product that
|
|
||||||
// overflows it, would wrap around and could match the size of
|
|
||||||
// _ArrayData_ by accident; the resulting header announces an
|
|
||||||
// element count that no reader can honor (the binary reader rejects
|
|
||||||
// it with out_of_range.408), so encode as a plain object instead
|
|
||||||
const auto dim = el.template get<std::uint64_t>();
|
|
||||||
if (!value_in_range_of<std::size_t>(dim))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const auto dim_size = static_cast<std::size_t>(dim);
|
|
||||||
if (dim_size != 0 && len > (std::numeric_limits<std::size_t>::max)() / dim_size)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
len *= dim_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
key = "_ArrayData_";
|
key = "_ArrayData_";
|
||||||
|
|||||||
@@ -2730,27 +2730,6 @@ TEST_CASE("BJData")
|
|||||||
CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type);
|
CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type);
|
||||||
CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size);
|
CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("ndarray whose dimensions overflow stays as object")
|
|
||||||
{
|
|
||||||
// the product of the dimensions wraps around std::size_t to 0
|
|
||||||
// and so matches the size of the empty _ArrayData_; writing this
|
|
||||||
// as an ndarray would announce an element count no reader can
|
|
||||||
// honor, so it has to stay a plain object
|
|
||||||
json j_overflow = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {9223372036854775808ull, 2}}, {"_ArrayType_", "uint8"}});
|
|
||||||
CHECK(json::from_bjdata(json::to_bjdata(j_overflow), true, true) == j_overflow);
|
|
||||||
|
|
||||||
// a single dimension that does not fit into std::size_t is
|
|
||||||
// rejected for the same reason (only observable where
|
|
||||||
// std::size_t is narrower than 64 bit)
|
|
||||||
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull}}, {"_ArrayType_", "uint8"}});
|
|
||||||
CHECK(json::from_bjdata(json::to_bjdata(j_huge), true, true) == j_huge);
|
|
||||||
|
|
||||||
// a well-formed ndarray is still encoded as one
|
|
||||||
json j_ok = json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint8"}});
|
|
||||||
CHECK(json::to_bjdata(j_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 1, 2, 3, 4, 5, 6}));
|
|
||||||
CHECK(json::from_bjdata(json::to_bjdata(j_ok), true, true) == j_ok);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,15 @@ using nlohmann::json;
|
|||||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <streambuf>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
@@ -219,6 +224,58 @@ class proxy_iterator
|
|||||||
iterator* m_it = nullptr;
|
iterator* m_it = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A streambuf that keeps no get area at all and refuses every putback: with an
|
||||||
|
// empty get area, sungetc() always ends up in pbackfail(). Used to check that
|
||||||
|
// the character terminating a number is left in the input without relying on
|
||||||
|
// the streambuf being able to put a consumed character back.
|
||||||
|
class no_putback_streambuf : public std::streambuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit no_putback_streambuf(std::string s) : m_data(std::move(s)) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// peek at the next character without consuming it
|
||||||
|
int_type underflow() override
|
||||||
|
{
|
||||||
|
if (m_pos >= m_data.size())
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
return traits_type::to_int_type(m_data[m_pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume the next character
|
||||||
|
int_type uflow() override
|
||||||
|
{
|
||||||
|
if (m_pos >= m_data.size())
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
return traits_type::to_int_type(m_data[m_pos++]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int_type pbackfail(int_type /*c*/) override
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_data;
|
||||||
|
std::size_t m_pos = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// read the characters that are left in a stream
|
||||||
|
std::string remaining(std::istream& is)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
char c = 0;
|
||||||
|
while (is.get(c))
|
||||||
|
{
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// JSON_HAS_CPP_20
|
// JSON_HAS_CPP_20
|
||||||
#if defined(__cpp_char8_t)
|
#if defined(__cpp_char8_t)
|
||||||
bool check_utf8()
|
bool check_utf8()
|
||||||
@@ -1181,6 +1238,122 @@ TEST_CASE("deserialization")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("stream position after extraction (#5340)")
|
||||||
|
{
|
||||||
|
SECTION("a number does not consume the character that terminates it")
|
||||||
|
{
|
||||||
|
// a number is only terminated by the character following it; that
|
||||||
|
// character must be given back so the stream is positioned right
|
||||||
|
// after the value
|
||||||
|
const std::vector<std::pair<std::string, std::string>> tests =
|
||||||
|
{
|
||||||
|
{"1true", "true"},
|
||||||
|
{"1[2]", "[2]"},
|
||||||
|
{"1{}", "{}"},
|
||||||
|
{R"(1"a")", R"("a")"},
|
||||||
|
{"1 true", " true"},
|
||||||
|
{"12,", ","},
|
||||||
|
{"-0.5e3x", "x"},
|
||||||
|
{"1null", "null"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& test : tests)
|
||||||
|
{
|
||||||
|
CAPTURE(test.first);
|
||||||
|
std::istringstream ss(test.first);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == json::parse(test.first.substr(0, test.first.size() - test.second.size())));
|
||||||
|
CHECK(remaining(ss) == test.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("values that are self-delimiting are unaffected")
|
||||||
|
{
|
||||||
|
const std::vector<std::pair<std::string, std::string>> tests =
|
||||||
|
{
|
||||||
|
{"truefalse", "false"},
|
||||||
|
{"[1][2]", "[2]"},
|
||||||
|
{R"({"a":1}{"b":2})", R"({"b":2})"},
|
||||||
|
{R"("a""b")", R"("b")"},
|
||||||
|
{"null null", " null"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& test : tests)
|
||||||
|
{
|
||||||
|
CAPTURE(test.first);
|
||||||
|
std::istringstream ss(test.first);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(remaining(ss) == test.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a number at the end of the input leaves nothing behind")
|
||||||
|
{
|
||||||
|
for (const std::string s :
|
||||||
|
{"1", "12", "-3.5e2", " 7 "
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s);
|
||||||
|
std::istringstream ss(s);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(remaining(ss).find_first_not_of(" \t\n\r") == std::string::npos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("repeated extraction of concatenated values")
|
||||||
|
{
|
||||||
|
std::istringstream ss(R"(1true[2]3"x"{"a":4}5)");
|
||||||
|
const std::vector<json> expected =
|
||||||
|
{
|
||||||
|
json(1), json(true), json::parse("[2]"), json(3),
|
||||||
|
json("x"), json::parse(R"({"a":4})"), json(5)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& e : expected)
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sax_parse with strict == false")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1true");
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(ss, &l, nlohmann::detail::input_format_t::json, false));
|
||||||
|
CHECK(l.events.size() == 1);
|
||||||
|
CHECK(l.events[0] == "number_unsigned(1)");
|
||||||
|
CHECK(remaining(ss) == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("strict parsing still rejects trailing data")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1true");
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(ss),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - unexpected true literal; expected end of input", json::parse_error&);
|
||||||
|
|
||||||
|
std::istringstream ss2("1true");
|
||||||
|
CHECK_FALSE(json::accept(ss2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a streambuf that cannot put back is not needed")
|
||||||
|
{
|
||||||
|
// the terminating character is never consumed, so no putback
|
||||||
|
// position is required
|
||||||
|
no_putback_streambuf buf("1true");
|
||||||
|
std::istream is(&buf);
|
||||||
|
json j;
|
||||||
|
is >> j;
|
||||||
|
CHECK(j == json(1));
|
||||||
|
CHECK(remaining(is) == "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// build with C++20
|
// build with C++20
|
||||||
// JSON_HAS_CPP_20
|
// JSON_HAS_CPP_20
|
||||||
#if defined(__cpp_char8_t)
|
#if defined(__cpp_char8_t)
|
||||||
|
|||||||
@@ -38,36 +38,6 @@ TEST_CASE("Better diagnostics with positions")
|
|||||||
"[json.exception.type_error.302] type must be number, but is string", json::type_error);
|
"[json.exception.type_error.302] type must be number, but is string", json::type_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("positions of strings containing escape sequences")
|
|
||||||
{
|
|
||||||
// escape sequences make the token longer than the string it parses to,
|
|
||||||
// so the positions must not be derived from the parsed value's length
|
|
||||||
const auto check = [](const std::string & text, const std::string & token)
|
|
||||||
{
|
|
||||||
CAPTURE(text)
|
|
||||||
CAPTURE(token)
|
|
||||||
const json j = json::parse(text);
|
|
||||||
const json& v = j.at("a");
|
|
||||||
CHECK(text.substr(v.start_pos(), v.end_pos() - v.start_pos()) == token);
|
|
||||||
};
|
|
||||||
|
|
||||||
check(R"({"a":"plain"})", R"("plain")");
|
|
||||||
check(R"({"a":"tab\there"})", R"("tab\there")");
|
|
||||||
check(R"({"a":"\n\n\n\n\n\n"})", R"("\n\n\n\n\n\n")");
|
|
||||||
check(R"({"a":"\""})", R"("\"")");
|
|
||||||
check(R"({"a":"\\"})", R"("\\")");
|
|
||||||
check(R"({"a":"é"})", R"("é")");
|
|
||||||
check(R"({"a":"🌞"})", R"("🌞")");
|
|
||||||
check("{\"a\":\"\xc3\xa9\"}", "\"\xc3\xa9\""); // multi-byte UTF-8, no escapes
|
|
||||||
|
|
||||||
// a string at the root, where an escape would otherwise push the
|
|
||||||
// reported start position past the opening quote
|
|
||||||
const std::string root = R"("a\tb")";
|
|
||||||
const json j = json::parse(root);
|
|
||||||
CHECK(j.start_pos() == 0);
|
|
||||||
CHECK(j.end_pos() == root.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("JSON patch add to primitive parent (#4292)")
|
SECTION("JSON patch add to primitive parent (#4292)")
|
||||||
{
|
{
|
||||||
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
||||||
|
|||||||
@@ -1713,44 +1713,6 @@ TEST_CASE("UBJSON")
|
|||||||
CHECK(json::to_ubjson(json::from_ubjson(s_L)) == s_i);
|
CHECK(json::to_ubjson(json::from_ubjson(s_L)) == s_i);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("no-op markers")
|
|
||||||
{
|
|
||||||
// A no-op ('N') is valid wherever a value may start; it is consumed
|
|
||||||
// by get_ignore_noop() before the value is read. It is not valid
|
|
||||||
// where a string length type specification is expected.
|
|
||||||
|
|
||||||
SECTION("accepted where a value may start")
|
|
||||||
{
|
|
||||||
// at top level, also repeated
|
|
||||||
CHECK(json::from_ubjson(std::vector<uint8_t>({'N', 'i', 1})) == json(1));
|
|
||||||
CHECK(json::from_ubjson(std::vector<uint8_t>({'N', 'N', 'N', 'i', 1})) == json(1));
|
|
||||||
|
|
||||||
// inside an array of unknown size, before and after an element
|
|
||||||
CHECK(json::from_ubjson(std::vector<uint8_t>({'[', 'N', 'i', 1, ']'})) == json({1}));
|
|
||||||
CHECK(json::from_ubjson(std::vector<uint8_t>({'[', 'i', 1, 'N', ']'})) == json({1}));
|
|
||||||
|
|
||||||
// inside an object of unknown size: before a key, between key
|
|
||||||
// and value, and before the closing '}'
|
|
||||||
CHECK(json::from_ubjson(std::vector<uint8_t>({'{', 'N', 'U', 1, 'a', 'i', 1, '}'})) == json({{"a", 1}}));
|
|
||||||
CHECK(json::from_ubjson(std::vector<uint8_t>({'{', 'U', 1, 'a', 'N', 'i', 1, '}'})) == json({{"a", 1}}));
|
|
||||||
CHECK(json::from_ubjson(std::vector<uint8_t>({'{', 'U', 1, 'a', 'i', 1, 'N', '}'})) == json({{"a", 1}}));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("rejected where a length type specification is expected")
|
|
||||||
{
|
|
||||||
json _;
|
|
||||||
|
|
||||||
// after the 'S' marker of a string value
|
|
||||||
std::vector<uint8_t> const v_S = {'S', 'N', 'U', 1, 'a'};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v_S), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4E", json::parse_error&);
|
|
||||||
|
|
||||||
// as the key length of an object with a known size, where
|
|
||||||
// no-ops are not permitted in the first place
|
|
||||||
std::vector<uint8_t> const v_key = {'{', '#', 'i', 1, 'N', 'U', 1, 'a', 'i', 1};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v_key), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4E", json::parse_error&);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("number")
|
SECTION("number")
|
||||||
{
|
{
|
||||||
SECTION("float")
|
SECTION("float")
|
||||||
|
|||||||
@@ -125,16 +125,6 @@ TEST_CASE("wide strings")
|
|||||||
std::u32string const w = U"\"\x110000";
|
std::u32string const w = U"\"\x110000";
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
|
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
|
||||||
|
|
||||||
// a code unit above U+10FFFF must not be narrowed onto the EOF
|
|
||||||
// sentinel: 0xFFFFFFFF would otherwise end the document silently and
|
|
||||||
// let everything following it pass the strict end-of-input check
|
|
||||||
std::u32string const trailing{U'[', U'1', U']', static_cast<char32_t>(0xFFFFFFFF), U'x'};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&);
|
|
||||||
CHECK(!json::accept(trailing));
|
|
||||||
|
|
||||||
// the same unit inside a string is reported as an ill-formed byte
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast<char32_t>(0xFFFFFFFF), U'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user