mirror of
https://github.com/nlohmann/json.git
synced 2026-08-10 19:23:19 +00:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a13902a33f | ||
|
|
c021a09b08 | ||
|
|
e4aaf46d38 | ||
|
|
ad94fb01cc | ||
|
|
c2e1cc50e0 | ||
|
|
173f2a7407 | ||
|
|
1c63a120b6 | ||
|
|
85889e8843 | ||
|
|
3c0a9a99fd | ||
|
|
e82724d87f | ||
|
|
5bc24e876b | ||
|
|
78821cd9c2 | ||
|
|
68f0722a19 | ||
|
|
5f121d8c50 | ||
|
|
585929bff9 | ||
|
|
31ba5208c8 | ||
|
|
da7b9bdb3d | ||
|
|
634f49bc5b |
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
with:
|
||||
stale-issue-label: 'state: stale'
|
||||
stale-pr-label: 'state: stale'
|
||||
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
name: code-coverage-report
|
||||
path: ${{ github.workspace }}/build/html
|
||||
- name: Publish report to Coveralls
|
||||
uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7
|
||||
uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
|
||||
|
||||
@@ -1801,13 +1801,13 @@ The library itself consists of a single header file licensed under the MIT licen
|
||||
- [**amalgamate.py - Amalgamate C source and header files**](https://github.com/edlund/amalgamate) to create a single header file
|
||||
- [**American fuzzy lop**](https://lcamtuf.coredump.cx/afl/) for fuzz testing
|
||||
- [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows
|
||||
- [**Artistic Style**](http://astyle.sourceforge.net) for automatic source code indentation
|
||||
- [**Artistic Style**](https://astyle.sourceforge.net) for automatic source code indentation
|
||||
- [**Clang**](https://clang.llvm.org) for compilation with code sanitizers
|
||||
- [**CMake**](https://cmake.org) for build automation
|
||||
- [**Codacy**](https://www.codacy.com) for further [code analysis](https://app.codacy.com/gh/nlohmann/json/dashboard)
|
||||
- [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json)
|
||||
- [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json)
|
||||
- [**cppcheck**](http://cppcheck.sourceforge.net) for static analysis
|
||||
- [**cppcheck**](https://cppcheck.sourceforge.io) for static analysis
|
||||
- [**doctest**](https://github.com/onqtam/doctest) for the unit tests
|
||||
- [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
|
||||
- [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks
|
||||
@@ -1822,6 +1822,15 @@ The library itself consists of a single header file licensed under the MIT licen
|
||||
|
||||
## Notes
|
||||
|
||||
### Standards compliance
|
||||
|
||||
The library targets strict conformance with [RFC 8259](https://tools.ietf.org/html/rfc8259.html). Both the original [JSONTestSuite](https://github.com/nst/JSONTestSuite) and its updated revision are exercised in CI; their test data is downloaded from [`nlohmann/json_test_data`](https://github.com/nlohmann/json_test_data) at configure time rather than committed to this repository (see [`tests/src/unit-testsuites.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-testsuites.cpp)):
|
||||
|
||||
- The updated revision runs all mandatory `y_` (must-accept) and `n_` (must-reject) cases through the strict [`parse()`](https://json.nlohmann.me/api/basic_json/parse/) entry point; the original suite runs its `n_` cases through `parse()` and its `y_` cases through [`operator>>`](https://json.nlohmann.me/api/operator_gtgt/).
|
||||
- The `i_` (implementation-defined) cases are, by RFC 8259, free to be accepted *or* rejected, so "passing all `i_` cases" is not a meaningful conformance metric. The library makes deliberate, documented choices there: nesting depth is not artificially limited, a leading UTF-8 byte order mark is silently ignored, [Unicode noncharacters](https://www.unicode.org/faq/private_use.html#nonchar1) are forwarded unchanged, invalid UTF-8 and lone/unpaired UTF-16 surrogates are rejected (stricter than required), and a number that cannot be stored without becoming `NaN`/`INF` raises [`out_of_range.406`](https://json.nlohmann.me/home/exceptions/#jsonexceptionout_of_range406).
|
||||
|
||||
One behavioral nuance is worth calling out, because a superficial test often misreads it as non-compliance: [`parse()`](https://json.nlohmann.me/api/basic_json/parse/) is strict and rejects trailing data after a value, whereas [`operator>>`](https://json.nlohmann.me/api/operator_gtgt/) follows relaxed iostream semantics — it parses a single value and leaves the stream positioned right after it. Feeding "a valid document followed by trailing bytes" through `operator>>` reports success; the same input through `parse()` is rejected. This is a documented two-API design, not a conformance gap. See [**parsing**](https://json.nlohmann.me/features/parsing/) for details.
|
||||
|
||||
### Character encoding
|
||||
|
||||
The library supports **Unicode input** as follows:
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
# -Wno-extra-semi-stmt The library uses assert which triggers this warning.
|
||||
# -Wno-padded We do not care about padding warnings.
|
||||
# -Wno-covered-switch-default All switches list all cases and a default case.
|
||||
# -Wno-c2y-extensions Clang 22.1 diagnoses __COUNTER__ as a C2y extension, also in
|
||||
# C++ mode. The library does not use __COUNTER__; the warnings
|
||||
# all come from vendored Doctest (SECTION/TEST_CASE macros).
|
||||
# -Wno-unsafe-buffer-usage Pervasive: the library's own low-level numeric/buffer code
|
||||
# (to_chars, serializer, lexer, binary reader/writer, input
|
||||
# adapters, json_pointer) plus vendored Doctest itself (~208
|
||||
@@ -20,5 +23,6 @@ set(CLANG_CXXFLAGS
|
||||
-Wno-extra-semi-stmt
|
||||
-Wno-padded
|
||||
-Wno-covered-switch-default
|
||||
-Wno-c2y-extensions
|
||||
-Wno-unsafe-buffer-usage
|
||||
)
|
||||
|
||||
@@ -29,7 +29,14 @@ Discarding a value (i.e., returning `#!cpp false`) has different effects dependi
|
||||
called:
|
||||
|
||||
- Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never
|
||||
read.
|
||||
read. This holds for every value type and for both kinds of parent: a discarded element is removed from the
|
||||
surrounding array, and a discarded member is removed from the surrounding object together with its key.
|
||||
- Arrays and objects can be discarded either at their `parse_event_t::array_start`/`parse_event_t::object_start` event
|
||||
or at their `parse_event_t::array_end`/`parse_event_t::object_end` event, and both remove the whole value. Discarding
|
||||
it at the start event also means the callback is called neither for the content of the value nor for its matching end
|
||||
event.
|
||||
- Discarding a `parse_event_t::key` event discards the whole object member. The callback is still called for the
|
||||
associated value, but its return value has no further effect.
|
||||
- In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level
|
||||
element is skipped.
|
||||
|
||||
@@ -49,7 +56,7 @@ called:
|
||||
## Return value
|
||||
|
||||
Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In
|
||||
the latter case, it is either skipped completely or replaced by an empty discarded object.
|
||||
the latter case, it is skipped completely, or replaced by `null` if it is the top-level value.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -68,6 +75,21 @@ the latter case, it is either skipped completely or replaced by an empty discard
|
||||
--8<-- "examples/parse__string__parser_callback_t.output"
|
||||
```
|
||||
|
||||
??? example
|
||||
|
||||
The example below shows where discarded values are removed. The array and the number are discarded in different
|
||||
ways, but in each case the parse result contains neither the value nor its key.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/parser_callback_t.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/parser_callback_t.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [parse](parse.md) deserialize from a compatible input
|
||||
@@ -76,3 +98,5 @@ the latter case, it is either skipped completely or replaced by an empty discard
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- Fixed in version 3.13.0 to also remove discarded values from a parent object; before, discarding an array or a value
|
||||
stored under an object key left a discarded member behind, which made the parse result serialize to invalid JSON.
|
||||
|
||||
@@ -69,7 +69,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
[`input_format_t`](input_format_t.md) for more information
|
||||
|
||||
`strict` (in)
|
||||
: whether the input has to be consumed completely (optional, `#!cpp true` by default)
|
||||
: whether the input has to be consumed completely (optional, `#!cpp true` by default); when `#!cpp false` and the
|
||||
input is a `#!cpp std::istream`, the stream is left positioned right after the parsed value
|
||||
|
||||
`ignore_comments` (in)
|
||||
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
||||
@@ -136,6 +137,8 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- Changed in version 4.0.0 to leave a `#!cpp std::istream` positioned right after the parsed value when `strict` is
|
||||
`#!cpp false`; see [`operator>>`](../operator_gtgt.md#notes).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -37,13 +37,25 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||
repeatedly to read a sequence of concatenated JSON values from the same stream:
|
||||
|
||||
```cpp
|
||||
json j1, j2;
|
||||
input >> j1; // parses the first value, stream now positioned right after it
|
||||
input >> j2; // parses the next value
|
||||
std::istringstream input("1true[2]");
|
||||
json j1, j2, j3;
|
||||
input >> j1; // j1 == 1, stream now positioned right after it
|
||||
input >> j2; // j2 == true
|
||||
input >> j3; // j3 == [2]
|
||||
```
|
||||
|
||||
Note this does **not** work for [JSON Lines](../features/parsing/json_lines.md) (newline-delimited JSON) input --
|
||||
see that page for why and for the recommended alternative.
|
||||
!!! note "Changed behavior for numbers"
|
||||
|
||||
A number is the only value whose end can be detected solely by reading the character that follows it. Up to
|
||||
version 3.13.0 that character was consumed and not put back, so the stream was left one byte too far whenever a
|
||||
number was immediately followed by another value: reading `1true` yielded `1` and left the stream at `rue`.
|
||||
Values had to be separated by whitespace to work around this.
|
||||
|
||||
The terminating character is now only looked at and left in the stream, so no separator is required. Code that
|
||||
relied on the extra byte being swallowed will observe it again.
|
||||
|
||||
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
|
||||
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
@@ -75,3 +87,5 @@ see that page for why and for the recommended alternative.
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- Changed in version 4.0.0 to leave the character that terminates a number in the stream, so that the stream is
|
||||
positioned right after the parsed value for every value type.
|
||||
|
||||
@@ -13,6 +13,12 @@ Therefore, adding object elements can yield a reallocation in which case all ite
|
||||
[`end()`](basic_json/end.md) iterator) and all references to the elements are invalidated. Also, any iterator or
|
||||
reference after the insertion point will point to the same index, which is now a different value.
|
||||
|
||||
## Complexity
|
||||
|
||||
[`ordered_map`](ordered_map.md) has no lookup index: every key-based object operation is a linear scan, so building or
|
||||
parsing an object of `n` keys costs O(n²) rather than O(n log n). See
|
||||
[`ordered_map` complexity](ordered_map.md#complexity) for the per-operation table and for measured numbers.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
@@ -56,6 +56,48 @@ std::equal_to<> // since C++14
|
||||
- **find**
|
||||
- **insert**
|
||||
|
||||
## Complexity
|
||||
|
||||
Because the elements are stored in a `std::vector` in insertion order, there is no index to look a key up by. Every
|
||||
key-based operation performs a **linear scan** over the stored elements. With `n` denoting the number of elements in the
|
||||
container:
|
||||
|
||||
| Operation | Complexity | Note |
|
||||
|----------------------------------------|----------------|----------------------------------------------------------|
|
||||
| **emplace** | O(n) | scans for an existing key, then appends (amortized O(1)) |
|
||||
| **operator\[\]** | O(n) | delegates to **emplace** (non-const) or **at** (const) |
|
||||
| **at** | O(n) | throws `#!cpp std::out_of_range` if the key is not found |
|
||||
| **find** | O(n) | |
|
||||
| **count** | O(n) | the result is always 0 or 1 |
|
||||
| **erase(key)** | O(n) | scan, then move the remaining elements one position down |
|
||||
| **erase(pos)**, **erase(first, last)** | O(n) | moves all elements after the erased range |
|
||||
| **insert(value)** | O(n) | equivalent to **emplace** |
|
||||
| **insert(first, last)** | O((n + m) * m) | for `m` inserted elements |
|
||||
|
||||
This differs from `#!cpp std::map`, where the same operations are O(log n).
|
||||
|
||||
!!! warning "Quadratic cost of building large objects"
|
||||
|
||||
Because every insertion scans all elements inserted so far, building an object of `n` distinct keys costs
|
||||
**O(n²)** in total. This applies to filling an [`ordered_json`](ordered_json.md) object key by key as well as to
|
||||
parsing one, since the parser inserts each key as it is read.
|
||||
|
||||
The cost is negligible for the object sizes typically found in configuration files or API payloads, but it grows
|
||||
steeply for machine-generated objects with many thousands of keys. Measured with `-O2 -DNDEBUG` for parsing a flat
|
||||
object of `n` keys, relative to `#!cpp nlohmann::json` (which uses `#!cpp std::map`):
|
||||
|
||||
| `n` | `json` | `ordered_json` | factor |
|
||||
|--------|--------|----------------|--------|
|
||||
| 2000 | 0.7 ms | 3.6 ms | 5× |
|
||||
| 4000 | 0.8 ms | 14.0 ms | 19× |
|
||||
| 8000 | 1.6 ms | 67.8 ms | 43× |
|
||||
| 16 000 | 3.3 ms | 181.6 ms | 54× |
|
||||
|
||||
If key order matters for objects of that size, consider a container with a lookup index, such as
|
||||
[`tsl::ordered_map`](https://github.com/Tessil/ordered-map)
|
||||
([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)), as the object type -- see
|
||||
[object order](../features/object_order.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a JSON text with an array and a number inside an object
|
||||
auto text = R"({"IDs": [116, 943], "Width": 800})";
|
||||
|
||||
// discard the array when the parser reads its opening bracket
|
||||
json j_array_start = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & /*parsed*/)
|
||||
{
|
||||
return event != json::parse_event_t::array_start;
|
||||
});
|
||||
|
||||
// discard the same array when the parser reads its closing bracket
|
||||
json j_array_end = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & /*parsed*/)
|
||||
{
|
||||
return event != json::parse_event_t::array_end;
|
||||
});
|
||||
|
||||
// discard the number, but keep its key
|
||||
json j_value = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & parsed)
|
||||
{
|
||||
return !(event == json::parse_event_t::value && parsed == json(800));
|
||||
});
|
||||
|
||||
// discard the key of the number
|
||||
json j_key = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & parsed)
|
||||
{
|
||||
return !(event == json::parse_event_t::key && parsed == json("Width"));
|
||||
});
|
||||
|
||||
// discard the top-level object
|
||||
json j_root = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & /*parsed*/)
|
||||
{
|
||||
return event != json::parse_event_t::object_end;
|
||||
});
|
||||
|
||||
// in every case, the discarded value is removed together with its key
|
||||
std::cout << j_array_start << '\n'
|
||||
<< j_array_end << '\n'
|
||||
<< j_value << '\n'
|
||||
<< j_key << '\n'
|
||||
<< j_root << '\n';
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"Width":800}
|
||||
{"Width":800}
|
||||
{"IDs":[116,943]}
|
||||
{"IDs":[116,943]}
|
||||
null
|
||||
@@ -66,8 +66,14 @@ auto t = j.get<std::tuple<double, std::string, int>>(); // {1.0, "hello", 42}
|
||||
std::get<1>(refs) = "world"; // modifies j[1] in place
|
||||
```
|
||||
|
||||
A referenced type must be one the library actually stores (or an arithmetic type it can convert to/from);
|
||||
otherwise this is a compile error.
|
||||
A referenced element must name the type the library actually *stores* — one of [`boolean_t`](../api/basic_json/boolean_t.md),
|
||||
[`number_integer_t`](../api/basic_json/number_integer_t.md), [`number_unsigned_t`](../api/basic_json/number_unsigned_t.md),
|
||||
[`number_float_t`](../api/basic_json/number_float_t.md), [`string_t`](../api/basic_json/string_t.md),
|
||||
[`binary_t`](../api/basic_json/binary_t.md), [`array_t`](../api/basic_json/array_t.md), or
|
||||
[`object_t`](../api/basic_json/object_t.md). There is nothing else to refer to, so a reference to any other type is a
|
||||
compile error even when a conversion would exist: `#!cpp std::tuple<int&>` is rejected, because the library stores a
|
||||
`#!cpp number_integer_t` (`#!cpp std::int64_t` by default) and not an `#!cpp int`. This restriction applies only to
|
||||
reference elements — a plain `#!cpp std::tuple<int>` converts by value as usual.
|
||||
|
||||
## Implicit conversions
|
||||
|
||||
@@ -116,17 +122,34 @@ which forces the explicit `get` form and can catch unintended conversions at com
|
||||
with a custom `adl_serializer<std::optional<T>>` specialization. Prefer `get<std::optional<T>>()`/`get_to()`
|
||||
over `static_cast` for optional types.
|
||||
|
||||
!!! warning "Converting to a fixed-size `std::array` does not check length"
|
||||
!!! warning "Converting to a fixed-size destination does not check the array size"
|
||||
|
||||
Converting a JSON array to `#!cpp std::array<T, N>` does not check that the JSON array's size matches `N`:
|
||||
if the JSON array is longer, the extra elements are silently dropped; if it is shorter, the remaining
|
||||
`std::array` elements are left default-constructed. No exception is thrown in either case.
|
||||
Some destination types have a size that is fixed by their C++ type rather than by the JSON value:
|
||||
`#!cpp std::pair<A, B>`, `#!cpp std::tuple<Ts...>`, `#!cpp std::array<T, N>`, C arrays `#!cpp T[N]`, and
|
||||
`#!cpp std::map`/`#!cpp std::unordered_map` with a non-string key type (which is read from an array of
|
||||
two-element arrays). All of them read exactly as many elements as they need via
|
||||
[`at`](../api/basic_json/at.md) and **never compare the JSON array's size to that number**. The two
|
||||
mismatch directions therefore behave differently:
|
||||
|
||||
- The JSON array has **too many** elements: the surplus is **silently discarded**, and no exception is
|
||||
thrown.
|
||||
- The JSON array has **too few** elements: `at` throws
|
||||
[`out_of_range.401`](../home/exceptions.md#jsonexceptionout_of_range401) for the first missing index --
|
||||
an out-of-range error, not a [`type_error`](../home/exceptions.md#type-errors), even though the cause
|
||||
is a shape mismatch.
|
||||
|
||||
```cpp
|
||||
json j = {1, 2, 3, 4, 5};
|
||||
auto a = j.get<std::array<int, 3>>(); // {1, 2, 3} -- elements 4 and 5 silently dropped
|
||||
|
||||
auto a = j.get<std::array<int, 3>>(); // {1, 2, 3} -- elements 4 and 5 silently dropped
|
||||
auto p = j.get<std::pair<int, int>>(); // (1, 2) -- elements 3, 4, and 5 silently dropped
|
||||
|
||||
json k = {1};
|
||||
auto q = k.get<std::pair<int, int>>(); // ❌ throws out_of_range.401
|
||||
```
|
||||
|
||||
If a size mismatch is an error in your application, check the size yourself before converting.
|
||||
|
||||
## Omitting a field when serializing `std::optional`
|
||||
|
||||
By default, `to_json` for `std::optional<T>` writes either the value or `#!json null` -- there is no built-in way
|
||||
|
||||
@@ -53,6 +53,12 @@ If you do want to preserve the **insertion order**, you can use the type [`nlohm
|
||||
|
||||
Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
|
||||
|
||||
The [`ordered_map`](../api/ordered_map.md) behind `nlohmann::ordered_json` is deliberately minimal and has no lookup
|
||||
index, so every key access is a linear scan and building an object of `n` keys costs O(n²). This is unnoticeable at
|
||||
typical object sizes but becomes significant for objects with many thousands of keys; see
|
||||
[`ordered_map` complexity](../api/ordered_map.md#complexity). The alternatives above keep a lookup index and do not
|
||||
have this cost.
|
||||
|
||||
### Notes on parsing
|
||||
|
||||
Note that you also need to call the right [`parse`](../api/basic_json/parse.md) function when reading from a file.
|
||||
|
||||
@@ -28,6 +28,22 @@ Inputs consisting of multiple values separated by newlines are handled by the [J
|
||||
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
|
||||
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
|
||||
|
||||
## Strictness and trailing data
|
||||
|
||||
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
|
||||
non-whitespace data after the value is reported as a parse error. Use it when you want to guarantee that an input is
|
||||
exactly one complete JSON document.
|
||||
|
||||
[`operator>>`](../../api/operator_gtgt.md) follows relaxed `#!cpp std::istream` semantics instead: it parses one JSON
|
||||
value and leaves the stream positioned right after it, without requiring the rest of the stream to be consumed. This is
|
||||
what makes it possible to read several concatenated values from the same stream, but it also means that "a valid
|
||||
document followed by trailing bytes" is accepted rather than rejected. If you are validating conformance, or need to
|
||||
reject any input that is not exactly one JSON document, prefer `parse`.
|
||||
|
||||
When using `operator>>` to read several concatenated values this way, a value that is a number must be followed by
|
||||
whitespace, because `operator>>` consumes the character that terminates a number — see the
|
||||
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
|
||||
|
||||
## SAX vs. DOM parsing
|
||||
|
||||
The library offers two parsing models:
|
||||
|
||||
@@ -49,4 +49,5 @@ JSON Lines input with more than one value is treated as invalid JSON by the [`pa
|
||||
with a JSON Lines input does not work, because the parser will try to parse one value after the last one.
|
||||
|
||||
This is different from parsing a stream of *concatenated* (non-newline-delimited) JSON values, for which
|
||||
`operator>>` does work -- see its [notes](../../api/operator_gtgt.md#notes) for details.
|
||||
`operator>>` does work, provided that a value that is a number is followed by whitespace -- see its
|
||||
[notes](../../api/operator_gtgt.md#notes) for details.
|
||||
|
||||
@@ -291,9 +291,10 @@ A JSON Pointer array index must be a number.
|
||||
|
||||
### json.exception.parse_error.110
|
||||
|
||||
When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read.
|
||||
When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has
|
||||
been read.
|
||||
|
||||
!!! failure "Example message"
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input
|
||||
@@ -301,6 +302,9 @@ When parsing CBOR or MessagePack, the byte vector ends before the complete value
|
||||
```
|
||||
[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: expected end of input; last byte: 0x5A
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BSON number: unexpected end of input
|
||||
```
|
||||
|
||||
### json.exception.parse_error.112
|
||||
|
||||
@@ -329,10 +333,14 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
|
||||
```
|
||||
[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
|
||||
```
|
||||
|
||||
### json.exception.parse_error.113
|
||||
|
||||
While parsing a map key, a value that is not a string has been read.
|
||||
A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a
|
||||
string was read where one was required (for instance as a map key), or the string's length specification is invalid.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
@@ -345,6 +353,9 @@ While parsing a map key, a value that is not a string has been read.
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative
|
||||
```
|
||||
|
||||
### json.exception.parse_error.114
|
||||
|
||||
@@ -853,13 +864,21 @@ and this exception no longer occurs.
|
||||
|
||||
### json.exception.out_of_range.408
|
||||
|
||||
The size (following `#`) of an UBJSON array or object exceeds the maximal capacity.
|
||||
The size of an array or object in a [binary format](../features/binary_formats/index.md) exceeds the maximal capacity:
|
||||
the size following `#` for [UBJSON](../features/binary_formats/ubjson.md)/[BJData](../features/binary_formats/bjdata.md),
|
||||
or the encoded length for [CBOR](../features/binary_formats/cbor.md).
|
||||
|
||||
!!! failure "Example message"
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
excessive array size: 8658170730974374167
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size
|
||||
```
|
||||
|
||||
### json.exception.out_of_range.409
|
||||
|
||||
|
||||
@@ -101,6 +101,9 @@ class input_stream_adapter
|
||||
// maintain ifstream flags, except eof
|
||||
if (is != nullptr)
|
||||
{
|
||||
// consume the character last returned by get_character() unless it
|
||||
// was given back with release_lookahead()
|
||||
commit_lookahead();
|
||||
is->clear(is->rdstate() & std::ios::eofbit);
|
||||
}
|
||||
}
|
||||
@@ -115,29 +118,60 @@ class input_stream_adapter
|
||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
rhs.lookahead = false;
|
||||
}
|
||||
|
||||
// Whether the character last returned by get_character() can be given back
|
||||
// to the input with release_lookahead().
|
||||
static constexpr bool supports_lookahead = true;
|
||||
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
//
|
||||
// The character is peeked rather than consumed: it is only stepped over
|
||||
// once the next character is requested, or when the adapter is destroyed.
|
||||
// Until then, release_lookahead() can leave it in the input.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
auto res = sb->sbumpc();
|
||||
if (lookahead)
|
||||
{
|
||||
// step over the character returned by the previous call
|
||||
sb->sbumpc();
|
||||
}
|
||||
|
||||
auto res = sb->sgetc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
// there is nothing to step over next time
|
||||
lookahead = false;
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookahead = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Leave the character last returned by get_character() in the input, so
|
||||
// that the next read from the stream - by this adapter or by the caller
|
||||
// once parsing is done - sees it again. Unlike putting a consumed
|
||||
// character back, this cannot fail.
|
||||
void release_lookahead() noexcept
|
||||
{
|
||||
lookahead = false;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||
{
|
||||
commit_lookahead();
|
||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||
{
|
||||
@@ -147,9 +181,23 @@ class input_stream_adapter
|
||||
}
|
||||
|
||||
private:
|
||||
// Step over the character last returned by get_character(). The character
|
||||
// has already been peeked successfully, so for every streambuf with a get
|
||||
// area this is a pointer increment that cannot fail.
|
||||
void commit_lookahead()
|
||||
{
|
||||
if (lookahead)
|
||||
{
|
||||
lookahead = false;
|
||||
sb->sbumpc();
|
||||
}
|
||||
}
|
||||
|
||||
/// the associated input stream
|
||||
std::istream* is = nullptr;
|
||||
std::streambuf* sb = nullptr;
|
||||
/// whether get_character() peeked a character that is not consumed yet
|
||||
bool lookahead = false;
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
|
||||
@@ -626,14 +626,7 @@ class json_sax_dom_callback_parser
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
||||
{
|
||||
// remove discarded value
|
||||
for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)
|
||||
{
|
||||
if (it->is_discarded())
|
||||
{
|
||||
ref_stack.back()->erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
remove_discarded_value(*ref_stack.back());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -674,8 +667,9 @@ class json_sax_dom_callback_parser
|
||||
bool end_array()
|
||||
{
|
||||
bool keep = true;
|
||||
const bool stored = ref_stack.back() != nullptr;
|
||||
|
||||
if (ref_stack.back())
|
||||
if (stored)
|
||||
{
|
||||
keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
|
||||
if (keep)
|
||||
@@ -709,9 +703,19 @@ class json_sax_dom_callback_parser
|
||||
keep_stack.pop_back();
|
||||
|
||||
// remove discarded value
|
||||
if (!keep && !ref_stack.empty() && ref_stack.back()->is_array())
|
||||
if (!ref_stack.empty() && ref_stack.back())
|
||||
{
|
||||
ref_stack.back()->m_data.m_value.array->pop_back();
|
||||
if (!keep && ref_stack.back()->is_array())
|
||||
{
|
||||
ref_stack.back()->m_data.m_value.array->pop_back();
|
||||
}
|
||||
else if ((!keep || !stored) && ref_stack.back()->is_object())
|
||||
{
|
||||
// the array is either still stored under its key or was never
|
||||
// stored, leaving the placeholder key() wrote; both show up as
|
||||
// a discarded member of the parent object
|
||||
remove_discarded_value(*ref_stack.back());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -801,6 +805,19 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
#endif
|
||||
|
||||
/// remove the discarded value the callback rejected from its parent
|
||||
static void remove_discarded_value(BasicJsonType& parent)
|
||||
{
|
||||
for (auto it = parent.begin(); it != parent.end(); ++it)
|
||||
{
|
||||
if (it->is_discarded())
|
||||
{
|
||||
parent.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] v value to add to the JSON value we build during parsing
|
||||
@param[in] skip_callback whether we should skip calling the callback
|
||||
@@ -841,6 +858,18 @@ class json_sax_dom_callback_parser
|
||||
// do not handle this value if we just learnt it shall be discarded
|
||||
if (!keep)
|
||||
{
|
||||
// if the value was to become an object member, key() already
|
||||
// stored a placeholder for it that has to be removed again
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
|
||||
{
|
||||
JSON_ASSERT(!key_keep_stack.empty());
|
||||
const bool placeholder_stored = key_keep_stack.back();
|
||||
key_keep_stack.pop_back();
|
||||
if (placeholder_stored)
|
||||
{
|
||||
remove_discarded_value(*ref_stack.back());
|
||||
}
|
||||
}
|
||||
return {false, nullptr};
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,24 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter reads with one character of lookahead that
|
||||
// can be left in the input (see input_stream_adapter::supports_lookahead),
|
||||
// detected like supports_seek above.
|
||||
template<typename InputAdapterType>
|
||||
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::supports_lookahead;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief lexical analysis
|
||||
|
||||
@@ -146,6 +164,12 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
static constexpr bool lazy_token_string =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
/// input_adapter_supports_lookahead
|
||||
static constexpr bool can_release_lookahead =
|
||||
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||
|
||||
public:
|
||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||
|
||||
@@ -1456,6 +1480,21 @@ scan_number_done:
|
||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
}
|
||||
|
||||
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
|
||||
|
||||
/// adapter with lookahead: leave the character in the input instead
|
||||
void release_lookahead_impl(std::true_type /*can_release*/)
|
||||
{
|
||||
if (next_unget)
|
||||
{
|
||||
// the character is read from the input again rather than replayed
|
||||
// from current, so the adapter must not step over it
|
||||
next_unget = false;
|
||||
ia.release_lookahead();
|
||||
}
|
||||
}
|
||||
|
||||
/// seekable adapter: nothing was captured, so nothing to undo
|
||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
@@ -1519,6 +1558,29 @@ scan_number_done:
|
||||
return position;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass a pending simulated unget on to the input
|
||||
|
||||
unget() only rewinds the lexer's own bookkeeping, so the character that
|
||||
terminated the last token (e.g. the character after a number) would still
|
||||
be stepped over when the input adapter is done. Callers that hand the
|
||||
input back to the user afterwards - operator>> and non-strict sax_parse -
|
||||
call this once when scanning is done, so that the input is positioned
|
||||
right after the value.
|
||||
|
||||
Adapters without lookahead (see input_adapter_supports_lookahead) are not
|
||||
handed back to the user, so this is a no-op for them.
|
||||
|
||||
Scanning may continue after this call: @a next_unget is cleared, and the
|
||||
character is read from the input again instead of being replayed from
|
||||
@a current. A pending unget of EOF needs no special case, because reaching
|
||||
EOF leaves no lookahead to release.
|
||||
*/
|
||||
void release_lookahead()
|
||||
{
|
||||
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
|
||||
}
|
||||
|
||||
/// 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
|
||||
{
|
||||
|
||||
@@ -99,8 +99,14 @@ class parser
|
||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
if (!strict)
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
// in strict mode, input must be completely read
|
||||
if (strict && (get_token() != token_type::end_of_input))
|
||||
else if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -127,8 +133,13 @@ class parser
|
||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
if (!strict)
|
||||
{
|
||||
// see above
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
// in strict mode, input must be completely read
|
||||
if (strict && (get_token() != token_type::end_of_input))
|
||||
else if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -165,8 +176,14 @@ class parser
|
||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||
const bool result = sax_parse_internal(sax);
|
||||
|
||||
if (result && !strict)
|
||||
{
|
||||
// the caller keeps using the input: position it right after the
|
||||
// value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
// strict mode: next byte must be EOF
|
||||
if (result && strict && (get_token() != token_type::end_of_input))
|
||||
else if (result && strict && (get_token() != token_type::end_of_input))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
|
||||
@@ -231,7 +231,9 @@ struct char_traits<signed char> : std::char_traits<char>
|
||||
// Redefine to_int_type function
|
||||
static int_type to_int_type(char_type c) noexcept
|
||||
{
|
||||
return static_cast<int_type>(c);
|
||||
// cast via unsigned char: sign-extending a negative char_type would make
|
||||
// byte 0xFF indistinguishable from eof()
|
||||
return static_cast<int_type>(static_cast<unsigned char>(c));
|
||||
}
|
||||
|
||||
static char_type to_char_type(int_type i) noexcept
|
||||
@@ -699,21 +701,35 @@ struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>> : std::true_type {};
|
||||
template <typename A>
|
||||
struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>&> : std::true_type {};
|
||||
|
||||
// checks if A and B are comparable using Compare functor
|
||||
// checks if A and B are comparable using Compare functor, assuming that
|
||||
// neither A nor B is a json_pointer type (that case is handled by
|
||||
// is_comparable below, which never instantiates this helper otherwise)
|
||||
template<typename Compare, typename A, typename B, typename = void>
|
||||
struct is_comparable : std::false_type {};
|
||||
struct is_comparable_no_json_pointer : std::false_type {};
|
||||
|
||||
// We exclude json_pointer here, because the checks using Compare(A, B) will
|
||||
// use json_pointer::operator string_t() which triggers a deprecation warning
|
||||
// for GCC. See https://github.com/nlohmann/json/issues/4621. The call to
|
||||
// is_json_pointer_of can be removed once the deprecated function has been
|
||||
// removed.
|
||||
template<typename Compare, typename A, typename B>
|
||||
struct is_comparable < Compare, A, B, enable_if_t < !is_json_pointer_of<A, B>::value
|
||||
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>()))>::value
|
||||
struct is_comparable_no_json_pointer < Compare, A, B, enable_if_t <
|
||||
std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>()))>::value
|
||||
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))>::value
|
||||
>> : std::true_type {};
|
||||
|
||||
// checks if A and B are comparable using Compare functor
|
||||
// We dispatch on is_json_pointer_of as a plain bool (rather than folding it
|
||||
// into a single enable_if_t condition together with the checks below) so
|
||||
// that the Compare(A, B) checks are only ever written - and thus only ever
|
||||
// instantiated - when A/B are not a json_pointer/string pair. Those checks
|
||||
// use json_pointer::operator string_t() (GCC, see #4621) resp. the
|
||||
// deprecated json_pointer/string operator== (Clang, see #5288), and merely
|
||||
// naming them as later operands of a plain && chain is not sufficient to
|
||||
// avoid their instantiation on all compilers, even when the first operand
|
||||
// is false. The dispatch on is_json_pointer_of can be removed once the
|
||||
// deprecated json_pointer comparison operators have been removed.
|
||||
template<typename Compare, typename A, typename B, bool = is_json_pointer_of<A, B>::value>
|
||||
struct is_comparable : std::false_type {};
|
||||
|
||||
template<typename Compare, typename A, typename B>
|
||||
struct is_comparable<Compare, A, B, false> : is_comparable_no_json_pointer<Compare, A, B> {};
|
||||
|
||||
template<typename T>
|
||||
using detect_is_transparent = typename T::is_transparent;
|
||||
|
||||
|
||||
@@ -3994,7 +3994,9 @@ struct char_traits<signed char> : std::char_traits<char>
|
||||
// Redefine to_int_type function
|
||||
static int_type to_int_type(char_type c) noexcept
|
||||
{
|
||||
return static_cast<int_type>(c);
|
||||
// cast via unsigned char: sign-extending a negative char_type would make
|
||||
// byte 0xFF indistinguishable from eof()
|
||||
return static_cast<int_type>(static_cast<unsigned char>(c));
|
||||
}
|
||||
|
||||
static char_type to_char_type(int_type i) noexcept
|
||||
@@ -4462,21 +4464,35 @@ struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>> : std::true_type {};
|
||||
template <typename A>
|
||||
struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>&> : std::true_type {};
|
||||
|
||||
// checks if A and B are comparable using Compare functor
|
||||
// checks if A and B are comparable using Compare functor, assuming that
|
||||
// neither A nor B is a json_pointer type (that case is handled by
|
||||
// is_comparable below, which never instantiates this helper otherwise)
|
||||
template<typename Compare, typename A, typename B, typename = void>
|
||||
struct is_comparable : std::false_type {};
|
||||
struct is_comparable_no_json_pointer : std::false_type {};
|
||||
|
||||
// We exclude json_pointer here, because the checks using Compare(A, B) will
|
||||
// use json_pointer::operator string_t() which triggers a deprecation warning
|
||||
// for GCC. See https://github.com/nlohmann/json/issues/4621. The call to
|
||||
// is_json_pointer_of can be removed once the deprecated function has been
|
||||
// removed.
|
||||
template<typename Compare, typename A, typename B>
|
||||
struct is_comparable < Compare, A, B, enable_if_t < !is_json_pointer_of<A, B>::value
|
||||
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>()))>::value
|
||||
struct is_comparable_no_json_pointer < Compare, A, B, enable_if_t <
|
||||
std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>()))>::value
|
||||
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))>::value
|
||||
>> : std::true_type {};
|
||||
|
||||
// checks if A and B are comparable using Compare functor
|
||||
// We dispatch on is_json_pointer_of as a plain bool (rather than folding it
|
||||
// into a single enable_if_t condition together with the checks below) so
|
||||
// that the Compare(A, B) checks are only ever written - and thus only ever
|
||||
// instantiated - when A/B are not a json_pointer/string pair. Those checks
|
||||
// use json_pointer::operator string_t() (GCC, see #4621) resp. the
|
||||
// deprecated json_pointer/string operator== (Clang, see #5288), and merely
|
||||
// naming them as later operands of a plain && chain is not sufficient to
|
||||
// avoid their instantiation on all compilers, even when the first operand
|
||||
// is false. The dispatch on is_json_pointer_of can be removed once the
|
||||
// deprecated json_pointer comparison operators have been removed.
|
||||
template<typename Compare, typename A, typename B, bool = is_json_pointer_of<A, B>::value>
|
||||
struct is_comparable : std::false_type {};
|
||||
|
||||
template<typename Compare, typename A, typename B>
|
||||
struct is_comparable<Compare, A, B, false> : is_comparable_no_json_pointer<Compare, A, B> {};
|
||||
|
||||
template<typename T>
|
||||
using detect_is_transparent = typename T::is_transparent;
|
||||
|
||||
@@ -7088,6 +7104,9 @@ class input_stream_adapter
|
||||
// maintain ifstream flags, except eof
|
||||
if (is != nullptr)
|
||||
{
|
||||
// consume the character last returned by get_character() unless it
|
||||
// was given back with release_lookahead()
|
||||
commit_lookahead();
|
||||
is->clear(is->rdstate() & std::ios::eofbit);
|
||||
}
|
||||
}
|
||||
@@ -7102,29 +7121,60 @@ class input_stream_adapter
|
||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
rhs.lookahead = false;
|
||||
}
|
||||
|
||||
// Whether the character last returned by get_character() can be given back
|
||||
// to the input with release_lookahead().
|
||||
static constexpr bool supports_lookahead = true;
|
||||
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
//
|
||||
// The character is peeked rather than consumed: it is only stepped over
|
||||
// once the next character is requested, or when the adapter is destroyed.
|
||||
// Until then, release_lookahead() can leave it in the input.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
auto res = sb->sbumpc();
|
||||
if (lookahead)
|
||||
{
|
||||
// step over the character returned by the previous call
|
||||
sb->sbumpc();
|
||||
}
|
||||
|
||||
auto res = sb->sgetc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
// there is nothing to step over next time
|
||||
lookahead = false;
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookahead = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Leave the character last returned by get_character() in the input, so
|
||||
// that the next read from the stream - by this adapter or by the caller
|
||||
// once parsing is done - sees it again. Unlike putting a consumed
|
||||
// character back, this cannot fail.
|
||||
void release_lookahead() noexcept
|
||||
{
|
||||
lookahead = false;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||
{
|
||||
commit_lookahead();
|
||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||
{
|
||||
@@ -7134,9 +7184,23 @@ class input_stream_adapter
|
||||
}
|
||||
|
||||
private:
|
||||
// Step over the character last returned by get_character(). The character
|
||||
// has already been peeked successfully, so for every streambuf with a get
|
||||
// area this is a pointer increment that cannot fail.
|
||||
void commit_lookahead()
|
||||
{
|
||||
if (lookahead)
|
||||
{
|
||||
lookahead = false;
|
||||
sb->sbumpc();
|
||||
}
|
||||
}
|
||||
|
||||
/// the associated input stream
|
||||
std::istream* is = nullptr;
|
||||
std::streambuf* sb = nullptr;
|
||||
/// whether get_character() peeked a character that is not consumed yet
|
||||
bool lookahead = false;
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
@@ -7823,6 +7887,24 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter reads with one character of lookahead that
|
||||
// can be left in the input (see input_stream_adapter::supports_lookahead),
|
||||
// detected like supports_seek above.
|
||||
template<typename InputAdapterType>
|
||||
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::supports_lookahead;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief lexical analysis
|
||||
|
||||
@@ -7844,6 +7926,12 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
static constexpr bool lazy_token_string =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
/// input_adapter_supports_lookahead
|
||||
static constexpr bool can_release_lookahead =
|
||||
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||
|
||||
public:
|
||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||
|
||||
@@ -9154,6 +9242,21 @@ scan_number_done:
|
||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
}
|
||||
|
||||
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
|
||||
|
||||
/// adapter with lookahead: leave the character in the input instead
|
||||
void release_lookahead_impl(std::true_type /*can_release*/)
|
||||
{
|
||||
if (next_unget)
|
||||
{
|
||||
// the character is read from the input again rather than replayed
|
||||
// from current, so the adapter must not step over it
|
||||
next_unget = false;
|
||||
ia.release_lookahead();
|
||||
}
|
||||
}
|
||||
|
||||
/// seekable adapter: nothing was captured, so nothing to undo
|
||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
@@ -9217,6 +9320,29 @@ scan_number_done:
|
||||
return position;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass a pending simulated unget on to the input
|
||||
|
||||
unget() only rewinds the lexer's own bookkeeping, so the character that
|
||||
terminated the last token (e.g. the character after a number) would still
|
||||
be stepped over when the input adapter is done. Callers that hand the
|
||||
input back to the user afterwards - operator>> and non-strict sax_parse -
|
||||
call this once when scanning is done, so that the input is positioned
|
||||
right after the value.
|
||||
|
||||
Adapters without lookahead (see input_adapter_supports_lookahead) are not
|
||||
handed back to the user, so this is a no-op for them.
|
||||
|
||||
Scanning may continue after this call: @a next_unget is cleared, and the
|
||||
character is read from the input again instead of being replayed from
|
||||
@a current. A pending unget of EOF needs no special case, because reaching
|
||||
EOF leaves no lookahead to release.
|
||||
*/
|
||||
void release_lookahead()
|
||||
{
|
||||
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
|
||||
}
|
||||
|
||||
/// 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
|
||||
{
|
||||
@@ -10049,14 +10175,7 @@ class json_sax_dom_callback_parser
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
||||
{
|
||||
// remove discarded value
|
||||
for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)
|
||||
{
|
||||
if (it->is_discarded())
|
||||
{
|
||||
ref_stack.back()->erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
remove_discarded_value(*ref_stack.back());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -10097,8 +10216,9 @@ class json_sax_dom_callback_parser
|
||||
bool end_array()
|
||||
{
|
||||
bool keep = true;
|
||||
const bool stored = ref_stack.back() != nullptr;
|
||||
|
||||
if (ref_stack.back())
|
||||
if (stored)
|
||||
{
|
||||
keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
|
||||
if (keep)
|
||||
@@ -10132,9 +10252,19 @@ class json_sax_dom_callback_parser
|
||||
keep_stack.pop_back();
|
||||
|
||||
// remove discarded value
|
||||
if (!keep && !ref_stack.empty() && ref_stack.back()->is_array())
|
||||
if (!ref_stack.empty() && ref_stack.back())
|
||||
{
|
||||
ref_stack.back()->m_data.m_value.array->pop_back();
|
||||
if (!keep && ref_stack.back()->is_array())
|
||||
{
|
||||
ref_stack.back()->m_data.m_value.array->pop_back();
|
||||
}
|
||||
else if ((!keep || !stored) && ref_stack.back()->is_object())
|
||||
{
|
||||
// the array is either still stored under its key or was never
|
||||
// stored, leaving the placeholder key() wrote; both show up as
|
||||
// a discarded member of the parent object
|
||||
remove_discarded_value(*ref_stack.back());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -10224,6 +10354,19 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
#endif
|
||||
|
||||
/// remove the discarded value the callback rejected from its parent
|
||||
static void remove_discarded_value(BasicJsonType& parent)
|
||||
{
|
||||
for (auto it = parent.begin(); it != parent.end(); ++it)
|
||||
{
|
||||
if (it->is_discarded())
|
||||
{
|
||||
parent.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] v value to add to the JSON value we build during parsing
|
||||
@param[in] skip_callback whether we should skip calling the callback
|
||||
@@ -10264,6 +10407,18 @@ class json_sax_dom_callback_parser
|
||||
// do not handle this value if we just learnt it shall be discarded
|
||||
if (!keep)
|
||||
{
|
||||
// if the value was to become an object member, key() already
|
||||
// stored a placeholder for it that has to be removed again
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
|
||||
{
|
||||
JSON_ASSERT(!key_keep_stack.empty());
|
||||
const bool placeholder_stored = key_keep_stack.back();
|
||||
key_keep_stack.pop_back();
|
||||
if (placeholder_stored)
|
||||
{
|
||||
remove_discarded_value(*ref_stack.back());
|
||||
}
|
||||
}
|
||||
return {false, nullptr};
|
||||
}
|
||||
|
||||
@@ -13920,8 +14075,14 @@ class parser
|
||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
if (!strict)
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
// in strict mode, input must be completely read
|
||||
if (strict && (get_token() != token_type::end_of_input))
|
||||
else if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -13948,8 +14109,13 @@ class parser
|
||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
if (!strict)
|
||||
{
|
||||
// see above
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
// in strict mode, input must be completely read
|
||||
if (strict && (get_token() != token_type::end_of_input))
|
||||
else if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -13986,8 +14152,14 @@ class parser
|
||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||
const bool result = sax_parse_internal(sax);
|
||||
|
||||
if (result && !strict)
|
||||
{
|
||||
// the caller keeps using the input: position it right after the
|
||||
// value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
// strict mode: next byte must be EOF
|
||||
if (result && strict && (get_token() != token_type::end_of_input))
|
||||
else if (result && strict && (get_token() != token_type::end_of_input))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
|
||||
@@ -1440,6 +1440,13 @@ TEST_CASE("parser class")
|
||||
]
|
||||
)";
|
||||
|
||||
const auto* structured_object = R"(
|
||||
{
|
||||
"foo": [1, 2],
|
||||
"bar": 3
|
||||
}
|
||||
)";
|
||||
|
||||
SECTION("filter nothing")
|
||||
{
|
||||
const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||
@@ -1515,6 +1522,48 @@ TEST_CASE("parser class")
|
||||
CHECK (j_filtered2 == json({1}));
|
||||
}
|
||||
|
||||
SECTION("filter array in object")
|
||||
{
|
||||
// the array is discarded once it is already stored under its key
|
||||
const json j_filtered1 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json& /*parsed*/) noexcept
|
||||
{
|
||||
return e != json::parse_event_t::array_end;
|
||||
});
|
||||
|
||||
CHECK (j_filtered1 == json({{"bar", 3}}));
|
||||
|
||||
// the array is discarded before it is stored, leaving the
|
||||
// placeholder the key event wrote
|
||||
const json j_filtered2 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json& /*parsed*/) noexcept
|
||||
{
|
||||
return e != json::parse_event_t::array_start;
|
||||
});
|
||||
|
||||
CHECK (j_filtered2 == json({{"bar", 3}}));
|
||||
}
|
||||
|
||||
SECTION("filter value in object")
|
||||
{
|
||||
// the value is discarded after its key was kept, leaving the
|
||||
// placeholder the key event wrote
|
||||
const json j_filtered1 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json & parsed) noexcept
|
||||
{
|
||||
return !(e == json::parse_event_t::value && parsed == json(3));
|
||||
});
|
||||
|
||||
CHECK (j_filtered1 == json({{"foo", {1, 2}}}));
|
||||
|
||||
// the same value is discarded together with its key, so no
|
||||
// placeholder was stored for it
|
||||
const json j_filtered2 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json & parsed) noexcept
|
||||
{
|
||||
return !((e == json::parse_event_t::key && parsed == json("bar")) ||
|
||||
(e == json::parse_event_t::value && parsed == json(3)));
|
||||
});
|
||||
|
||||
CHECK (j_filtered2 == json({{"foo", {1, 2}}}));
|
||||
}
|
||||
|
||||
SECTION("filter specific events")
|
||||
{
|
||||
SECTION("first closing event")
|
||||
|
||||
@@ -14,10 +14,15 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <streambuf>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <valarray>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define NOMINMAX
|
||||
@@ -219,6 +224,58 @@ class proxy_iterator
|
||||
iterator* m_it = nullptr;
|
||||
};
|
||||
|
||||
// A streambuf that keeps no get area at all and refuses every putback: with an
|
||||
// empty get area, sungetc() always ends up in pbackfail(). Used to check that
|
||||
// the character terminating a number is left in the input without relying on
|
||||
// the streambuf being able to put a consumed character back.
|
||||
class no_putback_streambuf : public std::streambuf
|
||||
{
|
||||
public:
|
||||
explicit no_putback_streambuf(std::string s) : m_data(std::move(s)) {}
|
||||
|
||||
protected:
|
||||
// peek at the next character without consuming it
|
||||
int_type underflow() override
|
||||
{
|
||||
if (m_pos >= m_data.size())
|
||||
{
|
||||
return traits_type::eof();
|
||||
}
|
||||
return traits_type::to_int_type(m_data[m_pos]);
|
||||
}
|
||||
|
||||
// consume the next character
|
||||
int_type uflow() override
|
||||
{
|
||||
if (m_pos >= m_data.size())
|
||||
{
|
||||
return traits_type::eof();
|
||||
}
|
||||
return traits_type::to_int_type(m_data[m_pos++]);
|
||||
}
|
||||
|
||||
int_type pbackfail(int_type /*c*/) override
|
||||
{
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_data;
|
||||
std::size_t m_pos = 0;
|
||||
};
|
||||
|
||||
// read the characters that are left in a stream
|
||||
std::string remaining(std::istream& is)
|
||||
{
|
||||
std::string result;
|
||||
char c = 0;
|
||||
while (is.get(c))
|
||||
{
|
||||
result += c;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// JSON_HAS_CPP_20
|
||||
#if defined(__cpp_char8_t)
|
||||
bool check_utf8()
|
||||
@@ -427,6 +484,30 @@ TEST_CASE("deserialization")
|
||||
CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
|
||||
}
|
||||
|
||||
SECTION("from std::vector<signed char>")
|
||||
{
|
||||
std::vector<signed char> const v = {'t', 'r', 'u', 'e'};
|
||||
CHECK(json::parse(v) == json(true));
|
||||
CHECK(json::accept(v));
|
||||
|
||||
SaxEventLogger l;
|
||||
CHECK(json::sax_parse(v, &l));
|
||||
CHECK(l.events.size() == 1);
|
||||
CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
|
||||
|
||||
// bytes outside ASCII are negative here and must not be sign-extended;
|
||||
// 0xC3 and 0xA9 do not fit in signed char (MSVC C4309), so spell them as negative values
|
||||
std::vector<signed char> const umlaut = {'"', static_cast<signed char>(0xC3 - 0x100), static_cast<signed char>(0xA9 - 0x100), '"'};
|
||||
CHECK(json::parse(umlaut) == json("\xC3\xA9"));
|
||||
CHECK(json::accept(umlaut));
|
||||
|
||||
// 0xFF (spelled as -1 to stay in range) must not be reported as end of input
|
||||
std::vector<signed char> const trailing = {'t', 'r', 'u', 'e', static_cast<signed char>(0xFF - 0x100)};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'true\xFF'; expected end of input", json::parse_error&);
|
||||
CHECK(!json::accept(trailing));
|
||||
}
|
||||
|
||||
SECTION("from std::array")
|
||||
{
|
||||
std::array<uint8_t, 5> const v { {'t', 'r', 'u', 'e'} };
|
||||
@@ -1157,6 +1238,122 @@ TEST_CASE("deserialization")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("stream position after extraction (#5340)")
|
||||
{
|
||||
SECTION("a number does not consume the character that terminates it")
|
||||
{
|
||||
// a number is only terminated by the character following it; that
|
||||
// character must be given back so the stream is positioned right
|
||||
// after the value
|
||||
const std::vector<std::pair<std::string, std::string>> tests =
|
||||
{
|
||||
{"1true", "true"},
|
||||
{"1[2]", "[2]"},
|
||||
{"1{}", "{}"},
|
||||
{R"(1"a")", R"("a")"},
|
||||
{"1 true", " true"},
|
||||
{"12,", ","},
|
||||
{"-0.5e3x", "x"},
|
||||
{"1null", "null"}
|
||||
};
|
||||
|
||||
for (const auto& test : tests)
|
||||
{
|
||||
CAPTURE(test.first);
|
||||
std::istringstream ss(test.first);
|
||||
json j;
|
||||
ss >> j;
|
||||
CHECK(j == json::parse(test.first.substr(0, test.first.size() - test.second.size())));
|
||||
CHECK(remaining(ss) == test.second);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values that are self-delimiting are unaffected")
|
||||
{
|
||||
const std::vector<std::pair<std::string, std::string>> tests =
|
||||
{
|
||||
{"truefalse", "false"},
|
||||
{"[1][2]", "[2]"},
|
||||
{R"({"a":1}{"b":2})", R"({"b":2})"},
|
||||
{R"("a""b")", R"("b")"},
|
||||
{"null null", " null"}
|
||||
};
|
||||
|
||||
for (const auto& test : tests)
|
||||
{
|
||||
CAPTURE(test.first);
|
||||
std::istringstream ss(test.first);
|
||||
json j;
|
||||
ss >> j;
|
||||
CHECK(remaining(ss) == test.second);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a number at the end of the input leaves nothing behind")
|
||||
{
|
||||
for (const std::string s :
|
||||
{"1", "12", "-3.5e2", " 7 "
|
||||
})
|
||||
{
|
||||
CAPTURE(s);
|
||||
std::istringstream ss(s);
|
||||
json j;
|
||||
ss >> j;
|
||||
CHECK(remaining(ss).find_first_not_of(" \t\n\r") == std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("repeated extraction of concatenated values")
|
||||
{
|
||||
std::istringstream ss(R"(1true[2]3"x"{"a":4}5)");
|
||||
const std::vector<json> expected =
|
||||
{
|
||||
json(1), json(true), json::parse("[2]"), json(3),
|
||||
json("x"), json::parse(R"({"a":4})"), json(5)
|
||||
};
|
||||
|
||||
for (const auto& e : expected)
|
||||
{
|
||||
json j;
|
||||
ss >> j;
|
||||
CHECK(j == e);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("sax_parse with strict == false")
|
||||
{
|
||||
std::istringstream ss("1true");
|
||||
SaxEventLogger l;
|
||||
CHECK(json::sax_parse(ss, &l, nlohmann::detail::input_format_t::json, false));
|
||||
CHECK(l.events.size() == 1);
|
||||
CHECK(l.events[0] == "number_unsigned(1)");
|
||||
CHECK(remaining(ss) == "true");
|
||||
}
|
||||
|
||||
SECTION("strict parsing still rejects trailing data")
|
||||
{
|
||||
std::istringstream ss("1true");
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(ss),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - unexpected true literal; expected end of input", json::parse_error&);
|
||||
|
||||
std::istringstream ss2("1true");
|
||||
CHECK_FALSE(json::accept(ss2));
|
||||
}
|
||||
|
||||
SECTION("a streambuf that cannot put back is not needed")
|
||||
{
|
||||
// the terminating character is never consumed, so no putback
|
||||
// position is required
|
||||
no_putback_streambuf buf("1true");
|
||||
std::istream is(&buf);
|
||||
json j;
|
||||
is >> j;
|
||||
CHECK(j == json(1));
|
||||
CHECK(remaining(is) == "true");
|
||||
}
|
||||
}
|
||||
|
||||
// build with C++20
|
||||
// JSON_HAS_CPP_20
|
||||
#if defined(__cpp_char8_t)
|
||||
|
||||
@@ -53,4 +53,34 @@ TEST_CASE("type traits")
|
||||
// NOLINTEND(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("char_traits")
|
||||
{
|
||||
SECTION("to_int_type does not sign-extend")
|
||||
{
|
||||
using unsigned_traits = nlohmann::detail::char_traits<unsigned char>;
|
||||
using signed_traits = nlohmann::detail::char_traits<signed char>;
|
||||
|
||||
CHECK(unsigned_traits::to_int_type(static_cast<unsigned char>(0x7F)) == 0x7F);
|
||||
CHECK(unsigned_traits::to_int_type(static_cast<unsigned char>(0x80)) == 0x80);
|
||||
CHECK(unsigned_traits::to_int_type(static_cast<unsigned char>(0xFF)) == 0xFF);
|
||||
|
||||
CHECK(signed_traits::to_int_type(static_cast<signed char>(0x7F)) == 0x7F);
|
||||
// 0x80 and 0xFF do not fit in signed char (MSVC C4309), so spell them as negative values
|
||||
CHECK(signed_traits::to_int_type(static_cast<signed char>(0x80 - 0x100)) == 0x80);
|
||||
CHECK(signed_traits::to_int_type(static_cast<signed char>(0xFF - 0x100)) == 0xFF);
|
||||
}
|
||||
|
||||
SECTION("no byte value collides with eof")
|
||||
{
|
||||
using unsigned_traits = nlohmann::detail::char_traits<unsigned char>;
|
||||
using signed_traits = nlohmann::detail::char_traits<signed char>;
|
||||
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
CHECK(unsigned_traits::to_int_type(static_cast<unsigned char>(i)) != unsigned_traits::eof());
|
||||
CHECK(signed_traits::to_int_type(static_cast<signed char>(i)) != signed_traits::eof());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user