Compare commits

..
Author SHA1 Message Date
Niels Lohmann d9e4155ba1 Merge branch 'json-view/14-view-compare' into json-view/14b-view-float-layout
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:39 +02:00
Niels Lohmann 7130880754 Merge branch 'json-view/13-view-dump' into json-view/14-view-compare
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:30 +02:00
Niels Lohmann 94c518f94d Merge branch 'json-view/14-view-compare' into json-view/14b-view-float-layout
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:19:59 +02:00
Niels Lohmann 76ce7e2c84 Merge branch 'json-view/13-view-dump' into json-view/14-view-compare
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:19:57 +02:00
Niels Lohmann ecf9df7c45 Convert the floats of json_view from the digit layout
The parser records where the integer digits, the fraction digits, and the
exponent of a float token are. For floats and doubles with at most 19
digits, the value is now read from that layout: the digits eight at a time,
without scanning the token, and rounded by the library's conversion core
(detail::decimal_to_float(): Clinger's fast path where both operands are
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
digits). It rounds correctly, so the values are those of parse(); other
tokens and types keep the library's conversion of the whole token.

get<double>(), materialize(), dump(), and comparisons use it. Traversing
canada.json (111,000 floats, every number converted): 0.95 -> 1.29 GB/s.

Tests add tokens around the limits (19 and 20 digits, 2^53, 10^22, and
those of float) to the bit-for-bit comparison with parse().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:51 +02:00
Niels Lohmann b992e1f76a Format the comparison examples with the pinned astyle
The "check" job runs astyle over the documentation examples once it gets
past the amalgamation step.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:50 +02:00
Niels Lohmann a7fa8d04e8 Address the cpplint findings of json_view's comparisons
compare.hpp includes <string> (build/include_what_you_use).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:49 +02:00
Niels Lohmann 677507137b Address the clang-tidy findings of the comparisons
Separate the comparison of discarded values from the other types, so
that the conditional chain has no repeated branch bodies, and mark
the deliberate comparisons of views with empty containers in the
tests.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:49 +02:00
Niels Lohmann 21d90fac1c Document the comparisons of json_view
- API pages for operator== and operator!= of basic_json_view, linked
  both ways with the basic_json pages
- the feature page and the class overview list the comparisons
- the examples show when the view helps: detecting a changed document
  without building json values

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:48 +02:00
Niels Lohmann e842f3a68f Add comparisons to json_view
basic_json_view gains operator== and operator!= with other views and with
basic_json values. Two views are equal if the values parse() would
produce for them are equal by basic_json's operator==: numbers compare by
value across their types, and objects by their members, with duplicate
keys resolved as parse() resolves them (the last value, at the position of
the first key). Objects are compared in member order if the object type
keeps an order (ordered_json), by key otherwise, as basic_json does.
Discarded views compare as discarded basic_json values do, which follows
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON. Nothing is materialized except
single numbers, and the walk is iterative.

