mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 19:18:12 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
732fc11890 | ||
|
|
252f43667b | ||
|
|
dc0b8890da | ||
|
|
5b323db9d5 | ||
|
|
cd6b428ef0 |
No files matched your search
@@ -166,19 +166,21 @@ jobs:
|
|||||||
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
|
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
|
||||||
# before doctest prints its first line - 39 of 102 tests on clang 18.
|
# before doctest prints its first line - 39 of 102 tests on clang 18.
|
||||||
# Keep the objects small by splitting the test files instead.
|
# Keep the objects small by splitting the test files instead.
|
||||||
# Objects with more than 65535 sections fail to link with "relocation
|
# The tests link with LLD: GNU ld fails on objects with more than 32767
|
||||||
# truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'": the MinGW
|
# sections with "relocation truncated to fit: IMAGE_REL_AMD64_REL32
|
||||||
# linker stores the section an associative COMDAT section belongs to in
|
# against `.rdata'", as it stores the section an associative COMDAT
|
||||||
# 16 bits (x_associated in binutils' include/coff/internal.h), so it
|
# section belongs to in 16 bits (x_associated in binutils'
|
||||||
# discards the jump tables of inline functions together with the wrong
|
# include/coff/internal.h) and so discards the jump tables of inline
|
||||||
# function. Each basic_json specialization a test file instantiates adds
|
# functions together with the wrong function. Each basic_json
|
||||||
# many sections, so test a second one in a file of its own.
|
# specialization and input type a test file instantiates adds many
|
||||||
|
# sections, so unit-msgpack.cpp and unit-regression2.cpp kept crossing
|
||||||
|
# that limit as the library grew.
|
||||||
- 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_CXX_FLAGS_DEBUG="-g0" ^
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
|
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld -lwinpthread" ^
|
||||||
-G"MinGW Makefiles" ^
|
-G"MinGW Makefiles" ^
|
||||||
-DCMAKE_BUILD_TYPE=Debug ^
|
-DCMAKE_BUILD_TYPE=Debug ^
|
||||||
-DJSON_BuildTests=On
|
-DJSON_BuildTests=On
|
||||||
|
|||||||
@@ -132,10 +132,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
|||||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
- [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
|
- [`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
|
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
|
## Version history
|
||||||
|
|
||||||
@@ -147,10 +143,6 @@ 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.
|
- 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
|
- `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.
|
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"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -112,16 +112,6 @@ 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
|
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.
|
[`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
|
## Examples
|
||||||
|
|
||||||
??? example "Example: (1) parse from a character array"
|
??? example "Example: (1) parse from a character array"
|
||||||
@@ -257,10 +247,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
|||||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
- [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
|
- [`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
|
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
|
## Version history
|
||||||
|
|
||||||
@@ -275,11 +261,6 @@ 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.
|
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
|
- 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.
|
(`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"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -116,10 +116,6 @@ consumer `sax` has a super-linear complexity.
|
|||||||
|
|
||||||
A UTF-8 byte order mark is silently ignored.
|
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
|
## Examples
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
@@ -141,10 +137,6 @@ use the same file with stdio functions, but it must not wait for another thread
|
|||||||
|
|
||||||
- [parse](parse.md) - deserialize from a compatible input
|
- [parse](parse.md) - deserialize from a compatible input
|
||||||
- [accept](accept.md) - check if the input is valid JSON
|
- [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
|
## Version history
|
||||||
|
|
||||||
@@ -156,10 +148,6 @@ use the same file with stdio functions, but it must not wait for another thread
|
|||||||
- 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.
|
||||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
- `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`.
|
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"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
|
|
||||||
## Parsing
|
## 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
|
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned
|
||||||
right after a parsed number
|
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,
|
- [**JSON_STRICT_BINARY_UTF8**](json_strict_binary_utf8.md) - opt in to checking strings for valid UTF-8 in the CBOR,
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
!!! warning "Define consistently"
|
|
||||||
|
|
||||||
The macro selects between two definitions of the library's internal stream input adapter. It must therefore be
|
|
||||||
defined identically for **every** translation unit that includes the library; mixing translation units that define
|
|
||||||
it with ones that do not is an ODR violation. Prefer setting it as a compile definition on the target rather than
|
|
||||||
with `#!cpp #define` in individual source files.
|
|
||||||
|
|
||||||
!!! 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.
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# 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).
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
!!! warning "Define consistently"
|
|
||||||
|
|
||||||
The macro selects between two definitions of the library's internal `FILE*` input adapter. It must therefore be
|
|
||||||
defined identically for **every** translation unit that includes the library; mixing translation units that define
|
|
||||||
it with ones that do not is an ODR violation (and can leave a file locked). Prefer setting it as a compile
|
|
||||||
definition on the target rather than with `#!cpp #define` in individual source files.
|
|
||||||
|
|
||||||
!!! 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.
|
|
||||||
@@ -72,12 +72,6 @@ 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
|
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.
|
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"
|
!!! tip "Workaround without the macro"
|
||||||
|
|
||||||
Separate the values in the stream with whitespace. The character consumed after a number is then the separator,
|
Separate the values in the stream with whitespace. The character consumed after a number is then the separator,
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
|||||||
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
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
|
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
|
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). A `std::istream` uses simdutf for the characters its stream buffer holds (see
|
diagnostic is preserved). Streaming inputs (files, `std::istream`, wide strings, user-defined adapters) always use the
|
||||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other streaming inputs (`FILE*`, wide strings,
|
scalar path.
|
||||||
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
|
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.
|
simdutf library. When it is not defined, no simdutf header is included and there is no dependency.
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ being read.
|
|||||||
- [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
- [`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
|
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_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
|
## Version history
|
||||||
|
|
||||||
@@ -134,6 +133,3 @@ being read.
|
|||||||
crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0.
|
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
|
- 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.
|
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).
|
|
||||||
@@ -133,14 +133,6 @@ 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).
|
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`
|
## `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
|
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
||||||
@@ -149,14 +141,6 @@ 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).
|
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`
|
## `JSON_NO_AUTOMATIC_UDLS`
|
||||||
|
|
||||||
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
||||||
|
|||||||
@@ -55,30 +55,6 @@ 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
|
[`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.
|
[`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.
|
|
||||||
|
|
||||||
## SAX vs. DOM parsing
|
## SAX vs. DOM parsing
|
||||||
|
|
||||||
The library offers two parsing models:
|
The library offers two parsing models:
|
||||||
|
|||||||
@@ -297,9 +297,7 @@ nav:
|
|||||||
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
|
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
|
||||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
||||||
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.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_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_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_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
||||||
- 'JSON_HAS_RANGE_VIEW_CONVERSION': api/macros/json_has_range_view_conversion.md
|
- 'JSON_HAS_RANGE_VIEW_CONVERSION': api/macros/json_has_range_view_conversion.md
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
#include <optional> // optional
|
#include <optional> // optional
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <iterator> // begin, end
|
#include <iterator> // begin, end, make_move_iterator, move_iterator
|
||||||
#include <memory> // allocator_traits
|
#include <memory> // allocator_traits
|
||||||
#include <string> // basic_string, char_traits
|
#include <string> // basic_string, char_traits
|
||||||
#include <tuple> // tuple, get
|
#include <tuple> // tuple, get
|
||||||
@@ -48,6 +48,32 @@ namespace detail
|
|||||||
* leave j with the type of the new value, but the pointer to the destroyed old one.
|
* leave j with the type of the new value, but the pointer to the destroyed old one.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// a move iterator over the elements of an rvalue container, unless they are
|
||||||
|
// const (e.g., std::set): libstdc++ before GCC 5 defines the reference of a
|
||||||
|
// std::move_iterator as value_type&&, which cannot bind to a const element
|
||||||
|
template<typename Iterator>
|
||||||
|
std::move_iterator<Iterator> make_move_iterator_if_mutable_impl(const Iterator& it, std::true_type /*mutable*/)
|
||||||
|
{
|
||||||
|
return std::make_move_iterator(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iterator>
|
||||||
|
Iterator make_move_iterator_if_mutable_impl(const Iterator& it, std::false_type /*mutable*/)
|
||||||
|
{
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Iterator>
|
||||||
|
using is_mutable_iterator = std::integral_constant < bool,
|
||||||
|
!std::is_const<typename std::remove_reference<decltype(*std::declval<Iterator&>())>::type>::value >;
|
||||||
|
|
||||||
|
template<typename Iterator>
|
||||||
|
auto make_move_iterator_if_mutable(const Iterator& it)
|
||||||
|
-> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {}))
|
||||||
|
{
|
||||||
|
return make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {});
|
||||||
|
}
|
||||||
|
|
||||||
template<value_t> struct external_constructor;
|
template<value_t> struct external_constructor;
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
@@ -174,7 +200,7 @@ struct external_constructor<value_t::array>
|
|||||||
using std::begin;
|
using std::begin;
|
||||||
using std::end;
|
using std::end;
|
||||||
|
|
||||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
|
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(make_move_iterator_if_mutable(begin(arr)), make_move_iterator_if_mutable(end(arr))));
|
||||||
j.replace_value(value_t::array, value);
|
j.replace_value(value_t::array, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +261,7 @@ struct external_constructor<value_t::object>
|
|||||||
using std::begin;
|
using std::begin;
|
||||||
using std::end;
|
using std::end;
|
||||||
|
|
||||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
|
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(make_move_iterator_if_mutable(begin(obj)), make_move_iterator_if_mutable(end(obj))));
|
||||||
j.replace_value(value_t::object, value);
|
j.replace_value(value_t::object, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,25 +439,27 @@ using is_movable_container_rvalue = negation<disjunction<
|
|||||||
std::is_const<T>,
|
std::is_const<T>,
|
||||||
has_dedicated_to_json<T>,
|
has_dedicated_to_json<T>,
|
||||||
is_basic_json<T>,
|
is_basic_json<T>,
|
||||||
is_range_view_type<T>,
|
|
||||||
is_enum_keyed_map_type<T>,
|
is_enum_keyed_map_type<T>,
|
||||||
std::is_same<T, std::vector<bool>>>>;
|
std::is_same<T, std::vector<bool>>>>;
|
||||||
|
|
||||||
// rvalue array-like containers
|
// rvalue array-like containers; object types are ruled out before views are
|
||||||
|
// checked, as std::ranges::view<T> for the object_t of an ordered_json would
|
||||||
|
// need that ordered_json to be complete (GCC 11, see #3312)
|
||||||
template<typename BasicJsonType, typename T>
|
template<typename BasicJsonType, typename T>
|
||||||
using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>,
|
using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||||
|
negation<is_compatible_object_type<BasicJsonType, T>>,
|
||||||
|
negation<is_range_view_type<T>>,
|
||||||
is_compatible_array_type<BasicJsonType, T>,
|
is_compatible_array_type<BasicJsonType, T>,
|
||||||
negation<disjunction<
|
negation<disjunction<
|
||||||
std::is_same<T, typename BasicJsonType::array_t>,
|
std::is_same<T, typename BasicJsonType::array_t>,
|
||||||
std::is_same<T, typename BasicJsonType::binary_t>,
|
std::is_same<T, typename BasicJsonType::binary_t>,
|
||||||
is_compatible_object_type<BasicJsonType, T>,
|
|
||||||
is_compatible_string_type<BasicJsonType, T>,
|
is_compatible_string_type<BasicJsonType, T>,
|
||||||
is_compatible_binary_type<BasicJsonType, T>>>>;
|
is_compatible_binary_type<BasicJsonType, T>>>>;
|
||||||
|
|
||||||
// rvalue containers: their elements are moved instead of copied
|
// rvalue containers: their elements are moved instead of copied
|
||||||
template<typename BasicJsonType, typename CompatibleArrayType,
|
template<typename BasicJsonType, typename CompatibleArrayType,
|
||||||
enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0>
|
enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0>
|
||||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
|
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||||
{
|
{
|
||||||
external_constructor<value_t::array>::construct_moving(j, arr);
|
external_constructor<value_t::array>::construct_moving(j, arr);
|
||||||
}
|
}
|
||||||
@@ -509,7 +537,8 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// rvalue object-like containers
|
// rvalue object-like containers (views have no key_type and mapped_type, so
|
||||||
|
// they are no compatible object type)
|
||||||
template<typename BasicJsonType, typename T>
|
template<typename BasicJsonType, typename T>
|
||||||
using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
|
using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||||
negation<std::is_same<T, typename BasicJsonType::object_t>>,
|
negation<std::is_same<T, typename BasicJsonType::object_t>>,
|
||||||
@@ -517,7 +546,7 @@ using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
|
|||||||
|
|
||||||
template<typename BasicJsonType, typename CompatibleObjectType,
|
template<typename BasicJsonType, typename CompatibleObjectType,
|
||||||
enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0>
|
enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0>
|
||||||
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj)
|
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||||
{
|
{
|
||||||
external_constructor<value_t::object>::construct_moving(j, obj);
|
external_constructor<value_t::object>::construct_moving(j, obj);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc
|
|||||||
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
|
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
|
||||||
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
|
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
|
||||||
// (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses)
|
// (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses)
|
||||||
constexpr unsigned fractional_low_shift = static_cast<unsigned>(extra_shift);
|
constexpr auto fractional_low_shift = static_cast<unsigned>(extra_shift);
|
||||||
constexpr unsigned fractional_high_shift = 64u - fractional_low_shift;
|
constexpr unsigned fractional_high_shift = 64u - fractional_low_shift;
|
||||||
const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift);
|
const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift);
|
||||||
std::uint64_t digit = 0;
|
std::uint64_t digit = 0;
|
||||||
|
|||||||
@@ -494,8 +494,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_missing_member(c
|
|||||||
static_cast<void>(string_type);
|
static_cast<void>(string_type);
|
||||||
static_cast<void>(val);
|
static_cast<void>(val);
|
||||||
// context-sensitive error message
|
// context-sensitive error message
|
||||||
const std::string error_msg = (op == "op") ? std::string("operation") : concat("operation '", op, '\'');
|
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat((op == "op") ? std::string("operation") : concat("operation '", op, '\''), string_type ? " must have string member '" : " must have member '", member, "'"), &val));
|
||||||
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename StringType, typename BasicJsonType>
|
template<typename StringType, typename BasicJsonType>
|
||||||
|
|||||||
@@ -14,12 +14,10 @@
|
|||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstring> // strlen
|
#include <cstring> // strlen
|
||||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||||
#include <limits> // numeric_limits
|
|
||||||
#include <streambuf> // streambuf
|
#include <streambuf> // streambuf
|
||||||
#include <string> // string, char_traits
|
#include <string> // string, char_traits
|
||||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||||
#include <utility> // pair, declval
|
#include <utility> // pair, declval
|
||||||
#include <vector> // vector
|
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
#include <cstdio> // FILE *
|
#include <cstdio> // FILE *
|
||||||
@@ -32,24 +30,6 @@
|
|||||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||||
#include <nlohmann/detail/string_utils.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__.
|
|
||||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
|
||||||
#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
|
|
||||||
#endif // JSON_NO_IO
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
@@ -76,38 +56,18 @@ class file_input_adapter
|
|||||||
: m_file(f)
|
: m_file(f)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(m_file != nullptr);
|
JSON_ASSERT(m_file != nullptr);
|
||||||
lock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make class move-only
|
// make class move-only
|
||||||
file_input_adapter(const file_input_adapter&) = delete;
|
file_input_adapter(const file_input_adapter&) = delete;
|
||||||
file_input_adapter(file_input_adapter&& rhs) noexcept
|
file_input_adapter(file_input_adapter&&) noexcept = default;
|
||||||
: 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=(const file_input_adapter&) = delete;
|
||||||
file_input_adapter& operator=(file_input_adapter&&) = delete;
|
file_input_adapter& operator=(file_input_adapter&&) = delete;
|
||||||
~file_input_adapter()
|
~file_input_adapter() = default;
|
||||||
{
|
|
||||||
if (m_file != nullptr)
|
|
||||||
{
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::char_traits<char>::int_type get_character() noexcept
|
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);
|
return std::fgetc(m_file);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the number of characters successfully read
|
// returns the number of characters successfully read
|
||||||
@@ -118,27 +78,6 @@ class file_input_adapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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
|
|
||||||
{
|
|
||||||
#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
|
/// the file pointer to read from
|
||||||
std::FILE* m_file;
|
std::FILE* m_file;
|
||||||
};
|
};
|
||||||
@@ -188,26 +127,15 @@ class input_stream_adapter
|
|||||||
input_stream_adapter& operator=(input_stream_adapter&) = delete;
|
input_stream_adapter& operator=(input_stream_adapter&) = delete;
|
||||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||||
|
|
||||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
|
||||||
: is(rhs.is), sb(rhs.sb)
|
|
||||||
#if JSON_PRECISE_STREAM_POSITION
|
#if JSON_PRECISE_STREAM_POSITION
|
||||||
, lookahead(rhs.lookahead)
|
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||||
#endif
|
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||||
#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)
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
rhs.is = nullptr;
|
rhs.is = nullptr;
|
||||||
rhs.sb = nullptr;
|
rhs.sb = nullptr;
|
||||||
#if JSON_PRECISE_STREAM_POSITION
|
|
||||||
rhs.lookahead = false;
|
rhs.lookahead = false;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_PRECISE_STREAM_POSITION
|
|
||||||
// Whether the character last returned by get_character() can be given back
|
// Whether the character last returned by get_character() can be given back
|
||||||
// to the input with release_lookahead().
|
// to the input with release_lookahead().
|
||||||
static constexpr bool supports_lookahead = true;
|
static constexpr bool supports_lookahead = true;
|
||||||
@@ -227,41 +155,17 @@ class input_stream_adapter
|
|||||||
sb->sbumpc();
|
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();
|
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
|
// there is nothing to step over next time
|
||||||
lookahead = false;
|
lookahead = false;
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
|
||||||
current_in_get_area = false;
|
|
||||||
#endif
|
|
||||||
is->clear(is->rdstate() | std::ios::eofbit);
|
is->clear(is->rdstate() | std::ios::eofbit);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lookahead = true;
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -275,6 +179,13 @@ class input_stream_adapter
|
|||||||
lookahead = false;
|
lookahead = false;
|
||||||
}
|
}
|
||||||
#else
|
#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
|
// 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.
|
||||||
@@ -283,37 +194,12 @@ class input_stream_adapter
|
|||||||
// stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION.
|
// stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION.
|
||||||
std::char_traits<char>::int_type get_character()
|
std::char_traits<char>::int_type get_character()
|
||||||
{
|
{
|
||||||
#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();
|
auto res = sb->sbumpc();
|
||||||
// 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()))
|
||||||
{
|
{
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
|
||||||
current_in_get_area = false;
|
|
||||||
#endif
|
|
||||||
is->clear(is->rdstate() | std::ios::eofbit);
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -332,165 +218,7 @@ class input_stream_adapter
|
|||||||
return res;
|
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
|
|
||||||
{
|
|
||||||
out = token_carry;
|
|
||||||
if (token_segment != nullptr)
|
|
||||||
{
|
|
||||||
out.insert(out.end(), token_segment, current_end());
|
|
||||||
}
|
|
||||||
if (drop_last && !out.empty())
|
|
||||||
{
|
|
||||||
out.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
|
|
||||||
token_segment = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
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
|
#if JSON_PRECISE_STREAM_POSITION
|
||||||
// Step over the character last returned by get_character(). The character
|
// Step over the character last returned by get_character(). The character
|
||||||
// has already been peeked successfully, so for every streambuf with a get
|
// has already been peeked successfully, so for every streambuf with a get
|
||||||
@@ -512,16 +240,6 @@ class input_stream_adapter
|
|||||||
/// whether get_character() peeked a character that is not consumed yet
|
/// whether get_character() peeked a character that is not consumed yet
|
||||||
bool lookahead = false;
|
bool lookahead = false;
|
||||||
#endif
|
#endif
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
|
||||||
/// the part of the current token that was in earlier get areas
|
|
||||||
std::vector<char> 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;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
|
|||||||
@@ -166,43 +166,6 @@ constexpr bool input_adapter_supports_bulk_scan(std::false_type /*detected*/)
|
|||||||
return false;
|
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
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -221,20 +184,9 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
/// whether the last read token can be reconstructed from the input adapter
|
/// whether the last read token can be reconstructed from the input adapter
|
||||||
/// on demand (in error paths) instead of being copied on every scanned
|
/// on demand (in error paths) instead of being copied on every scanned
|
||||||
/// character; see input_adapter_supports_seek
|
/// character; see input_adapter_supports_seek
|
||||||
static constexpr bool seekable =
|
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 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
|
/// whether the read position is derived from the input adapter on demand
|
||||||
/// instead of being counted on every scanned character. Seekable adapters
|
/// instead of being counted on every scanned character. Seekable adapters
|
||||||
/// know how much input they consumed and can revisit it, so
|
/// know how much input they consumed and can revisit it, so
|
||||||
@@ -242,7 +194,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
/// and line and column are recomputed from the consumed input only when
|
/// and line and column are recomputed from the consumed input only when
|
||||||
/// get_position() is called (errors). Counting per character kept two
|
/// get_position() is called (errors). Counting per character kept two
|
||||||
/// increments and a newline check in the hot path of every get().
|
/// increments and a newline check in the hot path of every get().
|
||||||
static constexpr bool lazy_position = seekable;
|
static constexpr bool lazy_position = lazy_token_string;
|
||||||
|
|
||||||
/// whether a simulated unget can be passed on to the input adapter, which
|
/// whether a simulated unget can be passed on to the input adapter, which
|
||||||
/// then leaves the character in the input; see
|
/// then leaves the character in the input; see
|
||||||
@@ -251,17 +203,14 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||||
|
|
||||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||||
/// directly from a contiguous input buffer (SWAR fast path). Without
|
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||||
/// lazy_token_string / lazy_position, the bulk paths copy the consumed
|
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||||
/// characters into token_string and advance the position counters
|
/// the per-character capture in get() cannot lose error diagnostics. It
|
||||||
/// themselves (see bulk_consume).
|
/// also implies lazy_position, so the bulk paths need not advance the
|
||||||
|
/// position counters.
|
||||||
static constexpr bool bulk_scan =
|
static constexpr bool bulk_scan =
|
||||||
input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
lazy_token_string
|
||||||
|
&& 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:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
@@ -270,11 +219,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
, discard_number_values(discard_number_values_)
|
, 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
|
// deleted because of pointer members
|
||||||
lexer(const lexer&) = delete;
|
lexer(const lexer&) = delete;
|
||||||
@@ -291,8 +236,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
/// contiguous input: try to decode the 4 hex digits following `\\u`
|
/// contiguous input: try to decode the 4 hex digits following `\\u`
|
||||||
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
||||||
/// to get(). On success, advances the adapter exactly as those 4 get()
|
/// to get(). On success, advances the adapter exactly as those 4 get()
|
||||||
/// calls would (including the position and token bookkeeping of a
|
/// calls would (bulk_scan implies lazy_position, so there are no position
|
||||||
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
|
/// counters to advance) and leaves @a current holding the last of
|
||||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
/// 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
|
/// 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
|
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
||||||
@@ -311,7 +256,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bulk_consume(reinterpret_cast<const char*>(raw), 4);
|
ia.bulk_skip(4);
|
||||||
current = char_traits<char_type>::to_int_type(raw[3]);
|
current = char_traits<char_type>::to_int_type(raw[3]);
|
||||||
out = codepoint;
|
out = codepoint;
|
||||||
return true;
|
return true;
|
||||||
@@ -440,36 +385,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
ia.bulk_skip(pos);
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
/// streaming input: no bulk fast path
|
||||||
@@ -1625,12 +1541,9 @@ scan_number_done:
|
|||||||
const std::size_t avail = rem + 1;
|
const std::size_t avail = rem + 1;
|
||||||
|
|
||||||
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
// 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 i = 0;
|
||||||
std::size_t dot_index = std::string::npos;
|
std::size_t dot_index = std::string::npos;
|
||||||
token_type number_type = token_type::value_unsigned;
|
token_type number_type = token_type::value_unsigned;
|
||||||
std::uint64_t w = 0;
|
|
||||||
if (data[0] == '-')
|
if (data[0] == '-')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_integer;
|
number_type = token_type::value_integer;
|
||||||
@@ -1640,21 +1553,22 @@ scan_number_done:
|
|||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const std::size_t integer_begin = i;
|
|
||||||
if (data[i] == '0')
|
if (data[i] == '0')
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
else if (data[i] >= '1' && data[i] <= '9')
|
else if (data[i] >= '1' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
i = accumulate_digits(data, i, avail, w);
|
++i;
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
std::size_t digit_count = i - integer_begin;
|
|
||||||
std::size_t fraction_digits = 0;
|
|
||||||
if (i < avail && data[i] == '.')
|
if (i < avail && data[i] == '.')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
@@ -1664,45 +1578,31 @@ scan_number_done:
|
|||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
const std::size_t fraction_begin = i;
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
i = accumulate_digits(data, i, avail, w);
|
{
|
||||||
fraction_digits = i - fraction_begin;
|
++i;
|
||||||
digit_count += fraction_digits;
|
}
|
||||||
}
|
}
|
||||||
// the mantissa ends here, whether or not an exponent part follows
|
// the mantissa ends here, whether or not an exponent part follows
|
||||||
const std::size_t mantissa_end = i;
|
const std::size_t mantissa_end = i;
|
||||||
std::int64_t exponent = 0;
|
|
||||||
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
++i;
|
++i;
|
||||||
bool negative_exponent = false;
|
|
||||||
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||||
{
|
{
|
||||||
negative_exponent = data[i] == '-';
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
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')
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
if (exponent < saturation)
|
|
||||||
{
|
|
||||||
exponent = (exponent * 10) + (data[i] - '0');
|
|
||||||
}
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
exponent = negative_exponent ? -exponent : exponent;
|
|
||||||
}
|
}
|
||||||
const std::size_t len = i;
|
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
|
// reset() records where this token starts (for diagnostics), so it has
|
||||||
// to run before the input position advances below
|
// to run before the input position advances below
|
||||||
@@ -1717,14 +1617,10 @@ scan_number_done:
|
|||||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||||
if (number_type != token_type::value_float)
|
if (number_type != token_type::value_float)
|
||||||
{
|
{
|
||||||
// up to 18 digits always fit into std::uint64_t and std::int64_t
|
const token_type integer_result = convert_integer(number_type, data, data + len);
|
||||||
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))
|
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||||
{
|
{
|
||||||
bulk_consume(data + 1, len - 1);
|
ia.bulk_skip(len - 1);
|
||||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
|
||||||
return integer_result;
|
return integer_result;
|
||||||
}
|
}
|
||||||
// The value does not fit an integer, so this token converts as a
|
// The value does not fit an integer, so this token converts as a
|
||||||
@@ -1740,80 +1636,11 @@ scan_number_done:
|
|||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
decimal_point_position = dot_index;
|
decimal_point_position = dot_index;
|
||||||
|
|
||||||
bulk_consume(data + 1, len - 1);
|
ia.bulk_skip(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> {}))
|
|
||||||
{
|
|
||||||
return token_type::value_float;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return convert_number(number_type, mantissa_end);
|
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
|
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||||
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
@@ -1869,30 +1696,11 @@ scan_number_done:
|
|||||||
/// seekable adapter: remember where the current token starts so it can be
|
/// seekable adapter: remember where the current token starts so it can be
|
||||||
/// reconstructed from the input on error; current has already been
|
/// reconstructed from the input on error; current has already been
|
||||||
/// consumed, hence the -1
|
/// consumed, hence the -1
|
||||||
void note_token_start(std::true_type /*lazy*/)
|
void note_token_start(std::true_type /*lazy*/) noexcept
|
||||||
{
|
|
||||||
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_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
|
/// streaming adapter: start copying the token eagerly, beginning with the
|
||||||
/// already-read first character
|
/// already-read first character
|
||||||
void note_token_start(std::false_type /*lazy*/) noexcept
|
void note_token_start(std::false_type /*lazy*/) noexcept
|
||||||
@@ -2213,20 +2021,6 @@ scan_number_done:
|
|||||||
public:
|
public:
|
||||||
/// 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
|
||||||
{
|
|
||||||
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
|
// 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
|
// was consumed from the input but is not part of the token; EOF is
|
||||||
@@ -2348,8 +2142,9 @@ scan_number_done:
|
|||||||
Pretty-printed JSON indents every line with a run of spaces, which the loop
|
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
|
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
|
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();
|
other whitespace are left to that loop. Skipping bypasses get(), which is
|
||||||
bulk_consume() does its bookkeeping.
|
fine because bulk_scan implies lazy_position: the position is derived from
|
||||||
|
the adapter, not counted per character.
|
||||||
*/
|
*/
|
||||||
void skip_indentation(std::true_type /*bulk*/)
|
void skip_indentation(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
@@ -2372,7 +2167,7 @@ scan_number_done:
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bulk_consume(data, pos);
|
ia.bulk_skip(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
|
|||||||
@@ -275,53 +275,13 @@ 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);
|
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
|
/// whether [first, last) contains a digit other than '0'
|
||||||
/// digits (after fast_float)
|
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
|
||||||
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
|
|
||||||
{
|
{
|
||||||
return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
|
return std::any_of(first, last, [](char c) noexcept
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@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)
|
|
||||||
{
|
{
|
||||||
const std::uint64_t v = read_eight_bytes(data + i);
|
return c != '0';
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
||||||
@@ -367,7 +327,7 @@ truncated is set.
|
|||||||
std::string::npos if there is none
|
std::string::npos if there is none
|
||||||
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
||||||
*/
|
*/
|
||||||
JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
|
inline float_significand parse_float_significand(const char* first, const char* last,
|
||||||
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||||
{
|
{
|
||||||
float_significand s;
|
float_significand s;
|
||||||
|
|||||||
@@ -680,12 +680,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
if (JSON_HEDLEY_LIKELY(m_object->is_object()))
|
if (JSON_HEDLEY_UNLIKELY(!m_object->is_object()))
|
||||||
{
|
{
|
||||||
return m_it.object_iterator->first;
|
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
|
return m_it.object_iterator->first;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <algorithm> // all_of
|
#include <algorithm> // all_of
|
||||||
#include <cctype> // isdigit
|
#include <cctype> // isdigit
|
||||||
#include <cerrno> // errno, ERANGE
|
#include <cerrno> // errno, ERANGE
|
||||||
|
#include <cstddef> // size_t
|
||||||
#include <cstdlib> // strtoull
|
#include <cstdlib> // strtoull
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
#include <iosfwd> // ostream
|
#include <iosfwd> // ostream
|
||||||
@@ -334,7 +335,7 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
break; // not a plain number: let strtoull decide
|
break; // not a plain number: let strtoull decide
|
||||||
}
|
}
|
||||||
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
|
acc = static_cast<size_type>((acc * 10) + static_cast<size_type>(c - '0'));
|
||||||
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||||
{
|
{
|
||||||
idx = acc;
|
idx = acc;
|
||||||
@@ -529,10 +530,10 @@ class json_pointer
|
|||||||
|
|
||||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||||
{
|
{
|
||||||
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
|
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL & (std::numeric_limits<std::size_t>::max)());
|
||||||
for (std::size_t i = 0; i < token.size(); ++i)
|
for (std::size_t i = 0; i < token.size(); ++i)
|
||||||
{
|
{
|
||||||
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
|
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL & (std::numeric_limits<std::size_t>::max)());
|
||||||
}
|
}
|
||||||
return h ^ (h >> 29u);
|
return h ^ (h >> 29u);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -895,11 +895,3 @@
|
|||||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||||
#endif
|
#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
|
|
||||||
@@ -21,9 +21,6 @@
|
|||||||
#undef JSON_NO_UNIQUE_ADDRESS
|
#undef JSON_NO_UNIQUE_ADDRESS
|
||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
#undef JSON_DISABLE_STREAM_BULK_SCAN
|
|
||||||
#undef JSON_DISABLE_UNLOCKED_FILE_IO
|
|
||||||
#undef JSON_UNLOCKED_FILE_IO
|
|
||||||
#undef JSON_DTOA_SSE2
|
#undef JSON_DTOA_SSE2
|
||||||
#undef JSON_DTOA_NEON
|
#undef JSON_DTOA_NEON
|
||||||
|
|
||||||
|
|||||||
+51
-49
@@ -29,6 +29,7 @@
|
|||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
#include <iterator> // make_move_iterator, random_access_iterator_tag
|
#include <iterator> // make_move_iterator, random_access_iterator_tag
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
|
#include <map> // map
|
||||||
#include <memory> // unique_ptr
|
#include <memory> // unique_ptr
|
||||||
#include <set> // swap, operator!=
|
#include <set> // swap, operator!=
|
||||||
#include <stdexcept> // length_error
|
#include <stdexcept> // length_error
|
||||||
@@ -754,26 +755,20 @@ private:
|
|||||||
// one level deep); stops at the first structured child it meets
|
// one level deep); stops at the first structured child it meets
|
||||||
static bool all_children_leaves(const basic_json& v) noexcept
|
static bool all_children_leaves(const basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
|
// array_t only needs forward iterators (see custom_array_type)
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
const auto& a = *v.m_data.m_value.array;
|
const auto& a = *v.m_data.m_value.array;
|
||||||
for (auto it = a.rbegin(); it != a.rend(); ++it)
|
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
||||||
{
|
{
|
||||||
if (!has_no_children(*it))
|
return has_no_children(el);
|
||||||
{
|
});
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
for (const auto& el : *v.m_data.m_value.object)
|
const auto& o = *v.m_data.m_value.object;
|
||||||
|
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
||||||
{
|
{
|
||||||
if (!has_no_children(el.second))
|
return has_no_children(el.second);
|
||||||
{
|
});
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The walk in destroy_container() may take the children of a
|
// The walk in destroy_container() may take the children of a
|
||||||
@@ -1230,7 +1225,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements<O>::value, int > = 0 >
|
template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements<O>::value, int > = 0 >
|
||||||
static constexpr std::size_t object_capacity() noexcept
|
std::size_t object_capacity() const noexcept
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1242,7 +1237,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements<O>::value && !detail::is_nlohmann_ordered_map<O>::value, int > = 0 >
|
template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements<O>::value && !detail::is_nlohmann_ordered_map<O>::value, int > = 0 >
|
||||||
static constexpr std::size_t object_capacity() noexcept
|
std::size_t object_capacity() const noexcept
|
||||||
{
|
{
|
||||||
return detail::unknown_size();
|
return detail::unknown_size();
|
||||||
}
|
}
|
||||||
@@ -3043,18 +3038,21 @@ public:
|
|||||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||||
static void throw_iterator_range_of_null(const basic_json* obj)
|
static void throw_iterator_range_of_null(const basic_json* obj)
|
||||||
{
|
{
|
||||||
|
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
|
||||||
JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj));
|
JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||||
static void throw_incompatible_reference_type(const basic_json& obj)
|
static void throw_incompatible_reference_type(const basic_json& obj)
|
||||||
{
|
{
|
||||||
|
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
|
||||||
JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj));
|
JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||||
static void throw_array_index_exceeds_size_type(size_type idx)
|
static void throw_array_index_exceeds_size_type(size_type idx)
|
||||||
{
|
{
|
||||||
|
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
|
||||||
JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type")));
|
JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3062,6 +3060,8 @@ public:
|
|||||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||||
static void throw_array_index_out_of_range(size_type idx, const basic_json& j)
|
static void throw_array_index_out_of_range(size_type idx, const basic_json& j)
|
||||||
{
|
{
|
||||||
|
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
|
||||||
|
static_cast<void>(j);
|
||||||
JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j));
|
JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3070,6 +3070,8 @@ public:
|
|||||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||||
static void throw_key_not_found(const KeyType& key, const basic_json& j)
|
static void throw_key_not_found(const KeyType& key, const basic_json& j)
|
||||||
{
|
{
|
||||||
|
static_cast<void>(key); // unused when JSON_NOEXCEPTION is defined
|
||||||
|
static_cast<void>(j);
|
||||||
JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j));
|
JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4893,19 +4895,19 @@ public:
|
|||||||
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||||
{
|
{
|
||||||
// insert only works for arrays
|
// insert only works for arrays
|
||||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||||
{
|
{
|
||||||
// check if iterator pos fits to this JSON value
|
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
|
||||||
{
|
|
||||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert to array and return iterator
|
|
||||||
return insert_iterator(pos, val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
// check if iterator pos fits to this JSON value
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||||
|
{
|
||||||
|
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert to array and return iterator
|
||||||
|
return insert_iterator(pos, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief inserts element into array
|
/// @brief inserts element into array
|
||||||
@@ -4913,21 +4915,21 @@ public:
|
|||||||
iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param)
|
iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param)
|
||||||
{
|
{
|
||||||
// insert only works for arrays
|
// insert only works for arrays
|
||||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||||
{
|
{
|
||||||
// check if iterator pos fits to this JSON value
|
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
|
||||||
{
|
|
||||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// moving into a local first keeps this safe even if val aliases
|
|
||||||
// an element of this array
|
|
||||||
basic_json tmp(std::move(val));
|
|
||||||
return insert_iterator(pos, std::move(tmp));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
// check if iterator pos fits to this JSON value
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||||
|
{
|
||||||
|
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// moving into a local first keeps this safe even if val aliases
|
||||||
|
// an element of this array
|
||||||
|
basic_json tmp(std::move(val));
|
||||||
|
return insert_iterator(pos, std::move(tmp));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief inserts copies of element into array
|
/// @brief inserts copies of element into array
|
||||||
@@ -4935,19 +4937,19 @@ public:
|
|||||||
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||||
{
|
{
|
||||||
// insert only works for arrays
|
// insert only works for arrays
|
||||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||||
{
|
{
|
||||||
// check if iterator pos fits to this JSON value
|
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
|
||||||
{
|
|
||||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert to array and return iterator
|
|
||||||
return insert_iterator(pos, cnt, val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
// check if iterator pos fits to this JSON value
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||||
|
{
|
||||||
|
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert to array and return iterator
|
||||||
|
return insert_iterator(pos, cnt, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief inserts range of elements into array
|
/// @brief inserts range of elements into array
|
||||||
@@ -5453,7 +5455,7 @@ public:
|
|||||||
// inline fast path for two values of the same non-owning scalar type; anything
|
// inline fast path for two values of the same non-owning scalar type; anything
|
||||||
// else falls through to the out-of-line general comparison
|
// else falls through to the out-of-line general comparison
|
||||||
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
|
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
|
||||||
if (lhs.m_data.m_type == rhs.m_data.m_type) \
|
if (lhs.m_data.m_type == rhs.m_data.m_type) /* NOLINT(readability/braces) */ \
|
||||||
{ \
|
{ \
|
||||||
switch (lhs.m_data.m_type) \
|
switch (lhs.m_data.m_type) \
|
||||||
{ \
|
{ \
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||||
|
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
|||||||
Generated
+148
-653
File diff suppressed because it is too large.
Load diff
@@ -285,10 +285,6 @@ if (JSON_TestSimdutf)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# test-file_input checks from a second thread that parsing from a FILE* releases the file's lock
|
|
||||||
find_package(Threads REQUIRED)
|
|
||||||
json_test_set_test_options("test-file_input;test-file_input_unlocked_io_disabled" LINK_LIBRARIES Threads::Threads)
|
|
||||||
|
|
||||||
# *DO* use json_test_set_test_options() above this line
|
# *DO* use json_test_set_test_options() above this line
|
||||||
|
|
||||||
json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}")
|
json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}")
|
||||||
@@ -373,33 +369,6 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
|||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
|
|
||||||
# test reading from a std::istream again without scanning the get area in bulk
|
|
||||||
json_test_set_test_options(test-stream-bulk-scan_disabled
|
|
||||||
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
|
||||||
)
|
|
||||||
json_test_add_test_for(src/unit-stream-bulk-scan.cpp
|
|
||||||
NAME test-stream-bulk-scan_disabled
|
|
||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
|
||||||
)
|
|
||||||
|
|
||||||
# test reading from a std::istream again with JSON_PRECISE_STREAM_POSITION
|
|
||||||
json_test_set_test_options(test-stream-bulk-scan_precise
|
|
||||||
COMPILE_DEFINITIONS JSON_PRECISE_STREAM_POSITION=1
|
|
||||||
)
|
|
||||||
json_test_add_test_for(src/unit-stream-bulk-scan.cpp
|
|
||||||
NAME test-stream-bulk-scan_precise
|
|
||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
|
||||||
)
|
|
||||||
|
|
||||||
# test reading from a FILE* again with the locking stdio functions
|
|
||||||
json_test_set_test_options(test-file_input_unlocked_io_disabled
|
|
||||||
COMPILE_DEFINITIONS JSON_DISABLE_UNLOCKED_FILE_IO=1
|
|
||||||
)
|
|
||||||
json_test_add_test_for(src/unit-file_input.cpp
|
|
||||||
NAME test-file_input_unlocked_io_disabled
|
|
||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
|
||||||
)
|
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# *DO NOT* use json_test_set_test_options() below this line
|
# *DO NOT* use json_test_set_test_options() below this line
|
||||||
|
|||||||
@@ -8,13 +8,11 @@
|
|||||||
|
|
||||||
#include <benchmark/benchmark.h>
|
#include <benchmark/benchmark.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <array>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <deque>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <array>
|
||||||
|
#include <deque>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <test_data.hpp>
|
#include <test_data.hpp>
|
||||||
|
|
||||||
@@ -53,65 +51,6 @@ BENCHMARK_CAPTURE(ParseFile, signed_ints, TEST_DATA_DIRECTORY "/regression
|
|||||||
BENCHMARK_CAPTURE(ParseFile, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json");
|
BENCHMARK_CAPTURE(ParseFile, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json");
|
||||||
BENCHMARK_CAPTURE(ParseFile, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json");
|
BENCHMARK_CAPTURE(ParseFile, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json");
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
// parse JSON from a FILE*
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
static void ParseFILE(benchmark::State& state, const char* filename)
|
|
||||||
{
|
|
||||||
while (state.KeepRunning())
|
|
||||||
{
|
|
||||||
state.PauseTiming();
|
|
||||||
std::FILE* f = std::fopen(filename, "rb");
|
|
||||||
auto* j = new json();
|
|
||||||
state.ResumeTiming();
|
|
||||||
|
|
||||||
*j = json::parse(f);
|
|
||||||
|
|
||||||
state.PauseTiming();
|
|
||||||
std::fclose(f);
|
|
||||||
delete j;
|
|
||||||
state.ResumeTiming();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
|
||||||
state.SetBytesProcessed(state.iterations() * file.tellg());
|
|
||||||
}
|
|
||||||
BENCHMARK_CAPTURE(ParseFILE, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
|
||||||
BENCHMARK_CAPTURE(ParseFILE, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
|
||||||
BENCHMARK_CAPTURE(ParseFILE, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
// read many small values from one stream (NDJSON)
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
static void ParseStreamValues(benchmark::State& state, bool numbers)
|
|
||||||
{
|
|
||||||
std::string input;
|
|
||||||
for (int i = 0; i < 10000; ++i)
|
|
||||||
{
|
|
||||||
input += numbers ? std::to_string(i * 7919) + " "
|
|
||||||
: R"({"id":)" + std::to_string(i) + R"(,"name":"user)" + std::to_string(i) + R"(","active":true,"score":)" + std::to_string(i * 0.25) + "}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto _ : state)
|
|
||||||
{
|
|
||||||
std::istringstream is(input);
|
|
||||||
json j;
|
|
||||||
std::size_t count = 0;
|
|
||||||
while (!(is >> std::ws).eof())
|
|
||||||
{
|
|
||||||
is >> j;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
benchmark::DoNotOptimize(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.SetBytesProcessed(static_cast<int64_t>(state.iterations() * input.size()));
|
|
||||||
}
|
|
||||||
BENCHMARK_CAPTURE(ParseStreamValues, ndjson, false);
|
|
||||||
BENCHMARK_CAPTURE(ParseStreamValues, numbers, true);
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// parse JSON from string
|
// parse JSON from string
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -15,12 +15,9 @@ using nlohmann::json;
|
|||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cmath> // isfinite
|
|
||||||
#include <cstdlib> // strtod
|
#include <cstdlib> // strtod
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <initializer_list> // initializer_list
|
|
||||||
#include <list> // list
|
|
||||||
#include <map> // map
|
#include <map> // map
|
||||||
#include <random> // mt19937
|
#include <random> // mt19937
|
||||||
#include <sstream> // stringstream
|
#include <sstream> // stringstream
|
||||||
@@ -335,109 +332,6 @@ TEST_CASE("lexer number fast path")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("digits accumulated while scanning")
|
|
||||||
{
|
|
||||||
// The fast path accumulates the digits while it validates them, eight
|
|
||||||
// at a time where possible, and converts tokens with at most 19
|
|
||||||
// digits (floats) or 18 digits (integers) from that value. Every digit
|
|
||||||
// count from 1 to 25, in the integer and in the fraction part, with
|
|
||||||
// and without sign and exponent, must give the byte path's result. A
|
|
||||||
// std::list has no contiguous storage, so parsing it takes the byte
|
|
||||||
// path.
|
|
||||||
// type and value, or the error (values beyond the range of double
|
|
||||||
// are rejected with out_of_range.406 on both paths)
|
|
||||||
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
|
|
||||||
{
|
|
||||||
JSON_TRY
|
|
||||||
{
|
|
||||||
const std::list<char> chars(doc.begin(), doc.end());
|
|
||||||
const json j = byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc);
|
|
||||||
return std::string(j[0].type_name()) + '|' + j.dump();
|
|
||||||
}
|
|
||||||
JSON_CATCH (const json::exception & e)
|
|
||||||
{
|
|
||||||
return e.what();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// the concatenation of @a parts
|
|
||||||
const auto join = [](std::initializer_list<std::string> parts)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
for (const auto& part : parts)
|
|
||||||
{
|
|
||||||
result += part;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<std::string> numbers;
|
|
||||||
for (std::size_t digits = 1; digits <= 25; ++digits)
|
|
||||||
{
|
|
||||||
std::string run;
|
|
||||||
for (std::size_t i = 0; i < digits; ++i)
|
|
||||||
{
|
|
||||||
run += static_cast<char>('1' + static_cast<char>((i * 7) % 9));
|
|
||||||
}
|
|
||||||
const std::string nines(digits, '9');
|
|
||||||
for (const char* sign_text :
|
|
||||||
{"", "-"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const std::string sign = sign_text;
|
|
||||||
numbers.push_back(join({sign, run}));
|
|
||||||
numbers.push_back(join({sign, nines}));
|
|
||||||
numbers.push_back(join({sign, "1", std::string(digits - 1, '0')}));
|
|
||||||
numbers.push_back(join({sign, "0.", run}));
|
|
||||||
numbers.push_back(join({sign, "0.", std::string(digits, '0'), "1"}));
|
|
||||||
numbers.push_back(join({sign, run, ".", run}));
|
|
||||||
numbers.push_back(join({sign, run, "e-", std::to_string(digits)}));
|
|
||||||
numbers.push_back(join({sign, "1.", run, "E+", std::to_string(digits * 13)}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// exponents beyond every range, and zeros
|
|
||||||
numbers.insert(numbers.end(), {"1e400", "1e-400", "-1e99999999999999999999", "1e-99999999999999999999",
|
|
||||||
"0e99999999999999999999", "0.0e-5", "-0.000", "123456789e-0000000000000000000000330"
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const auto& n : numbers)
|
|
||||||
{
|
|
||||||
CAPTURE(n)
|
|
||||||
const std::string doc = "[" + n + "]";
|
|
||||||
const std::string a = outcome(doc, false);
|
|
||||||
CHECK(a == outcome(doc, true));
|
|
||||||
const double expected = std::strtod(n.c_str(), nullptr);
|
|
||||||
if (a.compare(0, 6, "number") == 0 && std::isfinite(expected) && n.find_first_of(".eE") != std::string::npos)
|
|
||||||
{
|
|
||||||
CHECK(json::parse(doc)[0].get<double>() == expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("integers that do not fit a narrower type become floats")
|
|
||||||
{
|
|
||||||
// up to 18 digits, the fast path stores the accumulated value without
|
|
||||||
// an overflow check; a narrower number_integer_t/number_unsigned_t
|
|
||||||
// must still see the value rejected and converted as a float
|
|
||||||
using json32 = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
|
|
||||||
for (const char* n :
|
|
||||||
{"4294967295", "4294967296", "2147483647", "-2147483648", "-2147483649", "99999999999", "-99999999999"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(n)
|
|
||||||
const std::string doc = std::string("[") + n + "]";
|
|
||||||
const std::list<char> chars(doc.begin(), doc.end());
|
|
||||||
const json32 a = json32::parse(doc);
|
|
||||||
const json32 b = json32::parse(chars.begin(), chars.end());
|
|
||||||
CHECK(a[0].type() == b[0].type());
|
|
||||||
CHECK(a == b);
|
|
||||||
}
|
|
||||||
CHECK(json32::parse("[4294967295]")[0].is_number_unsigned());
|
|
||||||
CHECK(json32::parse("[4294967296]")[0].is_number_float());
|
|
||||||
CHECK(json32::parse("[-2147483648]")[0].is_number_integer());
|
|
||||||
CHECK(json32::parse("[-2147483649]")[0].is_number_float());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("token type classification")
|
SECTION("token type classification")
|
||||||
{
|
{
|
||||||
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
||||||
|
|||||||
@@ -198,11 +198,11 @@ TEST_CASE("copy constructor and destructor across value types")
|
|||||||
const json src_uint = 7u;
|
const json src_uint = 7u;
|
||||||
const json src_float = 2.5;
|
const json src_float = 2.5;
|
||||||
|
|
||||||
json c_null(src_null);
|
json c_null(src_null); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||||
json c_bool(src_bool);
|
json c_bool(src_bool); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||||
json c_int(src_int);
|
json c_int(src_int); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||||
json c_uint(src_uint);
|
json c_uint(src_uint); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||||
json c_float(src_float);
|
json c_float(src_float); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||||
|
|
||||||
CHECK(c_null.is_null());
|
CHECK(c_null.is_null());
|
||||||
CHECK(c_bool == true);
|
CHECK(c_bool == true);
|
||||||
@@ -300,12 +300,12 @@ TEST_CASE("copy constructor and destructor across value types")
|
|||||||
REQUIRE(v.size() == 70);
|
REQUIRE(v.size() == 70);
|
||||||
|
|
||||||
std::vector<json> copy(v);
|
std::vector<json> copy(v);
|
||||||
CHECK(copy == v);
|
CHECK((copy == v));
|
||||||
|
|
||||||
// mutating the copy leaves the original alone
|
// mutating the copy leaves the original alone
|
||||||
copy[5] = "changed";
|
copy[5] = "changed";
|
||||||
copy[6].push_back(1);
|
copy[6].push_back(1);
|
||||||
CHECK(copy != v);
|
CHECK((copy != v));
|
||||||
CHECK(v[5] == std::string(5, 'x'));
|
CHECK(v[5] == std::string(5, 'x'));
|
||||||
CHECK(v[6].size() == 3);
|
CHECK(v[6].size() == 3);
|
||||||
|
|
||||||
|
|||||||
@@ -817,8 +817,8 @@ template<class Key, class T, class IgnoredLess = std::less<Key>,
|
|||||||
struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>
|
struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>
|
||||||
{
|
{
|
||||||
using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>;
|
using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>;
|
||||||
using typename base_type::iterator;
|
using iterator = typename base_type::iterator;
|
||||||
using typename base_type::Container;
|
using Container = typename base_type::Container;
|
||||||
using base_type::base_type;
|
using base_type::base_type;
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <fstream>
|
|
||||||
#include <memory>
|
|
||||||
#include <random>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// Parsing from a FILE* locks the file once and reads it with the unlocked
|
|
||||||
// stdio functions (see JSON_DISABLE_UNLOCKED_FILE_IO). These tests pin that
|
|
||||||
// this is not observable: the results are the same, the file is unlocked
|
|
||||||
// again when parsing ends - also with an exception - and the same thread can
|
|
||||||
// keep using the file while it is locked, e.g., from a SAX callback.
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a file name of its own for every run: the test runs for several C++
|
|
||||||
// standards and configurations at the same time, in the same directory
|
|
||||||
std::string temp_file_name()
|
|
||||||
{
|
|
||||||
std::random_device rd;
|
|
||||||
return "unit-file_input-" + std::to_string(rd()) + ".tmp";
|
|
||||||
}
|
|
||||||
|
|
||||||
using file_ptr = std::unique_ptr<std::FILE, int (*)(std::FILE*)>;
|
|
||||||
|
|
||||||
// write @a content to the file @a name and open it for reading
|
|
||||||
file_ptr open_with(const char* name, const std::string& content)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::ofstream file(name, std::ios::binary);
|
|
||||||
file << content;
|
|
||||||
}
|
|
||||||
return {std::fopen(name, "rb"), &std::fclose};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The stdio lock is recursive, so whether parsing released it can only be
|
|
||||||
// seen from another thread: ftrylockfile() fails there while this thread
|
|
||||||
// holds the lock. ftrylockfile() is POSIX; elsewhere, the check is skipped.
|
|
||||||
#if defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
|
|
||||||
void check_unlocked(std::FILE* f)
|
|
||||||
{
|
|
||||||
int result = -1;
|
|
||||||
std::thread other([f, &result]
|
|
||||||
{
|
|
||||||
result = ftrylockfile(f);
|
|
||||||
if (result == 0)
|
|
||||||
{
|
|
||||||
funlockfile(f);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
other.join();
|
|
||||||
CHECK(result == 0);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
void check_unlocked(std::FILE* /*f*/) {}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// SAX consumer that also reads the position of the file it is parsing, which
|
|
||||||
// takes the file's lock again on the same thread
|
|
||||||
class position_reading_sax : public nlohmann::json_sax<json>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit position_reading_sax(std::FILE* f) : m_file(f) {}
|
|
||||||
position_reading_sax(const position_reading_sax&) = delete;
|
|
||||||
position_reading_sax(position_reading_sax&&) = delete;
|
|
||||||
position_reading_sax& operator=(const position_reading_sax&) = delete;
|
|
||||||
position_reading_sax& operator=(position_reading_sax&&) = delete;
|
|
||||||
~position_reading_sax() override = default;
|
|
||||||
|
|
||||||
bool null() override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool boolean(bool /*val*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool number_integer(number_integer_t /*val*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool string(string_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool binary(binary_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool start_object(std::size_t /*elements*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool key(string_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool end_object() override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool start_array(std::size_t /*elements*/) override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool end_array() override
|
|
||||||
{
|
|
||||||
return record();
|
|
||||||
}
|
|
||||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const nlohmann::detail::exception& /*ex*/) override
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<long>& positions() const
|
|
||||||
{
|
|
||||||
return m_positions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool record()
|
|
||||||
{
|
|
||||||
m_positions.push_back(std::ftell(m_file));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::FILE* m_file;
|
|
||||||
std::vector<long> m_positions{}; // NOLINT(readability-redundant-member-init)
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("parsing from a FILE*")
|
|
||||||
{
|
|
||||||
const std::string temp_name = temp_file_name();
|
|
||||||
const char* const name = temp_name.c_str();
|
|
||||||
const std::string input = "{\"a\": [1, -2.5, \"three\", true, null], \"b\": {\"c\": \"caf\xc3\xa9\"}}";
|
|
||||||
|
|
||||||
SECTION("values are the same as when parsing from a string")
|
|
||||||
{
|
|
||||||
const file_ptr f = open_with(name, input);
|
|
||||||
REQUIRE(f != nullptr);
|
|
||||||
CHECK(json::parse(f.get()) == json::parse(input));
|
|
||||||
// the whole input was consumed
|
|
||||||
std::array<char, 8> rest{};
|
|
||||||
CHECK(std::fgets(rest.data(), static_cast<int>(rest.size()), f.get()) == nullptr);
|
|
||||||
check_unlocked(f.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the file is unlocked after a parse error")
|
|
||||||
{
|
|
||||||
const file_ptr f = open_with(name, "[1, 2, x]");
|
|
||||||
REQUIRE(f != nullptr);
|
|
||||||
json _;
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::parse(f.get()),
|
|
||||||
"[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - invalid literal; last read: '2, x'",
|
|
||||||
json::parse_error&);
|
|
||||||
check_unlocked(f.get());
|
|
||||||
|
|
||||||
// the file is still usable: the rest of the input is there
|
|
||||||
std::array<char, 8> rest{};
|
|
||||||
CHECK(std::fgets(rest.data(), static_cast<int>(rest.size()), f.get()) != nullptr);
|
|
||||||
CHECK(std::string(rest.data()) == "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the file can be parsed again and read with stdio afterwards")
|
|
||||||
{
|
|
||||||
const file_ptr f = open_with(name, "[1] [2] tail");
|
|
||||||
REQUIRE(f != nullptr);
|
|
||||||
|
|
||||||
// sax_parse with strict == false stops after the first value; an array
|
|
||||||
// ends with its closing bracket, so the space after it is not read
|
|
||||||
json j;
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax1(j);
|
|
||||||
CHECK(json::sax_parse(f.get(), &sax1, json::input_format_t::json, false));
|
|
||||||
CHECK(j == json({1}));
|
|
||||||
check_unlocked(f.get());
|
|
||||||
|
|
||||||
json k;
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax2(k);
|
|
||||||
CHECK(json::sax_parse(f.get(), &sax2, json::input_format_t::json, false));
|
|
||||||
CHECK(k == json({2}));
|
|
||||||
check_unlocked(f.get());
|
|
||||||
|
|
||||||
std::array<char, 8> rest{};
|
|
||||||
CHECK(std::fgets(rest.data(), static_cast<int>(rest.size()), f.get()) != nullptr);
|
|
||||||
CHECK(std::string(rest.data()) == " tail");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the same thread can use the file while it is parsed")
|
|
||||||
{
|
|
||||||
const file_ptr f = open_with(name, "[10, 20, 30]");
|
|
||||||
REQUIRE(f != nullptr);
|
|
||||||
position_reading_sax sax(f.get());
|
|
||||||
CHECK(json::sax_parse(f.get(), &sax));
|
|
||||||
// start_array, three numbers, end_array
|
|
||||||
CHECK(sax.positions().size() == 5);
|
|
||||||
CHECK(sax.positions().front() == 1);
|
|
||||||
check_unlocked(f.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("binary formats read from a FILE*")
|
|
||||||
{
|
|
||||||
const json j = {{"string", std::string(300, 'x')}, {"array", {1, 2, 3}}, {"number", 1.5}};
|
|
||||||
const std::vector<std::uint8_t> cbor = json::to_cbor(j);
|
|
||||||
const file_ptr f = open_with(name, std::string(reinterpret_cast<const char*>(cbor.data()), cbor.size()));
|
|
||||||
REQUIRE(f != nullptr);
|
|
||||||
CHECK(json::from_cbor(f.get()) == j);
|
|
||||||
check_unlocked(f.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
static_cast<void>(std::remove(name));
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,474 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// The fast paths for contiguous input (bulk string scanning, SWAR searches,
|
||||||
|
// UTF-8 prefix validation, the 8-digit number loop, the 8-byte indentation
|
||||||
|
// skip, the BSON/BON8 bulk string readers) load several bytes at a time. This
|
||||||
|
// test places every input directly in front of (or directly behind) an
|
||||||
|
// inaccessible page, so reading even one byte outside the input crashes, with
|
||||||
|
// or without sanitizers. ASan alone misses over-reads into the slack of the
|
||||||
|
// same heap allocation.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#define JSON_TEST_GUARD_PAGE_WINDOWS
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#elif defined(__unix__) || defined(__APPLE__)
|
||||||
|
#define JSON_TEST_GUARD_PAGE_POSIX
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS) || defined(JSON_TEST_GUARD_PAGE_POSIX)
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Three pages: an inaccessible one, a readable one, an inaccessible one. An
|
||||||
|
// input is copied either to the end of the middle page (any read past its last
|
||||||
|
// byte faults) or to its start (any read before its first byte faults).
|
||||||
|
class guarded_buffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
guarded_buffer()
|
||||||
|
: page(page_size())
|
||||||
|
{
|
||||||
|
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||||
|
void* const p = VirtualAlloc(nullptr, 3 * page, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
|
||||||
|
REQUIRE(p != nullptr);
|
||||||
|
base = static_cast<unsigned char*>(p);
|
||||||
|
DWORD old_protect = 0;
|
||||||
|
REQUIRE(VirtualProtect(base + page, page, PAGE_READWRITE, &old_protect) != 0);
|
||||||
|
#else
|
||||||
|
void* const p = mmap(nullptr, 3 * page, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||||
|
REQUIRE(p != MAP_FAILED);
|
||||||
|
base = static_cast<unsigned char*>(p);
|
||||||
|
REQUIRE(mprotect(base + page, page, PROT_READ | PROT_WRITE) == 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
~guarded_buffer()
|
||||||
|
{
|
||||||
|
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||||
|
VirtualFree(base, 0, MEM_RELEASE);
|
||||||
|
#else
|
||||||
|
munmap(base, 3 * page);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
guarded_buffer(const guarded_buffer&) = delete;
|
||||||
|
guarded_buffer& operator=(const guarded_buffer&) = delete;
|
||||||
|
guarded_buffer(guarded_buffer&&) = delete;
|
||||||
|
guarded_buffer& operator=(guarded_buffer&&) = delete;
|
||||||
|
|
||||||
|
// copies @a input so its last byte is the last readable byte
|
||||||
|
const std::uint8_t* at_end(const std::string& input)
|
||||||
|
{
|
||||||
|
REQUIRE(input.size() <= page);
|
||||||
|
unsigned char* const first = base + (2 * page) - input.size();
|
||||||
|
std::memcpy(first, input.data(), input.size());
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copies @a input so its first byte is the first readable byte
|
||||||
|
const std::uint8_t* at_start(const std::string& input)
|
||||||
|
{
|
||||||
|
REQUIRE(input.size() <= page);
|
||||||
|
unsigned char* const first = base + page;
|
||||||
|
std::memcpy(first, input.data(), input.size());
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::size_t page_size()
|
||||||
|
{
|
||||||
|
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||||
|
SYSTEM_INFO info;
|
||||||
|
GetSystemInfo(&info);
|
||||||
|
return static_cast<std::size_t>(info.dwPageSize);
|
||||||
|
#else
|
||||||
|
return static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t page = 0;
|
||||||
|
unsigned char* base = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// the serialization of the result of @a f, or the exception message; @a f
|
||||||
|
// takes the allow_exceptions flag, which is false without exceptions, where
|
||||||
|
// an error yields a discarded value instead
|
||||||
|
template<typename F>
|
||||||
|
std::string outcome(F f)
|
||||||
|
{
|
||||||
|
#if defined(JSON_NOEXCEPTION)
|
||||||
|
return "value: " + f(false).dump(-1, ' ', false, json::error_handler_t::keep);
|
||||||
|
#else
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return "value: " + f(true).dump(-1, ' ', false, json::error_handler_t::keep);
|
||||||
|
}
|
||||||
|
catch (const json::exception& e)
|
||||||
|
{
|
||||||
|
return std::string("error: ") + e.what();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// a SAX handler that only counts events, so sax_parse runs without a DOM
|
||||||
|
struct counting_sax : nlohmann::json_sax<json>
|
||||||
|
{
|
||||||
|
std::size_t events = 0;
|
||||||
|
std::string error{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
|
||||||
|
bool null() override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool boolean(bool /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool number_integer(number_integer_t /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool string(string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool binary(binary_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool start_object(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool key(string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool end_object() override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool start_array(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool end_array() override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool parse_error(std::size_t position, const std::string& /*last_token*/, const nlohmann::detail::exception& ex) override
|
||||||
|
{
|
||||||
|
error = std::to_string(position) + " " + ex.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool count()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// all prefixes of each seed, so the input ends at every byte of it
|
||||||
|
std::vector<std::string> all_prefixes(const std::vector<std::string>& seeds)
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
for (const auto& seed : seeds)
|
||||||
|
{
|
||||||
|
for (std::size_t n = 0; n <= seed.size(); ++n)
|
||||||
|
{
|
||||||
|
result.push_back(seed.substr(0, n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> json_seeds()
|
||||||
|
{
|
||||||
|
std::vector<std::string> seeds =
|
||||||
|
{
|
||||||
|
// string runs: ASCII, escapes, \u escapes, surrogate pairs
|
||||||
|
"\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"",
|
||||||
|
R"("ab\ncd\"ef\\gh\u00e4ij\uD83D\uDE00kl\u12")",
|
||||||
|
// 2-, 3- and 4-byte UTF-8 sequences, then invalid UTF-8
|
||||||
|
std::string("\"\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80") + "abcdefgh\"",
|
||||||
|
"\"abcdefgh\xC3\x28 \xED\xA0\x80 \xF4\x90\x80\x80 \xF8 \xC0\xAF\"",
|
||||||
|
std::string("\"abcdefg\x01") + "abcdefgh\"",
|
||||||
|
// numbers, so the 8-digit loop runs up to the end
|
||||||
|
"12345678",
|
||||||
|
"1234567890123456",
|
||||||
|
"12345678901234567890123",
|
||||||
|
"-0.000000001234567890123456789",
|
||||||
|
"3.14159265358979323846264338327950288e+100",
|
||||||
|
"-1.2345678901234567e-12",
|
||||||
|
"1E-5",
|
||||||
|
"[1.5,2e10,-3E-5,0.000000001,123456789012345678]",
|
||||||
|
// literals
|
||||||
|
"[true,false,null]",
|
||||||
|
// comments (only parsed with ignore_comments)
|
||||||
|
"/* comment */ [1, // line comment\n 2] /**/",
|
||||||
|
// a pretty-printed document
|
||||||
|
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(4),
|
||||||
|
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(8, '\t'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// indentation runs of every length, with and without a closing bracket
|
||||||
|
for (std::size_t n = 0; n <= 20; ++n)
|
||||||
|
{
|
||||||
|
seeds.push_back("[\n" + std::string(n, ' ') + "1,\n" + std::string(n, ' ') + "2\n]");
|
||||||
|
}
|
||||||
|
|
||||||
|
return all_prefixes(seeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a value whose binary serializations contain long strings and keys, numbers
|
||||||
|
// of every width, and nested containers
|
||||||
|
json binary_document()
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
j["a key that is longer than eight bytes"] = "a string value that is longer than eight bytes";
|
||||||
|
j["\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80"] = "\xF0\x9F\x98\x80\xE2\x82\xAC\xC3\xA4 utf-8 0123456789";
|
||||||
|
j["numbers"] = {0, 1, -1, 255, -256, 65536, -2147483649LL, 4294967296ULL, 1.5, -2.25e100};
|
||||||
|
j["nested"] = {{"x", {{"y", {true, false, nullptr}}}}};
|
||||||
|
j["s"] = "";
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// readers for check_binary(), callable with a container or an iterator range
|
||||||
|
// followed by the allow_exceptions flag
|
||||||
|
#define JSON_TEST_GUARD_PAGE_READER(name, from) \
|
||||||
|
struct name \
|
||||||
|
{ \
|
||||||
|
template<typename... Args> \
|
||||||
|
json operator()(bool allow_exceptions, Args&& ... args) const \
|
||||||
|
{ \
|
||||||
|
return json::from(std::forward<Args>(args)..., true, allow_exceptions); \
|
||||||
|
} \
|
||||||
|
};
|
||||||
|
JSON_TEST_GUARD_PAGE_READER(read_bson, from_bson)
|
||||||
|
JSON_TEST_GUARD_PAGE_READER(read_bon8, from_bon8)
|
||||||
|
JSON_TEST_GUARD_PAGE_READER(read_cbor, from_cbor)
|
||||||
|
JSON_TEST_GUARD_PAGE_READER(read_msgpack, from_msgpack)
|
||||||
|
JSON_TEST_GUARD_PAGE_READER(read_ubjson, from_ubjson)
|
||||||
|
JSON_TEST_GUARD_PAGE_READER(read_bjdata, from_bjdata)
|
||||||
|
#undef JSON_TEST_GUARD_PAGE_READER
|
||||||
|
|
||||||
|
// reads every prefix of @a bytes at both ends of the guarded page and from a
|
||||||
|
// std::string, which must give the same outcome
|
||||||
|
template<typename Reader>
|
||||||
|
void check_binary(guarded_buffer& guard, const std::vector<std::uint8_t>& bytes, Reader parse)
|
||||||
|
{
|
||||||
|
for (const auto& input : all_prefixes({std::string(bytes.begin(), bytes.end())}))
|
||||||
|
{
|
||||||
|
CAPTURE(input)
|
||||||
|
const std::string expected = outcome([&](bool allow)
|
||||||
|
{
|
||||||
|
return parse(allow, input);
|
||||||
|
});
|
||||||
|
for (int placement = 0; placement < 2; ++placement)
|
||||||
|
{
|
||||||
|
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||||
|
CHECK(outcome([&](bool allow)
|
||||||
|
{
|
||||||
|
return parse(allow, first, first + input.size());
|
||||||
|
}) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse_float_native() at both ends of the guarded page, compared bit for bit
|
||||||
|
// with converting the same token from a std::string
|
||||||
|
template<typename FloatType>
|
||||||
|
void check_float_native(guarded_buffer& guard, const std::string& token)
|
||||||
|
{
|
||||||
|
const std::size_t decimal_point = token.find('.');
|
||||||
|
const std::size_t exponent = token.find_first_of("eE");
|
||||||
|
const std::size_t mantissa_end = exponent == std::string::npos ? token.size() : exponent;
|
||||||
|
const FloatType expected = nlohmann::detail::parse_float_native<FloatType>(token.data(), token.data() + token.size(), decimal_point, mantissa_end);
|
||||||
|
for (int placement = 0; placement < 2; ++placement)
|
||||||
|
{
|
||||||
|
const char* const first = reinterpret_cast<const char*>(placement == 0 ? guard.at_end(token) : guard.at_start(token));
|
||||||
|
const FloatType value = nlohmann::detail::parse_float_native<FloatType>(first, first + token.size(), decimal_point, mantissa_end);
|
||||||
|
CHECK(std::memcmp(&value, &expected, sizeof(value)) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("guard page")
|
||||||
|
{
|
||||||
|
guarded_buffer guard;
|
||||||
|
|
||||||
|
SECTION("string scanning")
|
||||||
|
{
|
||||||
|
// fills that drive each function to a different stop
|
||||||
|
const std::vector<std::string> fills =
|
||||||
|
{
|
||||||
|
std::string(64, 'a'),
|
||||||
|
std::string(64, '"'),
|
||||||
|
std::string(64, '\\'),
|
||||||
|
std::string(64, '\x01'),
|
||||||
|
std::string(64, '\x7F'),
|
||||||
|
std::string(64, '\xFF'),
|
||||||
|
};
|
||||||
|
std::vector<std::string> patterns = fills;
|
||||||
|
{
|
||||||
|
std::string utf8;
|
||||||
|
while (utf8.size() < 64)
|
||||||
|
{
|
||||||
|
utf8 += "\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80" "ab";
|
||||||
|
}
|
||||||
|
patterns.push_back(utf8);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::string bytes;
|
||||||
|
for (unsigned int i = 0; i < 64; ++i)
|
||||||
|
{
|
||||||
|
bytes.push_back(static_cast<char>((i * 37u) & 0xFFu));
|
||||||
|
}
|
||||||
|
patterns.push_back(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& pattern : patterns)
|
||||||
|
{
|
||||||
|
for (std::size_t n = 0; n <= 64; ++n)
|
||||||
|
{
|
||||||
|
const std::string input = pattern.substr(0, n);
|
||||||
|
for (int placement = 0; placement < 2; ++placement)
|
||||||
|
{
|
||||||
|
const unsigned char* const p = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||||
|
|
||||||
|
CHECK(nlohmann::detail::find_string_special(p, n) <= n);
|
||||||
|
CHECK(nlohmann::detail::find_ascii_copyable_run(p, n) <= n);
|
||||||
|
CHECK(nlohmann::detail::valid_utf8_prefix(p, n) <= n);
|
||||||
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
|
CHECK(nlohmann::detail::find_string_delimiter(p, n) <= n);
|
||||||
|
#endif
|
||||||
|
CHECK(nlohmann::detail::string_bulk_run(p, n) <= n);
|
||||||
|
CHECK(nlohmann::detail::scalar_string_bulk_run(p, n) <= n);
|
||||||
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
CHECK(nlohmann::detail::validate_one_utf8(p + i, n - i) <= n - i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("float conversion")
|
||||||
|
{
|
||||||
|
// the lexer converts floats from its token buffer, so the JSON section
|
||||||
|
// below never places a float token at the page boundary; this calls the
|
||||||
|
// conversion directly: the 8-digit loops of parse_float_significand()
|
||||||
|
// and digit_comparison() read up to the end of the token
|
||||||
|
const std::vector<std::string> tokens = all_prefixes(
|
||||||
|
{
|
||||||
|
"1234567812345678123456781234567812345678",
|
||||||
|
"-1.2345678901234567890123456789e-123",
|
||||||
|
"0.00000000000000000000000000000000000001",
|
||||||
|
"98765432.10987654",
|
||||||
|
"123456789012345678901234567890e-30",
|
||||||
|
// halfway between 1 and the next double, then just above it
|
||||||
|
"1.00000000000000011102230246251565404236316680908203125",
|
||||||
|
"1.000000000000000111022302462515654042363166809082031250000000001",
|
||||||
|
});
|
||||||
|
for (const auto& token : tokens)
|
||||||
|
{
|
||||||
|
// only complete number tokens: the conversion expects validated input
|
||||||
|
if (!token.empty() && json::accept(token))
|
||||||
|
{
|
||||||
|
CAPTURE(token)
|
||||||
|
check_float_native<double>(guard, token);
|
||||||
|
check_float_native<float>(guard, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("JSON")
|
||||||
|
{
|
||||||
|
for (const auto& input : json_seeds())
|
||||||
|
{
|
||||||
|
CAPTURE(input)
|
||||||
|
const std::string expected = outcome([&](bool allow)
|
||||||
|
{
|
||||||
|
return json::parse(input, nullptr, allow);
|
||||||
|
});
|
||||||
|
const std::string expected_comments = outcome([&](bool allow)
|
||||||
|
{
|
||||||
|
return json::parse(input, nullptr, allow, true);
|
||||||
|
});
|
||||||
|
const bool expected_accept = json::accept(input);
|
||||||
|
counting_sax expected_sax;
|
||||||
|
const bool expected_sax_result = json::sax_parse(input, &expected_sax);
|
||||||
|
|
||||||
|
for (int placement = 0; placement < 2; ++placement)
|
||||||
|
{
|
||||||
|
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||||
|
const std::uint8_t* const last = first + input.size();
|
||||||
|
|
||||||
|
CHECK(outcome([&](bool allow)
|
||||||
|
{
|
||||||
|
return json::parse(first, last, nullptr, allow);
|
||||||
|
}) == expected);
|
||||||
|
CHECK(outcome([&](bool allow)
|
||||||
|
{
|
||||||
|
return json::parse(first, last, nullptr, allow, true);
|
||||||
|
}) == expected_comments);
|
||||||
|
CHECK(json::accept(first, last) == expected_accept);
|
||||||
|
|
||||||
|
counting_sax sax;
|
||||||
|
CHECK(json::sax_parse(first, last, &sax) == expected_sax_result);
|
||||||
|
CHECK(sax.events == expected_sax.events);
|
||||||
|
CHECK(sax.error == expected_sax.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary formats")
|
||||||
|
{
|
||||||
|
// every prefix ends inside a value, e.g., inside a BSON key or BON8
|
||||||
|
// string without its terminator
|
||||||
|
const json j = binary_document();
|
||||||
|
check_binary(guard, json::to_bson(j), read_bson());
|
||||||
|
check_binary(guard, json::to_bon8(j), read_bon8());
|
||||||
|
check_binary(guard, json::to_cbor(j), read_cbor());
|
||||||
|
check_binary(guard, json::to_msgpack(j), read_msgpack());
|
||||||
|
check_binary(guard, json::to_ubjson(j), read_ubjson());
|
||||||
|
check_binary(guard, json::to_bjdata(j), read_bjdata());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1637,6 +1637,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
|
|||||||
{
|
{
|
||||||
Json j = {1, 2, 3, 4};
|
Json j = {1, 2, 3, 4};
|
||||||
|
|
||||||
|
const std::vector<int> reversed = {4, 3, 2, 1};
|
||||||
auto it = j.end();
|
auto it = j.end();
|
||||||
std::vector<int> seen;
|
std::vector<int> seen;
|
||||||
while (it != j.begin())
|
while (it != j.begin())
|
||||||
@@ -1644,7 +1645,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
|
|||||||
--it;
|
--it;
|
||||||
seen.push_back(it->template get<int>());
|
seen.push_back(it->template get<int>());
|
||||||
}
|
}
|
||||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
CHECK(seen == reversed);
|
||||||
|
|
||||||
auto it2 = j.end();
|
auto it2 = j.end();
|
||||||
auto old = it2--;
|
auto old = it2--;
|
||||||
@@ -1656,14 +1657,14 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
|
|||||||
{
|
{
|
||||||
seen.push_back(r->template get<int>());
|
seen.push_back(r->template get<int>());
|
||||||
}
|
}
|
||||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
CHECK(seen == reversed);
|
||||||
|
|
||||||
seen.clear();
|
seen.clear();
|
||||||
for (auto r = j.crbegin(); r != j.crend(); ++r)
|
for (auto r = j.crbegin(); r != j.crend(); ++r)
|
||||||
{
|
{
|
||||||
seen.push_back(*r);
|
seen.push_back(r->template get<int>());
|
||||||
}
|
}
|
||||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
CHECK(seen == reversed);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("object: -- and rbegin/rend")
|
SECTION("object: -- and rbegin/rend")
|
||||||
|
|||||||
@@ -1230,11 +1230,11 @@ TEST_CASE("JSON pointer array index tokens match strtoull semantics")
|
|||||||
|
|
||||||
SECTION("random tokens")
|
SECTION("random tokens")
|
||||||
{
|
{
|
||||||
std::mt19937 gen(42);
|
std::mt19937 gen(42); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
|
||||||
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
|
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
|
||||||
for (int i = 0; i < 5000; ++i)
|
for (int i = 0; i < 5000; ++i)
|
||||||
{
|
{
|
||||||
std::string token(static_cast<std::size_t>(gen() % 23), '0');
|
std::string token(gen() % 23, '0');
|
||||||
for (auto& c : token)
|
for (auto& c : token)
|
||||||
{
|
{
|
||||||
c = alphabet[gen() % alphabet.size()];
|
c = alphabet[gen() % alphabet.size()];
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
@@ -73,40 +72,6 @@ class no_putback_streambuf : public std::streambuf
|
|||||||
std::size_t m_pos = 0;
|
std::size_t m_pos = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A streambuf that serves its input in get areas of a fixed size, so that the
|
|
||||||
// end of a number falls on every possible offset of a get area. The lexer scans
|
|
||||||
// the get area directly; the stream position must not depend on its size.
|
|
||||||
class chunked_streambuf : public std::streambuf
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
chunked_streambuf(std::string s, std::size_t chunk_size)
|
|
||||||
: m_data(std::move(s)), m_chunk_size(chunk_size)
|
|
||||||
{}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
int_type underflow() override
|
|
||||||
{
|
|
||||||
if (gptr() < egptr())
|
|
||||||
{
|
|
||||||
return traits_type::to_int_type(*gptr());
|
|
||||||
}
|
|
||||||
m_pos += static_cast<std::size_t>(egptr() - eback());
|
|
||||||
if (m_pos >= m_data.size())
|
|
||||||
{
|
|
||||||
setg(nullptr, nullptr, nullptr);
|
|
||||||
return traits_type::eof();
|
|
||||||
}
|
|
||||||
char* const begin = &m_data[m_pos];
|
|
||||||
setg(begin, begin, begin + (std::min)(m_chunk_size, m_data.size() - m_pos));
|
|
||||||
return traits_type::to_int_type(*gptr());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_data;
|
|
||||||
std::size_t m_pos = 0;
|
|
||||||
std::size_t m_chunk_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// read the characters that are left in a stream
|
// read the characters that are left in a stream
|
||||||
std::string remaining(std::istream& is)
|
std::string remaining(std::istream& is)
|
||||||
{
|
{
|
||||||
@@ -298,26 +263,4 @@ TEST_CASE("JSON_PRECISE_STREAM_POSITION")
|
|||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::parse(is), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::parse(is), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("the position does not depend on the get area of the streambuf")
|
|
||||||
{
|
|
||||||
const std::vector<std::size_t> chunk_sizes = {1, 2, 3, 4, 5, 7, 8, 13, 4096};
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes)
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf("12345678901234567890true\"a long string value\"-1.25e+3,[1,2]42", chunk_size);
|
|
||||||
std::istream input(&buf);
|
|
||||||
json j;
|
|
||||||
|
|
||||||
input >> j;
|
|
||||||
CHECK(j == 12345678901234567890u);
|
|
||||||
input >> j;
|
|
||||||
CHECK(j == true);
|
|
||||||
input >> j;
|
|
||||||
CHECK(j == "a long string value");
|
|
||||||
input >> j;
|
|
||||||
CHECK(j == -1250.0);
|
|
||||||
CHECK(remaining(input) == ",[1,2]42");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1374,7 +1374,13 @@ void check_dump_against_reference(const std::string& s, const bool with_strict =
|
|||||||
};
|
};
|
||||||
for (const auto error_handler : error_handlers)
|
for (const auto error_handler : error_handlers)
|
||||||
{
|
{
|
||||||
|
#if defined(JSON_NOEXCEPTION)
|
||||||
|
// a strict dump of invalid UTF-8 calls std::abort() instead of throwing
|
||||||
|
static_cast<void>(with_strict);
|
||||||
|
if (error_handler == json::error_handler_t::strict)
|
||||||
|
#else
|
||||||
if (!with_strict && error_handler == json::error_handler_t::strict)
|
if (!with_strict && error_handler == json::error_handler_t::strict)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1429,7 +1435,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
|||||||
"\xF4\x90\x80\x80",
|
"\xF4\x90\x80\x80",
|
||||||
// sequences cut short by ASCII, a control character, a quote, a
|
// sequences cut short by ASCII, a control character, a quote, a
|
||||||
// backslash, DEL, or the lead byte of a well-formed character
|
// backslash, DEL, or the lead byte of a well-formed character
|
||||||
"\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80",
|
"\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80", // NOLINT(bugprone-suspicious-missing-comma): "\xC3" "a" ends the hex escape
|
||||||
// truncated sequences
|
// truncated sequences
|
||||||
"\xC3", "\xE2\x82", "\xF0\x9F\x98"
|
"\xC3", "\xE2\x82", "\xF0\x9F\x98"
|
||||||
};
|
};
|
||||||
@@ -1489,7 +1495,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
|||||||
{
|
{
|
||||||
for (const auto& pattern : patterns)
|
for (const auto& pattern : patterns)
|
||||||
{
|
{
|
||||||
check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17 : 8));
|
check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17u : 8u));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,417 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
// the library undefines its configuration macros at the end of the header
|
|
||||||
#if defined(JSON_DISABLE_STREAM_BULK_SCAN) && JSON_DISABLE_STREAM_BULK_SCAN
|
|
||||||
#define TEST_STREAM_BULK_SCAN 0
|
|
||||||
#else
|
|
||||||
#define TEST_STREAM_BULK_SCAN 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <fstream>
|
|
||||||
#include <istream>
|
|
||||||
#include <random>
|
|
||||||
#include <sstream>
|
|
||||||
#include <streambuf>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// The lexer scans the characters in the get area of a std::istream's
|
|
||||||
// streambuf directly instead of reading them one at a time. These tests pin
|
|
||||||
// that this is not observable: for any get area size - including a streambuf
|
|
||||||
// without a get area, which still takes the character-at-a-time path - the
|
|
||||||
// parsed values, the exception messages (including the "last read" token),
|
|
||||||
// the stream state, and the characters left in the stream are the same. See
|
|
||||||
// unit-stream-bulk-scan-disabled.cpp for JSON_DISABLE_STREAM_BULK_SCAN.
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// A streambuf that serves its input in get areas of a fixed size, so that
|
|
||||||
// tokens, numbers, UTF-8 sequences, and indentation cross the end of the get
|
|
||||||
// area at every possible offset. With a size of 0, it keeps no get area at all
|
|
||||||
// and serves every character from underflow()/uflow().
|
|
||||||
class chunked_streambuf : public std::streambuf
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
chunked_streambuf(std::string s, std::size_t chunk_size)
|
|
||||||
: m_data(std::move(s)), m_chunk_size(chunk_size)
|
|
||||||
{}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
int_type underflow() override
|
|
||||||
{
|
|
||||||
if (m_chunk_size == 0)
|
|
||||||
{
|
|
||||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
|
|
||||||
}
|
|
||||||
if (gptr() < egptr())
|
|
||||||
{
|
|
||||||
return traits_type::to_int_type(*gptr());
|
|
||||||
}
|
|
||||||
// the previous get area has been consumed
|
|
||||||
m_pos += static_cast<std::size_t>(egptr() - eback());
|
|
||||||
if (m_pos >= m_data.size())
|
|
||||||
{
|
|
||||||
setg(nullptr, nullptr, nullptr);
|
|
||||||
return traits_type::eof();
|
|
||||||
}
|
|
||||||
char* const begin = &m_data[m_pos];
|
|
||||||
setg(begin, begin, begin + (std::min)(m_chunk_size, m_data.size() - m_pos));
|
|
||||||
return traits_type::to_int_type(*gptr());
|
|
||||||
}
|
|
||||||
|
|
||||||
int_type uflow() override
|
|
||||||
{
|
|
||||||
if (m_chunk_size == 0)
|
|
||||||
{
|
|
||||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
|
|
||||||
}
|
|
||||||
return std::streambuf::uflow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_data;
|
|
||||||
std::size_t m_pos = 0;
|
|
||||||
std::size_t m_chunk_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// how a test reads from the stream
|
|
||||||
enum class read_mode
|
|
||||||
{
|
|
||||||
extract, // up to four times operator>>, as for concatenated values
|
|
||||||
parse, // json::parse(stream)
|
|
||||||
parse_lenient, // json::parse(stream) with comments
|
|
||||||
};
|
|
||||||
|
|
||||||
// Read @a input from a chunked_streambuf and describe everything observable:
|
|
||||||
// the values or the exception, the stream state after each value, and the
|
|
||||||
// characters left in the stream.
|
|
||||||
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode)
|
|
||||||
{
|
|
||||||
chunked_streambuf buf(input, chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
std::string result;
|
|
||||||
for (int i = 0; i < 4; ++i)
|
|
||||||
{
|
|
||||||
json j;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (mode == read_mode::extract)
|
|
||||||
{
|
|
||||||
is >> j;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
j = json::parse(is, nullptr, true, mode == read_mode::parse_lenient);
|
|
||||||
}
|
|
||||||
result += "value " + j.dump() + "; state " + std::to_string(static_cast<unsigned int>(is.rdstate())) + "\n";
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
result += std::string("exception ") + e.what() + "\n";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (mode != read_mode::extract)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
is.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
is.clear();
|
|
||||||
result += "remaining ";
|
|
||||||
char c = 0;
|
|
||||||
while (is.get(c))
|
|
||||||
{
|
|
||||||
result += c;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get area sizes that put every boundary everywhere in short inputs, plus one
|
|
||||||
// that holds most inputs completely
|
|
||||||
std::vector<std::size_t> chunk_sizes()
|
|
||||||
{
|
|
||||||
return {1, 2, 3, 4, 5, 7, 8, 9, 13, 16, 64, 4096};
|
|
||||||
}
|
|
||||||
|
|
||||||
// valid and invalid inputs covering every token type and every bulk path:
|
|
||||||
// strings (plain runs, escapes, UTF-8), numbers (all grammar parts, too large
|
|
||||||
// for an integer), and indentation
|
|
||||||
std::vector<std::string> corpus()
|
|
||||||
{
|
|
||||||
return
|
|
||||||
{
|
|
||||||
// numbers, terminated in every way
|
|
||||||
"0", "-0", "123", "-123", "1.5", "-1.5e+10", "2E-3", "123456789012345678901234567890",
|
|
||||||
"18446744073709551615", "18446744073709551616", "-9223372036854775809",
|
|
||||||
"123 456", "123,456", "123]", "1true", "42\nfoo", "1.25e3}", "7\t8\r9",
|
|
||||||
// malformed numbers
|
|
||||||
"1.", "-", "1e", "1e+", "01", "-a", "1.x", "1ee5",
|
|
||||||
// strings
|
|
||||||
R"("")", R"("abc")", R"("a long string with a run of ordinary characters to scan in bulk")",
|
|
||||||
R"("escapes \" \\ \/ \b \f \n \r \t é 😀")",
|
|
||||||
"\"UTF-8: \xc3\xa9 \xe2\x82\xac \xf0\x9f\x98\x80 done\"",
|
|
||||||
// malformed strings
|
|
||||||
R"("unterminated)", "\"control\x01character\"", "\"ill-formed \xc3\x28 UTF-8\"", R"("bad \x escape")",
|
|
||||||
R"("\ud800 lone surrogate")",
|
|
||||||
// literals
|
|
||||||
"true", "false", "null", "tru", "nul", "falsey",
|
|
||||||
// structures
|
|
||||||
"[]", "{}", "[1,2,3]", R"({"a":1,"b":[true,false,null],"c":{"d":"e"}})", "[1,]", R"({"a" 1})", "[[[[[]]]]]",
|
|
||||||
// pretty-printed
|
|
||||||
"{\n \"key\": [\n 1,\n -2.5,\n \"three\"\n ]\n}",
|
|
||||||
"[\n \"deep\"\n ]",
|
|
||||||
"{\n \"a\": 1,\n \"b\": 2x\n}",
|
|
||||||
// concatenated values and whitespace
|
|
||||||
"1 2 3", R"("a""b")", "[1][2]", "{}{}", " \n\t 42 \n\t ", "",
|
|
||||||
// comments (only accepted by parse_lenient)
|
|
||||||
"/* comment */ 1", "// comment\n[1, /* c */ 2]",
|
|
||||||
// byte order mark
|
|
||||||
"\xef\xbb\xbf[1]",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("parsing from a std::istream scans the get area in bulk")
|
|
||||||
{
|
|
||||||
SECTION("results do not depend on the get area")
|
|
||||||
{
|
|
||||||
const std::vector<read_mode> read_modes = {read_mode::extract, read_mode::parse, read_mode::parse_lenient};
|
|
||||||
for (const auto& input : corpus())
|
|
||||||
{
|
|
||||||
for (const read_mode mode : read_modes)
|
|
||||||
{
|
|
||||||
// no get area: the character-at-a-time path
|
|
||||||
const std::string expected = observe(input, 0, mode);
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(input)
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
CAPTURE(static_cast<int>(mode))
|
|
||||||
CHECK(observe(input, chunk_size, mode) == expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("values are the same as when parsing from a string")
|
|
||||||
{
|
|
||||||
const std::string input = R"({"name": "nlohmann::json", "numbers": [1, -2, 3.5, 4e10, 18446744073709551615],)"
|
|
||||||
"\n \"text\": \"caf\xc3\xa9 \\u00e9 \\\"quoted\\\"\", \"nested\": [[{}], [[]]], \"flags\": [true, false, null]}";
|
|
||||||
const json expected = json::parse(input);
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf(input, chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
CHECK(json::parse(is) == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::istringstream ss(input);
|
|
||||||
CHECK(json::parse(ss) == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("a token that spans several get areas is reported completely")
|
|
||||||
{
|
|
||||||
// the error is found after the string has crossed many get areas; the
|
|
||||||
// token string holds everything since the start of the last token
|
|
||||||
const std::string input = R"(["this string is longer than every get area", tru])";
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf(input, chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
json j;
|
|
||||||
CHECK_THROWS_WITH_AS(is >> j,
|
|
||||||
"[json.exception.parse_error.101] parse error at line 1, column 50: syntax error while parsing value - invalid literal; last read: '\"this string is longer than every get area\", tru]'",
|
|
||||||
json::parse_error&);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string unterminated = R"("this string is longer than every get area)";
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf(unterminated, chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
json j;
|
|
||||||
CHECK_THROWS_WITH_AS(is >> j,
|
|
||||||
"[json.exception.parse_error.101] parse error at line 1, column 43: syntax error while parsing value - invalid string: missing closing quote; last read: '\"this string is longer than every get area'",
|
|
||||||
json::parse_error&);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("an error before the first token reports everything read")
|
|
||||||
{
|
|
||||||
chunked_streambuf buf(" \n x", 2);
|
|
||||||
std::istream is(&buf);
|
|
||||||
json j;
|
|
||||||
CHECK_THROWS_WITH_AS(is >> j,
|
|
||||||
"[json.exception.parse_error.101] parse error at line 2, column 3: syntax error while parsing value - invalid literal; last read: ' <U+000A> x'",
|
|
||||||
json::parse_error&);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("lines and columns are counted across get areas")
|
|
||||||
{
|
|
||||||
const std::string input = "[\n 1,\n \"two\",\n 3.0,\n x\n]";
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf(input, chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
json j;
|
|
||||||
CHECK_THROWS_WITH_AS(is >> j,
|
|
||||||
"[json.exception.parse_error.101] parse error at line 5, column 5: syntax error while parsing value - invalid literal; last read: '3.0,<U+000A> x'",
|
|
||||||
json::parse_error&);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("many values from one stream")
|
|
||||||
{
|
|
||||||
std::string input;
|
|
||||||
for (int i = 0; i < 200; ++i)
|
|
||||||
{
|
|
||||||
input += R"({"id":)" + std::to_string(i) + R"(,"name":"user)" + std::to_string(i) + R"(","score":)" + std::to_string(i) + ".5}\n";
|
|
||||||
input += std::to_string(i * 7919) + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf(input, chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
json j;
|
|
||||||
int count = 0;
|
|
||||||
while (!(is >> std::ws).eof())
|
|
||||||
{
|
|
||||||
is >> j;
|
|
||||||
if (count % 2 == 0)
|
|
||||||
{
|
|
||||||
CHECK(j["id"] == count / 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CHECK(j == (count / 2) * 7919);
|
|
||||||
}
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
CHECK(count == 400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the stream can be used after parsing")
|
|
||||||
{
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf(R"({"a": "some text"} trailing words 42)", chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
json j;
|
|
||||||
is >> j;
|
|
||||||
CHECK(j == json({{"a", "some text"}}));
|
|
||||||
|
|
||||||
std::string word;
|
|
||||||
int number = 0;
|
|
||||||
is >> word;
|
|
||||||
CHECK(word == "trailing");
|
|
||||||
is >> word >> number;
|
|
||||||
CHECK(word == "words");
|
|
||||||
CHECK(number == 42);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("std::stringstream and std::ifstream")
|
|
||||||
{
|
|
||||||
const std::string input = R"([1, 2.5, "three", {"four": [true, null]}] 5)";
|
|
||||||
|
|
||||||
std::stringstream ss(input);
|
|
||||||
json j1;
|
|
||||||
json j2;
|
|
||||||
ss >> j1 >> j2;
|
|
||||||
CHECK(j1 == json::parse(R"([1, 2.5, "three", {"four": [true, null]}])"));
|
|
||||||
CHECK(j2 == 5);
|
|
||||||
CHECK(ss.eof());
|
|
||||||
|
|
||||||
// a file name of its own for every run: the test runs for several C++
|
|
||||||
// standards and configurations at the same time, in the same directory
|
|
||||||
std::random_device rd;
|
|
||||||
const std::string temp_name = "unit-stream-bulk-scan-" + std::to_string(rd()) + ".tmp";
|
|
||||||
const char* const filename = temp_name.c_str();
|
|
||||||
{
|
|
||||||
std::ofstream file(filename);
|
|
||||||
file << input;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
std::ifstream file(filename);
|
|
||||||
json k1;
|
|
||||||
json k2;
|
|
||||||
file >> k1 >> k2;
|
|
||||||
CHECK(k1 == j1);
|
|
||||||
CHECK(k2 == j2);
|
|
||||||
}
|
|
||||||
static_cast<void>(std::remove(filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("binary formats read from a stream")
|
|
||||||
{
|
|
||||||
const json j = {{"string", std::string(300, 'x')}, {"array", {1, 2, 3}}, {"number", 1.5}};
|
|
||||||
const std::vector<std::uint8_t> cbor = json::to_cbor(j);
|
|
||||||
const std::string bytes(reinterpret_cast<const char*>(cbor.data()), cbor.size());
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf(bytes, chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
CHECK(json::from_cbor(is) == j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if TEST_STREAM_BULK_SCAN
|
|
||||||
SECTION("the adapter keeps no token unless the lexer starts one")
|
|
||||||
{
|
|
||||||
// the binary readers read through the same adapter, but never start a
|
|
||||||
// token; refilling the get area must then not collect the input
|
|
||||||
chunked_streambuf buf(std::string(1000, 'x'), 7);
|
|
||||||
std::istream is(&buf);
|
|
||||||
nlohmann::detail::input_stream_adapter ia(is);
|
|
||||||
for (int i = 0; i < 1000; ++i)
|
|
||||||
{
|
|
||||||
CHECK(ia.get_character() == 'x');
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<char> token;
|
|
||||||
ia.copy_token(token, false);
|
|
||||||
CHECK(token.empty());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("SAX parsing from a stream")
|
|
||||||
{
|
|
||||||
const std::string input = R"({"a": [1, "two", 3.5]} rest)";
|
|
||||||
for (const std::size_t chunk_size : chunk_sizes())
|
|
||||||
{
|
|
||||||
CAPTURE(chunk_size)
|
|
||||||
chunked_streambuf buf(input, chunk_size);
|
|
||||||
std::istream is(&buf);
|
|
||||||
json j;
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax(j);
|
|
||||||
CHECK(json::sax_parse(is, &sax, json::input_format_t::json, false));
|
|
||||||
CHECK(j == json::parse(R"({"a": [1, "two", 3.5]})"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -218,7 +218,11 @@ TEST_CASE("compliance tests from nativejson-benchmark")
|
|||||||
n1e308[0] = '[';
|
n1e308[0] = '[';
|
||||||
n1e308[1] = '1';
|
n1e308[1] = '1';
|
||||||
n1e308[310] = ']';
|
n1e308[310] = ']';
|
||||||
|
#if JSON_STRICT_NUL_HANDLING
|
||||||
|
n1e308.pop_back(); // a NUL byte after the value is a syntax error in strict mode
|
||||||
|
#else
|
||||||
n1e308[311] = '\0';
|
n1e308[311] = '\0';
|
||||||
|
#endif
|
||||||
TEST_DOUBLE(n1e308, 1E308);
|
TEST_DOUBLE(n1e308, 1E308);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
|||||||
|
|
||||||
SECTION("at")
|
SECTION("at")
|
||||||
{
|
{
|
||||||
const json carr = arr;
|
const json& carr = arr;
|
||||||
const json cobj = obj;
|
const json& cobj = obj;
|
||||||
CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
|
CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
|
||||||
CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range");
|
CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range");
|
||||||
CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number");
|
CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number");
|
||||||
@@ -65,7 +65,7 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
|||||||
CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number");
|
CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number");
|
||||||
|
|
||||||
nlohmann::ordered_json oobj = {{"a", 1}};
|
nlohmann::ordered_json oobj = {{"a", 1}};
|
||||||
const nlohmann::ordered_json coobj = oobj;
|
const nlohmann::ordered_json& coobj = oobj;
|
||||||
CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
||||||
CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
||||||
CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
|
CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
|
||||||
@@ -94,9 +94,10 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
|||||||
CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
|
CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION) // patch_msg() catches the exceptions, which JSON_NOEXCEPTION turns into std::abort()
|
||||||
SECTION("patch")
|
SECTION("patch")
|
||||||
{
|
{
|
||||||
const auto patch_msg = [](const char* doc, const char* patch)
|
const auto patch_msg = [](const char* doc, const char* patch) -> std::string
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -105,16 +106,23 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
|||||||
}
|
}
|
||||||
catch (const json::exception& e)
|
catch (const json::exception& e)
|
||||||
{
|
{
|
||||||
// with JSON_DIAGNOSTICS, what() names the offending value like "(/0) "; drop it
|
// with JSON_DIAGNOSTICS, what() names the offending value like "(/0) ",
|
||||||
|
// and with JSON_DIAGNOSTIC_POSITIONS its position like "(bytes 0-2) "; drop both
|
||||||
std::string msg = e.what();
|
std::string msg = e.what();
|
||||||
const auto pos = msg.find(" (/");
|
for (const char* marker :
|
||||||
if (pos != std::string::npos)
|
{
|
||||||
|
" (/", " (bytes "
|
||||||
|
})
|
||||||
{
|
{
|
||||||
msg.erase(pos + 1, msg.find(") ", pos) - pos + 1);
|
const auto pos = msg.find(marker);
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
msg.erase(pos + 1, msg.find(") ", pos) - pos + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
return std::string();
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects");
|
CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects");
|
||||||
@@ -132,4 +140,5 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
|||||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}");
|
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}");
|
||||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'");
|
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user