Tests compare the results for pairs of 1,200 generated documents (also
written differently: sorted keys, canonical numbers) with those of
basic_json, for json and ordered_json, plus numbers, duplicate keys,
member order, discarded values, and 100,000 levels of nesting.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:47 +02:00
21 changed files with 1297 additions and 11 deletions
+1
View File
@@ -67,6 +67,7 @@ cc_library(
"include/nlohmann/detail/string_utils.hpp", "include/nlohmann/detail/string_utils.hpp",
"include/nlohmann/detail/value_t.hpp", "include/nlohmann/detail/value_t.hpp",
"include/nlohmann/detail/view/builder.hpp", "include/nlohmann/detail/view/builder.hpp",
"include/nlohmann/detail/view/compare.hpp",
"include/nlohmann/detail/view/document_data.hpp", "include/nlohmann/detail/view/document_data.hpp",
"include/nlohmann/detail/view/errors.hpp", "include/nlohmann/detail/view/errors.hpp",
"include/nlohmann/detail/view/input.hpp", "include/nlohmann/detail/view/input.hpp",
+2
View File
@@ -178,6 +178,8 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_token
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator bool', 'Method', 'api/basic_json_view/operator_bool/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator bool', 'Method', 'api/basic_json_view/operator_bool/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator<<', 'Operator', 'api/basic_json_view/operator_ltlt/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator<<', 'Operator', 'api/basic_json_view/operator_ltlt/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator[]', 'Operator', 'api/basic_json_view/operator[]/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator[]', 'Operator', 'api/basic_json_view/operator[]/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator==', 'Operator', 'api/basic_json_view/operator_eq/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator!=', 'Operator', 'api/basic_json_view/operator_ne/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::size', 'Method', 'api/basic_json_view/size/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::size', 'Method', 'api/basic_json_view/size/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::source_offset', 'Method', 'api/basic_json_view/source_offset/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::source_offset', 'Method', 'api/basic_json_view/source_offset/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type', 'Method', 'api/basic_json_view/type/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type', 'Method', 'api/basic_json_view/type/index.html');
@@ -166,6 +166,8 @@ Linear.
- [operator!=](operator_ne.md) compare for inequality - [operator!=](operator_ne.md) compare for inequality
- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20) - [operator<=>](operator_spaceship.md) comparison: 3-way (C++20)
- [basic_json_view::operator==](../basic_json_view/operator_eq.md) - the same comparison on a zero-copy view, without
building a `basic_json` value for it
## Version history ## Version history
@@ -89,6 +89,12 @@ Linear.
--8<-- "examples/operator__notequal__nullptr_t.output" --8<-- "examples/operator__notequal__nullptr_t.output"
``` ```
## See also
- [operator==](operator_eq.md) compare for equality
- [basic_json_view::operator!=](../basic_json_view/operator_ne.md) - the same comparison on a zero-copy view, without
building a `basic_json` value for it
## Version history ## Version history
1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.13.0 to remove 1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.13.0 to remove
@@ -20,11 +20,12 @@ Moving the document itself does not invalidate its views: the index is heap-allo
`basic_json_document` object. `basic_json_document` object.
`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element `basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element
access, lookup, iteration, and conversion -- [`get<T>()`](get.md), [`get_string()`](get_string.md), access, lookup, iteration, conversion, and comparison -- [`get<T>()`](get.md), [`get_string()`](get_string.md),
[`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a [`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a
subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and
[`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). It does not (yet) provide [`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). [`operator==`](operator_eq.md) and
comparison. [`operator!=`](operator_ne.md) compare two views, or a view and a `BasicJsonType` value, without ever building a
`BasicJsonType` value for a view; no ordering comparison (`#!cpp operator<`) is provided.
## Template parameters ## Template parameters
@@ -107,6 +108,11 @@ comparison.
- [**number_token**](number_token.md) - get a number's token text without a copy - [**number_token**](number_token.md) - get a number's token text without a copy
- [**materialize**](materialize.md) - build the `BasicJsonType` value of this subtree - [**materialize**](materialize.md) - build the `BasicJsonType` value of this subtree
### Comparison
- [**operator==**](operator_eq.md) - comparison: equal
- [**operator!=**](operator_ne.md) - comparison: not equal
### Serialization ### Serialization
- [**dump**](dump.md) - serialize to a JSON-formatted string - [**dump**](dump.md) - serialize to a JSON-formatted string
@@ -0,0 +1,107 @@
# <small>nlohmann::basic_json_view::</small>operator==
```cpp
// (1)
bool operator==(const basic_json_view& lhs, const basic_json_view& rhs);
// (2)
bool operator==(const basic_json_view& lhs, const BasicJsonType& rhs);
bool operator==(const BasicJsonType& lhs, const basic_json_view& rhs);
```
1. Compares two views for equality: whether the values [`BasicJsonType::parse()`](../basic_json/parse.md) would
produce for `lhs` and `rhs` are equal, according to `BasicJsonType`'s [`operator==`](../basic_json/operator_eq.md).
2. Compares a view and a `BasicJsonType` value for equality, in either order: whether the value `parse()` would
produce for the view and the other operand are equal, according to `BasicJsonType`'s
[`operator==`](../basic_json/operator_eq.md).
Neither overload builds a `BasicJsonType` value for a view to do the comparison (see [Notes](#notes) below). Numbers
compare by value across their types (`#!cpp 1 == 1.0`), and an object compares by its members, with duplicate keys
resolved exactly as `parse()` resolves them -- the last value, at the position of the first occurrence of the key.
## Parameters
`lhs` (in)
: first value to consider
`rhs` (in)
: second value to consider
## Return value
whether the values `lhs` and `rhs` are equal
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to either operand, or to the document(s) a
view refers to.
## Exceptions
May throw `#!cpp std::bad_alloc`. Unlike the other comparison and most other `basic_json_view` functions,
`operator==` is not `#!cpp noexcept`: resolving an object's members needs a temporary array to sort them by key (see
[Complexity](#complexity) below), and that allocation can fail.
## Complexity
Linear in the size of the compared values: every number, string, array element, and object member is visited at most
once, and the walk is iterative, so the nesting depth it can compare is limited by available memory only, not by the
call stack (as for [`materialize()`](materialize.md)). Resolving an object's members takes an additional O(n log n)
in the number of members at that level, since they are sorted by key to detect and resolve duplicates before being
compared. Two arrays of different [`size()`](size.md) are rejected without visiting either one's elements.
## Notes
Only a single number, boolean, or `#!cpp null` value is ever materialized into a `BasicJsonType`, to reuse its
`operator==` -- for numbers, so that values written differently in the source text but equal in value (e.g. an
integer and a floating-point literal) still compare equal, following the same rules `BasicJsonType` does for special
values such as `#!cpp NaN`. Constructing one of these scalars never allocates. Strings are compared directly, without
allocating, either from the source text on both sides or, for overload 2, against `BasicJsonType`'s own string.
Arrays and objects are never materialized at all; only their elements or members are visited, one pair at a time.
!!! info "How objects are compared"
For a [`json_view`](../json_view.md) (`BasicJsonType::object_t` is `#!cpp std::map`), members are compared by
key, regardless of the order they appear in the source text. For an
[`ordered_json_view`](../ordered_json_view.md) (`object_t` is `ordered_map`), they are compared in the order
they occur, so the very same two objects with their members reordered can compare equal as `json_view`s but not
as `ordered_json_view`s. This is exactly how [`json`](../json.md) and [`ordered_json`](../ordered_json.md)
compare, see ["Comparing different `basic_json` specializations"](../basic_json/operator_eq.md#notes).
!!! info "Discarded views"
A [discarded](is_discarded.md) view compares the same way a discarded `BasicJsonType` value does, which is
governed by
[`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../macros/json_use_legacy_discarded_value_comparison.md): by
default, a discarded view is never equal to anything, not even another discarded view.
No ordering comparison (`#!cpp operator<`) is provided for `basic_json_view`; [`materialize()`](materialize.md) is
the way to get a `BasicJsonType` value that supports it.
## Examples
??? example
The example below checks whether a newly received configuration differs from the previous one, and whether a
received document matches what a test expects -- directly on views, without ever materializing a `BasicJsonType`
value for either side.
```cpp
--8<-- "examples/basic_json_view__operator_eq.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__operator_eq.output"
```
## See also
- [operator!=](operator_ne.md) - compare for inequality
- [materialize](materialize.md) - build a `BasicJsonType` value, e.g. to keep comparing after the document is gone
- [`BasicJsonType::operator==`](../basic_json/operator_eq.md) - the corresponding function of `basic_json`
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,82 @@
# <small>nlohmann::basic_json_view::</small>operator!=
```cpp
// (1)
bool operator!=(const basic_json_view& lhs, const basic_json_view& rhs);
// (2)
bool operator!=(const basic_json_view& lhs, const BasicJsonType& rhs);
bool operator!=(const BasicJsonType& lhs, const basic_json_view& rhs);
```
1. Compares two views for inequality. Returns `#!cpp !(lhs == rhs)`, see [operator==](operator_eq.md).
2. Compares a view and a `BasicJsonType` value for inequality, in either order. Returns `#!cpp !(lhs == rhs)` (or,
for the reversed order, `#!cpp !(rhs == lhs)`), see [operator==](operator_eq.md).
Since `operator!=` is defined as the negation of [`operator==`](operator_eq.md), it follows the same rules for
special cases: for instance, since a [discarded](is_discarded.md) view is never equal to anything by default (see
[operator=='s Notes](operator_eq.md#notes)), it is never *unequal* to anything either -- `#!cpp discarded != discarded`
is also `#!cpp false`, exactly as for a discarded `BasicJsonType` value.
## Parameters
`lhs` (in)
: first value to consider
`rhs` (in)
: second value to consider
## Return value
whether the values `lhs` and `rhs` are not equal
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to either operand, or to the document(s) a
view refers to.
## Exceptions
May throw `#!cpp std::bad_alloc`, propagated from [`operator==`](operator_eq.md#exceptions). Unlike most other
`basic_json_view` functions, `operator!=` is not `#!cpp noexcept`.
## Complexity
Linear, as [`operator==`](operator_eq.md#complexity).
## Notes
See the [Notes](operator_eq.md#notes) of `operator==` -- in particular for how an object's members are compared
(order matters for [`ordered_json_view`](../ordered_json_view.md) but not for [`json_view`](../json_view.md)) and
for how discarded views compare.
No ordering comparison (`#!cpp operator<`) is provided for `basic_json_view`; [`materialize()`](materialize.md) is
the way to get a `BasicJsonType` value that supports it.
## Examples
??? example
The example below asserts, as a test would, that a received document differs from an unwanted value, and shows
that -- as for [`json`](../json.md)/[`ordered_json`](../ordered_json.md) -- reordering an object's members is
detected as a difference for an `ordered_json_view` but not for a `json_view`.
```cpp
--8<-- "examples/basic_json_view__operator_ne.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__operator_ne.output"
```
## See also
- [operator==](operator_eq.md) - compare for equality
- [materialize](materialize.md) - build a `BasicJsonType` value, e.g. to keep comparing after the document is gone
- [`BasicJsonType::operator!=`](../basic_json/operator_ne.md) - the corresponding function of `basic_json`
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,30 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
using json_document = nlohmann::json_document;
using json = nlohmann::json;
int main()
{
// two snapshots of a polled configuration endpoint -- compare them
// directly as views, without ever building a nlohmann::json value for
// either one
const json_document previous = json_document::parse(
R"({"name": "cache", "port": 6379, "timeout": 30})");
const json_document current = json_document::parse(
R"({"port": 6379.0, "timeout": 30, "name": "cache"})");
// same members, reordered, and 6379 written as a float -- operator==
// treats them the same way BasicJsonType::operator== would
std::cout << std::boolalpha << (previous.root() == current.root()) << '\n';
// an actually changed value is detected the same way
const json_document changed = json_document::parse(
R"({"name": "cache", "port": 6380, "timeout": 30})");
std::cout << (previous.root() == changed.root()) << '\n';
// comparing a view directly against an expected json value -- handy in a
// test, without materializing the received document at all
const json expected = {{"name", "cache"}, {"port", 6379}, {"timeout", 30}};
std::cout << (previous.root() == expected) << '\n';
}
@@ -0,0 +1,3 @@
true
false
true
@@ -0,0 +1,28 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
using json_document = nlohmann::json_document;
using ordered_json_document = nlohmann::ordered_json_document;
using json = nlohmann::json;
int main()
{
// assert, as a test would, that a received document differs from an
// unwanted shape -- without ever materializing it into a json value just
// to compare
const json_document received = json_document::parse(
R"({"status": "ok", "code": 200})");
const json unwanted = {{"status", "error"}, {"code", 500}};
std::cout << std::boolalpha << (received.root() != unwanted) << '\n';
// json (std::map) compares object members regardless of order ...
const json_document a = json_document::parse(R"({"a": 1, "b": 2})");
const json_document b = json_document::parse(R"({"b": 2, "a": 1})");
std::cout << (a.root() != b.root()) << '\n';
// ... but ordered_json (ordered_map) compares them in the order they
// appear, so the very same reordering is detected as a difference
const ordered_json_document oa = ordered_json_document::parse(R"({"a": 1, "b": 2})");
const ordered_json_document ob = ordered_json_document::parse(R"({"b": 2, "a": 1})");
std::cout << (oa.root() != ob.root()) << '\n';
}
@@ -0,0 +1,3 @@
true
false
true
+5 -1
View File
@@ -139,7 +139,11 @@ whenever any of the other conditions above was not met.
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
view has no `basic_json` value to point at, so the exception is created without one, regardless of how view has no `basic_json` value to point at, so the exception is created without one, regardless of how
`BasicJsonType` was built. `BasicJsonType` was built.
- **Comparison is not (yet) provided** by `basic_json_view`. For now, - **Ordering comparisons are not provided** by `basic_json_view` -- there is no `#!cpp operator<`.
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
[`materialize()`](../api/basic_json_view/materialize.md) or [`parse()`](../api/basic_json/parse.md) would produce
equal values for them, without ever building a tree to do it. For ordering, too,
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can compare. [`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can compare.
## Getting values out without copying ## Getting values out without copying
+2
View File
@@ -281,6 +281,8 @@ nav:
- 'operator bool': api/basic_json_view/operator_bool.md - 'operator bool': api/basic_json_view/operator_bool.md
- 'operator<<': api/basic_json_view/operator_ltlt.md - 'operator<<': api/basic_json_view/operator_ltlt.md
- 'operator[]': api/basic_json_view/operator[].md - 'operator[]': api/basic_json_view/operator[].md
- 'operator==': api/basic_json_view/operator_eq.md
- 'operator!=': api/basic_json_view/operator_ne.md
- 'size': api/basic_json_view/size.md - 'size': api/basic_json_view/size.md
- 'source_offset': api/basic_json_view/source_offset.md - 'source_offset': api/basic_json_view/source_offset.md
- 'type': api/basic_json_view/type.md - 'type': api/basic_json_view/type.md
+317
View File
@@ -0,0 +1,317 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <algorithm> // sort, stable_sort
#include <cstddef> // size_t
#include <string> // string
#include <utility> // move, pair
#include <vector> // vector
#include <nlohmann/json.hpp>
#include <nlohmann/detail/view/macro_scope.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
namespace view
{
// Equality of views, and of views with basic_json values, with the semantics
// of basic_json's operator== applied to the values parse() would produce:
// numbers compare by value across their types, an object is compared by its
// members with duplicate keys resolved as parse() resolves them (the last
// value, at the position of the first occurrence), and in document order if
// the object type keeps an order (ordered_json), by key otherwise.
/// one side of a comparison: a view
template<typename BasicJsonType, typename View>
class view_side
{
public:
using string_view_t = typename View::string_view_t;
explicit view_side(const View& v) noexcept
: m_view(v)
{}
value_t type() const noexcept
{
return m_view.type();
}
std::size_t size() const noexcept
{
return m_view.size();
}
string_view_t string() const
{
return m_view.get_string();
}
/// a number, boolean, or null as a basic_json value (no allocation)
BasicJsonType scalar() const
{
switch (m_view.type())
{
case value_t::number_integer:
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
case value_t::number_unsigned:
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
case value_t::number_float:
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
case value_t::boolean:
return BasicJsonType(m_view.template get<bool>());
case value_t::null:
case value_t::object:
case value_t::array:
case value_t::string:
case value_t::binary:
case value_t::discarded:
default:
return BasicJsonType(nullptr);
}
}
void elements(std::vector<view_side>& out) const
{
out.reserve(m_view.size());
for (const View e : m_view)
{
out.emplace_back(e);
}
}
/// the members as parse() keeps them: one per key, the last value at the
/// position of the first occurrence; in that order, or sorted by key
void members(std::vector<std::pair<string_view_t, view_side>>& out, bool ordered) const
{
struct member
{
string_view_t key;
View value;
std::size_t position;
};
std::vector<member> all;
all.reserve(m_view.size());
std::size_t position = 0;
for (auto it = m_view.begin(); it != m_view.end(); ++it)
{
all.push_back(member{it.key(), it.value(), position++});
}
std::stable_sort(all.begin(), all.end(), [](const member & a, const member & b)
{
return a.key < b.key;
});
std::vector<member> unique;
unique.reserve(all.size());
for (std::size_t i = 0; i < all.size();)
{
std::size_t last = i;
while (last + 1 < all.size() && all[last + 1].key == all[i].key)
{
++last;
}
unique.push_back(member{all[i].key, all[last].value, all[i].position});
i = last + 1;
}
if (ordered)
{
std::sort(unique.begin(), unique.end(), [](const member & a, const member & b)
{
return a.position < b.position;
});
}
out.reserve(unique.size());
for (const member& m : unique)
{
out.emplace_back(m.key, view_side(m.value));
}
}
private:
View m_view;
};
/// the other side of a comparison: a basic_json value
template<typename BasicJsonType, typename StringView>
class json_side
{
public:
using string_view_t = StringView;
explicit json_side(const BasicJsonType& j) noexcept
: m_json(&j)
{}
value_t type() const noexcept
{
return m_json->type();
}
std::size_t size() const noexcept
{
return m_json->size();
}
string_view_t string() const
{
const auto& s = m_json->template get_ref<const typename BasicJsonType::string_t&>();
return string_view_t(s.data(), s.size());
}
BasicJsonType scalar() const
{
return *m_json;
}
void elements(std::vector<json_side>& out) const
{
out.reserve(m_json->size());
for (const auto& e : *m_json)
{
out.emplace_back(e);
}
}
void members(std::vector<std::pair<string_view_t, json_side>>& out, bool ordered) const
{
out.reserve(m_json->size());
for (auto it = m_json->cbegin(); it != m_json->cend(); ++it)
{
out.emplace_back(string_view_t(it.key().data(), it.key().size()), json_side(it.value()));
}
if (!ordered)
{
std::sort(out.begin(), out.end(), [](const std::pair<string_view_t, json_side>& a, const std::pair<string_view_t, json_side>& b)
{
return a.first < b.first;
});
}
}
private:
const BasicJsonType* m_json;
};
/// whether two sides are equal; iterative, so that the nesting depth is
/// limited by memory only
template<typename BasicJsonType, typename A, typename B>
bool equal(const A& a0, const B& b0)
{
using string_view_t = typename A::string_view_t;
const bool ordered = is_ordered_map<typename BasicJsonType::object_t>::value;
struct frame
{
std::vector<A> elements_a{};
std::vector<B> elements_b{};
std::vector<std::pair<string_view_t, A>> members_a{};
std::vector<std::pair<string_view_t, B>> members_b{};
bool object = false;
std::size_t next = 0;
};
std::vector<frame> stack;
A a = a0;
B b = b0;
for (;;)
{
const value_t ta = a.type();
const value_t tb = b.type();
const bool numbers = (ta == value_t::number_integer || ta == value_t::number_unsigned || ta == value_t::number_float)
&& (tb == value_t::number_integer || tb == value_t::number_unsigned || tb == value_t::number_float);
if (ta == value_t::discarded || tb == value_t::discarded)
{
// basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)
if (ta != tb || !(BasicJsonType(value_t::discarded) == BasicJsonType(value_t::discarded)))
{
return false;
}
}
else
{
if (!numbers && ta != tb)
{
return false;
}
if (ta == value_t::string)
{
if (!(a.string() == b.string()))
{
return false;
}
}
else if (ta == value_t::array || ta == value_t::object)
{
if (a.size() != b.size() && ta == value_t::array)
{
return false;
}
frame f;
f.object = ta == value_t::object;
if (f.object)
{
a.members(f.members_a, ordered);
b.members(f.members_b, ordered);
if (f.members_a.size() != f.members_b.size())
{
return false;
}
}
else
{
a.elements(f.elements_a);
b.elements(f.elements_b);
}
stack.push_back(std::move(f));
}
else if (!(a.scalar() == b.scalar())) // numbers (also of different types), null, boolean
{
return false;
}
}
// the next pair of values
for (;;)
{
if (stack.empty())
{
return true;
}
frame& f = stack.back();
const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size();
if (f.next == count)
{
stack.pop_back();
continue;
}
if (f.object)
{
if (!(f.members_a[f.next].first == f.members_b[f.next].first))
{
return false;
}
a = f.members_a[f.next].second;
b = f.members_b[f.next].second;
}
else
{
a = f.elements_a[f.next];
b = f.elements_b[f.next];
}
++f.next;
break;
}
}
}
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+1 -1
View File
@@ -81,7 +81,7 @@ BasicJsonType materialize(const document_data& d, const node* n)
++n; ++n;
break; break;
case value_t::number_float: case value_t::number_float:
sax.number_float(float_value<typename BasicJsonType::number_float_t>(d.str(*n), *n), no_token); sax.number_float(float_value<typename BasicJsonType::number_float_t>(d, *n), no_token);
++n; ++n;
break; break;
case value_t::boolean: case value_t::boolean:
+94
View File
@@ -9,11 +9,15 @@
#pragma once #pragma once
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint64_t
#include <string> // string #include <string> // string
#include <type_traits> // integral_constant
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <nlohmann/detail/view/document_data.hpp>
#include <nlohmann/detail/view/macro_scope.hpp> #include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp> #include <nlohmann/detail/view/node.hpp>
#include <nlohmann/detail/view/scan.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@@ -62,6 +66,96 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
return convert_float<FloatType>(first, last, dot, mantissa_end); return convert_float<FloatType>(first, last, dot, mantissa_end);
} }
/*!
@brief the digits of a float token with at most 19 digits, from its layout
The digit layout recorded while parsing says where the integer digits, the
fraction digits, and the exponent are, so the digits are read eight at a
time without scanning.
@param[in] p first character of the token
@param[in] e end of the token
@param[in] limit end of the readable memory (the source text)
*/
NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
{
const bool negative = *p == '-';
p += negative ? 1 : 0;
std::uint64_t w = parse_upto19(p, int_digits, limit);
p += int_digits;
std::int64_t q = 0;
if (frac_digits != 0)
{
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
p += 1 + frac_digits;
q = -static_cast<std::int64_t>(frac_digits);
}
if (p != e)
{
// [eE][+-]digits; huge exponents saturate (the parser rejected overflow)
++p;
const bool exp_negative = *p == '-';
p += (*p == '-' || *p == '+') ? 1 : 0;
std::int64_t exp_value = 0;
for (; p != e; ++p)
{
if (exp_value < 0x10000000)
{
exp_value = (exp_value * 10) + (*p - '0');
}
}
q += exp_negative ? -exp_value : exp_value;
}
float_significand d;
d.w = w;
d.exponent = q;
d.negative = negative;
return d;
}
/*!
@brief the value of a float token with at most 19 digits, from its layout
The result is correctly rounded by the lexer's conversion
(detail::decimal_to_float(): Clinger's fast path where both operands are
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
digits), so it is the value parse() produces.
*/
template<typename FloatType>
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
{
return decimal_to_float<FloatType>(layout_decimal(p, e, int_digits, frac_digits, limit));
}
/// the value of the float token of a node, as parse() converts it; floats and
/// doubles with at most 19 digits are converted from the digit layout
template<typename FloatType>
FloatType float_value(const document_data& d, const node& n)
{
return float_value<FloatType>(d, n, std::integral_constant<bool, has_native_float_format<FloatType>::value> {});
}
template<typename FloatType>
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
{
const unsigned int_digits = n.extra & 0xFFu;
const unsigned frac_digits = n.extra >> 8u;
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
{
// (a float token not written by an edit is in the text)
const auto* const first = reinterpret_cast<const unsigned char*>(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
return layout_float<FloatType>(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(d.src + d.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
return float_value<FloatType>(d.str(n), n);
}
template<typename FloatType>
FloatType float_value(const document_data& d, const node& n, std::false_type /*other*/)
{
return float_value<FloatType>(d.str(n), n);
}
} // namespace view } // namespace view
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+1 -1
View File
@@ -256,7 +256,7 @@ class view_serializer
} }
else else
{ {
write_float(float_value<number_float_t>(m_doc.str(n), n)); write_float(float_value<number_float_t>(m_doc, n));
} }
break; break;
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump()) case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
+1 -1
View File
@@ -49,7 +49,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::number_integer: case value_t::number_integer:
return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n)))); return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n))));
case value_t::number_float: case value_t::number_float:
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d.str(n), n)); return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n));
case value_t::boolean: case value_t::boolean:
return static_cast<T>((n.flags & node_flags::is_true) != 0); return static_cast<T>((n.flags & node_flags::is_true) != 0);
case value_t::null: case value_t::null:
+46
View File
@@ -47,6 +47,7 @@
#endif #endif
#include <nlohmann/detail/view/builder.hpp> #include <nlohmann/detail/view/builder.hpp>
#include <nlohmann/detail/view/compare.hpp>
#include <nlohmann/detail/view/document_data.hpp> #include <nlohmann/detail/view/document_data.hpp>
#include <nlohmann/detail/view/errors.hpp> #include <nlohmann/detail/view/errors.hpp>
#include <nlohmann/detail/view/input.hpp> #include <nlohmann/detail/view/input.hpp>
@@ -603,6 +604,44 @@ class basic_json_view
} }
#endif #endif
////////////////
// comparison //
////////////////
/// whether the values parse() would produce for two views are equal, as
/// by BasicJsonType's operator== (numbers by value, objects by their
/// members with duplicate keys resolved as parse() resolves them)
friend bool operator==(const basic_json_view& a, const basic_json_view& b)
{
return detail::view::equal<BasicJsonType>(side(a), side(b));
}
friend bool operator!=(const basic_json_view& a, const basic_json_view& b)
{
return !(a == b);
}
/// whether the value parse() would produce for a view equals a value
friend bool operator==(const basic_json_view& a, const BasicJsonType& j)
{
return detail::view::equal<BasicJsonType>(side(a), json_side_t(j));
}
friend bool operator==(const BasicJsonType& j, const basic_json_view& a)
{
return a == j;
}
friend bool operator!=(const basic_json_view& a, const BasicJsonType& j)
{
return !(a == j);
}
friend bool operator!=(const BasicJsonType& j, const basic_json_view& a)
{
return !(a == j);
}
///////////////// /////////////////
// materialize // // materialize //
///////////////// /////////////////
@@ -635,6 +674,13 @@ class basic_json_view
: m_doc(d), m_node(n) : m_doc(d), m_node(n)
{} {}
using json_side_t = detail::view::json_side<BasicJsonType, string_view_t>;
static detail::view::view_side<BasicJsonType, basic_json_view> side(const basic_json_view& v) noexcept
{
return detail::view::view_side<BasicJsonType, basic_json_view>(v);
}
/// the number of source bytes of this value (estimated for values with /// the number of source bytes of this value (estimated for values with
/// decoded strings) /// decoded strings)
std::size_t source_extent() const noexcept std::size_t source_extent() const noexcept
+464 -3
View File
@@ -1580,6 +1580,326 @@ inline bool build(document_data& d, const char* src, std::size_t size, bool comm
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/compare.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include <algorithm> // sort, stable_sort
#include <cstddef> // size_t
#include <string> // string
#include <utility> // move, pair
#include <vector> // vector
// #include <nlohmann/json.hpp>
// #include <nlohmann/detail/view/macro_scope.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
namespace view
{
// Equality of views, and of views with basic_json values, with the semantics
// of basic_json's operator== applied to the values parse() would produce:
// numbers compare by value across their types, an object is compared by its
// members with duplicate keys resolved as parse() resolves them (the last
// value, at the position of the first occurrence), and in document order if
// the object type keeps an order (ordered_json), by key otherwise.
/// one side of a comparison: a view
template<typename BasicJsonType, typename View>
class view_side
{
public:
using string_view_t = typename View::string_view_t;
explicit view_side(const View& v) noexcept
: m_view(v)
{}
value_t type() const noexcept
{
return m_view.type();
}
std::size_t size() const noexcept
{
return m_view.size();
}
string_view_t string() const
{
return m_view.get_string();
}
/// a number, boolean, or null as a basic_json value (no allocation)
BasicJsonType scalar() const
{
switch (m_view.type())
{
case value_t::number_integer:
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
case value_t::number_unsigned:
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
case value_t::number_float:
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
case value_t::boolean:
return BasicJsonType(m_view.template get<bool>());
case value_t::null:
case value_t::object:
case value_t::array:
case value_t::string:
case value_t::binary:
case value_t::discarded:
default:
return BasicJsonType(nullptr);
}
}
void elements(std::vector<view_side>& out) const
{
out.reserve(m_view.size());
for (const View e : m_view)
{
out.emplace_back(e);
}
}
/// the members as parse() keeps them: one per key, the last value at the
/// position of the first occurrence; in that order, or sorted by key
void members(std::vector<std::pair<string_view_t, view_side>>& out, bool ordered) const
{
struct member
{
string_view_t key;
View value;
std::size_t position;
};
std::vector<member> all;
all.reserve(m_view.size());
std::size_t position = 0;
for (auto it = m_view.begin(); it != m_view.end(); ++it)
{
all.push_back(member{it.key(), it.value(), position++});
}
std::stable_sort(all.begin(), all.end(), [](const member & a, const member & b)
{
return a.key < b.key;
});
std::vector<member> unique;
unique.reserve(all.size());
for (std::size_t i = 0; i < all.size();)
{
std::size_t last = i;
while (last + 1 < all.size() && all[last + 1].key == all[i].key)
{
++last;
}
unique.push_back(member{all[i].key, all[last].value, all[i].position});
i = last + 1;
}
if (ordered)
{
std::sort(unique.begin(), unique.end(), [](const member & a, const member & b)
{
return a.position < b.position;
});
}
out.reserve(unique.size());
for (const member& m : unique)
{
out.emplace_back(m.key, view_side(m.value));
}
}
private:
View m_view;
};
/// the other side of a comparison: a basic_json value
template<typename BasicJsonType, typename StringView>
class json_side
{
public:
using string_view_t = StringView;
explicit json_side(const BasicJsonType& j) noexcept
: m_json(&j)
{}
value_t type() const noexcept
{
return m_json->type();
}
std::size_t size() const noexcept
{
return m_json->size();
}
string_view_t string() const
{
const auto& s = m_json->template get_ref<const typename BasicJsonType::string_t&>();
return string_view_t(s.data(), s.size());
}
BasicJsonType scalar() const
{
return *m_json;
}
void elements(std::vector<json_side>& out) const
{
out.reserve(m_json->size());
for (const auto& e : *m_json)
{
out.emplace_back(e);
}
}
void members(std::vector<std::pair<string_view_t, json_side>>& out, bool ordered) const
{
out.reserve(m_json->size());
for (auto it = m_json->cbegin(); it != m_json->cend(); ++it)
{
out.emplace_back(string_view_t(it.key().data(), it.key().size()), json_side(it.value()));
}
if (!ordered)
{
std::sort(out.begin(), out.end(), [](const std::pair<string_view_t, json_side>& a, const std::pair<string_view_t, json_side>& b)
{
return a.first < b.first;
});
}
}
private:
const BasicJsonType* m_json;
};
/// whether two sides are equal; iterative, so that the nesting depth is
/// limited by memory only
template<typename BasicJsonType, typename A, typename B>
bool equal(const A& a0, const B& b0)
{
using string_view_t = typename A::string_view_t;
const bool ordered = is_ordered_map<typename BasicJsonType::object_t>::value;
struct frame
{
std::vector<A> elements_a{};
std::vector<B> elements_b{};
std::vector<std::pair<string_view_t, A>> members_a{};
std::vector<std::pair<string_view_t, B>> members_b{};
bool object = false;
std::size_t next = 0;
};
std::vector<frame> stack;
A a = a0;
B b = b0;
for (;;)
{
const value_t ta = a.type();
const value_t tb = b.type();
const bool numbers = (ta == value_t::number_integer || ta == value_t::number_unsigned || ta == value_t::number_float)
&& (tb == value_t::number_integer || tb == value_t::number_unsigned || tb == value_t::number_float);
if (ta == value_t::discarded || tb == value_t::discarded)
{
// basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)
if (ta != tb || !(BasicJsonType(value_t::discarded) == BasicJsonType(value_t::discarded)))
{
return false;
}
}
else
{
if (!numbers && ta != tb)
{
return false;
}
if (ta == value_t::string)
{
if (!(a.string() == b.string()))
{
return false;
}
}
else if (ta == value_t::array || ta == value_t::object)
{
if (a.size() != b.size() && ta == value_t::array)
{
return false;
}
frame f;
f.object = ta == value_t::object;
if (f.object)
{
a.members(f.members_a, ordered);
b.members(f.members_b, ordered);
if (f.members_a.size() != f.members_b.size())
{
return false;
}
}
else
{
a.elements(f.elements_a);
b.elements(f.elements_b);
}
stack.push_back(std::move(f));
}
else if (!(a.scalar() == b.scalar())) // numbers (also of different types), null, boolean
{
return false;
}
}
// the next pair of values
for (;;)
{
if (stack.empty())
{
return true;
}
frame& f = stack.back();
const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size();
if (f.next == count)
{
stack.pop_back();
continue;
}
if (f.object)
{
if (!(f.members_a[f.next].first == f.members_b[f.next].first))
{
return false;
}
a = f.members_a[f.next].second;
b = f.members_b[f.next].second;
}
else
{
a = f.elements_a[f.next];
b = f.elements_b[f.next];
}
++f.next;
break;
}
}
}
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/document_data.hpp> // #include <nlohmann/detail/view/document_data.hpp>
// #include <nlohmann/detail/view/errors.hpp> // #include <nlohmann/detail/view/errors.hpp>
@@ -2217,13 +2537,19 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint64_t
#include <string> // string #include <string> // string
#include <type_traits> // integral_constant
// #include <nlohmann/json.hpp> // #include <nlohmann/json.hpp>
// #include <nlohmann/detail/view/document_data.hpp>
// #include <nlohmann/detail/view/macro_scope.hpp> // #include <nlohmann/detail/view/macro_scope.hpp>
// #include <nlohmann/detail/view/node.hpp> // #include <nlohmann/detail/view/node.hpp>
// #include <nlohmann/detail/view/scan.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@@ -2272,6 +2598,96 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
return convert_float<FloatType>(first, last, dot, mantissa_end); return convert_float<FloatType>(first, last, dot, mantissa_end);
} }
/*!
@brief the digits of a float token with at most 19 digits, from its layout
The digit layout recorded while parsing says where the integer digits, the
fraction digits, and the exponent are, so the digits are read eight at a
time without scanning.
@param[in] p first character of the token
@param[in] e end of the token
@param[in] limit end of the readable memory (the source text)
*/
NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
{
const bool negative = *p == '-';
p += negative ? 1 : 0;
std::uint64_t w = parse_upto19(p, int_digits, limit);
p += int_digits;
std::int64_t q = 0;
if (frac_digits != 0)
{
w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit);
p += 1 + frac_digits;
q = -static_cast<std::int64_t>(frac_digits);
}
if (p != e)
{
// [eE][+-]digits; huge exponents saturate (the parser rejected overflow)
++p;
const bool exp_negative = *p == '-';
p += (*p == '-' || *p == '+') ? 1 : 0;
std::int64_t exp_value = 0;
for (; p != e; ++p)
{
if (exp_value < 0x10000000)
{
exp_value = (exp_value * 10) + (*p - '0');
}
}
q += exp_negative ? -exp_value : exp_value;
}
float_significand d;
d.w = w;
d.exponent = q;
d.negative = negative;
return d;
}
/*!
@brief the value of a float token with at most 19 digits, from its layout
The result is correctly rounded by the lexer's conversion
(detail::decimal_to_float(): Clinger's fast path where both operands are
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
digits), so it is the value parse() produces.
*/
template<typename FloatType>
NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept
{
return decimal_to_float<FloatType>(layout_decimal(p, e, int_digits, frac_digits, limit));
}
/// the value of the float token of a node, as parse() converts it; floats and
/// doubles with at most 19 digits are converted from the digit layout
template<typename FloatType>
FloatType float_value(const document_data& d, const node& n)
{
return float_value<FloatType>(d, n, std::integral_constant<bool, has_native_float_format<FloatType>::value> {});
}
template<typename FloatType>
FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/)
{
const unsigned int_digits = n.extra & 0xFFu;
const unsigned frac_digits = n.extra >> 8u;
if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many")
{
// (a float token not written by an edit is in the text)
const auto* const first = reinterpret_cast<const unsigned char*>(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
return layout_float<FloatType>(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(d.src + d.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
return float_value<FloatType>(d.str(n), n);
}
template<typename FloatType>
FloatType float_value(const document_data& d, const node& n, std::false_type /*other*/)
{
return float_value<FloatType>(d.str(n), n);
}
} // namespace view } // namespace view
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
@@ -2340,7 +2756,7 @@ BasicJsonType materialize(const document_data& d, const node* n)
++n; ++n;
break; break;
case value_t::number_float: case value_t::number_float:
sax.number_float(float_value<typename BasicJsonType::number_float_t>(d.str(*n), *n), no_token); sax.number_float(float_value<typename BasicJsonType::number_float_t>(d, *n), no_token);
++n; ++n;
break; break;
case value_t::boolean: case value_t::boolean:
@@ -2829,7 +3245,7 @@ class view_serializer
} }
else else
{ {
write_float(float_value<number_float_t>(m_doc.str(n), n)); write_float(float_value<number_float_t>(m_doc, n));
} }
break; break;
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump()) case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
@@ -3164,7 +3580,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::number_integer: case value_t::number_integer:
return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n)))); return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n))));
case value_t::number_float: case value_t::number_float:
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d.str(n), n)); return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n));
case value_t::boolean: case value_t::boolean:
return static_cast<T>((n.flags & node_flags::is_true) != 0); return static_cast<T>((n.flags & node_flags::is_true) != 0);
case value_t::null: case value_t::null:
@@ -3766,6 +4182,44 @@ class basic_json_view
} }
#endif #endif
////////////////
// comparison //
////////////////
/// whether the values parse() would produce for two views are equal, as
/// by BasicJsonType's operator== (numbers by value, objects by their
/// members with duplicate keys resolved as parse() resolves them)
friend bool operator==(const basic_json_view& a, const basic_json_view& b)
{
return detail::view::equal<BasicJsonType>(side(a), side(b));
}
friend bool operator!=(const basic_json_view& a, const basic_json_view& b)
{
return !(a == b);
}
/// whether the value parse() would produce for a view equals a value
friend bool operator==(const basic_json_view& a, const BasicJsonType& j)
{
return detail::view::equal<BasicJsonType>(side(a), json_side_t(j));
}
friend bool operator==(const BasicJsonType& j, const basic_json_view& a)
{
return a == j;
}
friend bool operator!=(const basic_json_view& a, const BasicJsonType& j)
{
return !(a == j);
}
friend bool operator!=(const BasicJsonType& j, const basic_json_view& a)
{
return !(a == j);
}
///////////////// /////////////////
// materialize // // materialize //
///////////////// /////////////////
@@ -3798,6 +4252,13 @@ class basic_json_view
: m_doc(d), m_node(n) : m_doc(d), m_node(n)
{} {}
using json_side_t = detail::view::json_side<BasicJsonType, string_view_t>;
static detail::view::view_side<BasicJsonType, basic_json_view> side(const basic_json_view& v) noexcept
{
return detail::view::view_side<BasicJsonType, basic_json_view>(v);
}
/// the number of source bytes of this value (estimated for values with /// the number of source bytes of this value (estimated for values with
/// decoded strings) /// decoded strings)
std::size_t source_extent() const noexcept std::size_t source_extent() const noexcept
+93 -1
View File
@@ -873,7 +873,15 @@ TEST_CASE("json_view values")
std::mt19937_64 rng(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) std::mt19937_64 rng(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
std::vector<std::string> tokens = {"0.1", "-0.0", "1e308", "1.7976931348623157e308", "2.2250738585072011e-308", "4.9e-324", "5e-324", std::vector<std::string> tokens = {"0.1", "-0.0", "1e308", "1.7976931348623157e308", "2.2250738585072011e-308", "4.9e-324", "5e-324",
"0.1000000000000000055511151231257827021181583404541015625", "123456789012345678901234567890", "0.1000000000000000055511151231257827021181583404541015625", "123456789012345678901234567890",
"9007199254740993", "1.00000000000000011102230246251565404236316680908203125", "7.2057594037927933e16" "9007199254740993", "1.00000000000000011102230246251565404236316680908203125", "7.2057594037927933e16",
// around the limits of the conversion from the digit layout: 19 and 20
// digits, and those of Clinger's fast path (2^53, 10^22)
"1234567890.123456789", "1234567890.1234567891", "0.0000000000000000001", "123456789012345678.9",
"9007199254740992.0", "9007199254740993.0", "9007199254740994.0", "1.5e22", "1.5e23", "15e-22", "15e-23",
"1e-400", "0.0e0", "-0.0e-5", "12E+3", "12e-0",
// and of float: 2^24 + 1 and 2^24 + 3 (ties), the subnormal and normal limits
"16777217", "16777219", "1.4e-45", "7.006492321624085e-46", "7.006492321624086e-46",
"1.17549435e-38", "0.30000001192092896", "3.4028234e37"
}; };
for (int i = 0; i < 20000; ++i) for (int i = 0; i < 20000; ++i)
{ {
@@ -1185,3 +1193,87 @@ TEST_CASE("json_view dump")
CHECK(json_view().dump() == json(json::value_t::discarded).dump()); CHECK(json_view().dump() == json(json::value_t::discarded).dump());
} }
} }
TEST_CASE("json_view comparison")
{
SECTION("equality of the values parse() produces")
{
generator g;
std::vector<std::string> texts;
for (int i = 0; i < 600; ++i)
{
std::string text;
g.value(text, 0);
texts.push_back(text);
// the same value written differently: sorted keys, canonical numbers
texts.push_back(json::parse(text).dump(1));
}
for (std::size_t i = 0; i + 2 < texts.size(); ++i)
{
for (std::size_t k = i; k < i + 3; ++k)
{
CAPTURE(texts[i]);
CAPTURE(texts[k]);
const json_document a = json_document::parse(texts[i]);
const json_document b = json_document::parse(texts[k]);
const json ja = json::parse(texts[i]);
const json jb = json::parse(texts[k]);
CHECK((a.root() == b.root()) == (ja == jb));
CHECK((a.root() != b.root()) == (ja != jb));
CHECK((a.root() == jb) == (ja == jb));
CHECK((jb == a.root()) == (ja == jb));
CHECK((a.root() != jb) == (ja != jb));
CHECK((jb != a.root()) == (ja != jb));
// ordered_json compares members in order
const ordered_json_document oa = ordered_json_document::parse(texts[i]);
const ordered_json_document ob = ordered_json_document::parse(texts[k]);
const ordered_json oja = ordered_json::parse(texts[i]);
const ordered_json ojb = ordered_json::parse(texts[k]);
CHECK((oa.root() == ob.root()) == (oja == ojb));
CHECK((oa.root() == ojb) == (oja == ojb));
}
}
}
SECTION("numbers, duplicate keys, member order")
{
const auto same = [](const char* x, const char* y)
{
return json_document::parse(x).root() == json_document::parse(y).root();
};
CHECK(same("1", "1.0"));
CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]"));
CHECK(!same("1", "1.5"));
CHECK(same("18446744073709551615", "18446744073709551615"));
CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})"));
CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})"));
CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})"));
CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})"));
CHECK(!same("[1, 2]", "[2, 1]"));
CHECK(!same("\"a\"", "\"b\""));
CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\""));
CHECK(!same("null", "false"));
CHECK(!same("[]", "{}"));
CHECK(ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})").root() == ordered_json_document::parse(R"({"a": 3, "b": 2})").root());
CHECK(ordered_json_document::parse(R"({"a": 1, "b": 2})").root() != ordered_json_document::parse(R"({"b": 2, "a": 1})").root());
// discarded values compare as basic_json's do
const json discarded(json::value_t::discarded);
CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested
CHECK((json_view() == discarded) == (discarded == discarded));
CHECK(!(json_view() == json_document::parse("null").root())); // NOLINT(readability-container-size-empty)
CHECK(!(json_document::parse("null").root() == discarded));
}
SECTION("deep nesting")
{
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
const json_document a = json_document::parse(deep);
const json_document b = json_document::parse(deep);
CHECK(a.root() == b.root());
CHECK(a.root() == json::parse(deep));
const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']');
CHECK(a.root() != json_document::parse(other).root());
}
}