diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index e0281f893..3d3c22a02 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -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<<', '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_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::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'); diff --git a/docs/mkdocs/docs/api/basic_json/operator_eq.md b/docs/mkdocs/docs/api/basic_json/operator_eq.md index b575622d1..5eb0478aa 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_eq.md +++ b/docs/mkdocs/docs/api/basic_json/operator_eq.md @@ -166,6 +166,8 @@ Linear. - [operator!=](operator_ne.md) compare for inequality - [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 diff --git a/docs/mkdocs/docs/api/basic_json/operator_ne.md b/docs/mkdocs/docs/api/basic_json/operator_ne.md index 5abb4a5af..25c2d6d89 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ne.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ne.md @@ -89,6 +89,12 @@ Linear. --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 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 diff --git a/docs/mkdocs/docs/api/basic_json_view/index.md b/docs/mkdocs/docs/api/basic_json_view/index.md index da50a61e2..56760db2f 100644 --- a/docs/mkdocs/docs/api/basic_json_view/index.md +++ b/docs/mkdocs/docs/api/basic_json_view/index.md @@ -20,11 +20,12 @@ Moving the document itself does not invalidate its views: the index is heap-allo `basic_json_document` object. `basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element -access, lookup, iteration, and conversion -- [`get()`](get.md), [`get_string()`](get_string.md), +access, lookup, iteration, conversion, and comparison -- [`get()`](get.md), [`get_string()`](get_string.md), [`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on demand. [`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 -comparison. +[`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). [`operator==`](operator_eq.md) and +[`operator!=`](operator_ne.md) compare two views, or a view and a `BasicJsonType` value, without ever building a +`BasicJsonType` value for a view; no ordering comparison (`#!cpp operator<`) is provided. ## Template parameters @@ -107,6 +108,11 @@ comparison. - [**number_token**](number_token.md) - get a number's token text without a copy - [**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 - [**dump**](dump.md) - serialize to a JSON-formatted string diff --git a/docs/mkdocs/docs/api/basic_json_view/operator_eq.md b/docs/mkdocs/docs/api/basic_json_view/operator_eq.md new file mode 100644 index 000000000..6f96f4692 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/operator_eq.md @@ -0,0 +1,107 @@ +# nlohmann::basic_json_view::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. diff --git a/docs/mkdocs/docs/api/basic_json_view/operator_ne.md b/docs/mkdocs/docs/api/basic_json_view/operator_ne.md new file mode 100644 index 000000000..2e85f8abe --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/operator_ne.md @@ -0,0 +1,82 @@ +# nlohmann::basic_json_view::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. diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator_eq.cpp b/docs/mkdocs/docs/examples/basic_json_view__operator_eq.cpp new file mode 100644 index 000000000..ac59838f4 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__operator_eq.cpp @@ -0,0 +1,30 @@ +#include +#include + +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'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator_eq.output b/docs/mkdocs/docs/examples/basic_json_view__operator_eq.output new file mode 100644 index 000000000..87f8d93a0 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__operator_eq.output @@ -0,0 +1,3 @@ +true +false +true diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator_ne.cpp b/docs/mkdocs/docs/examples/basic_json_view__operator_ne.cpp new file mode 100644 index 000000000..e284ae1a1 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__operator_ne.cpp @@ -0,0 +1,28 @@ +#include +#include + +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'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator_ne.output b/docs/mkdocs/docs/examples/basic_json_view__operator_ne.output new file mode 100644 index 000000000..87f8d93a0 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__operator_ne.output @@ -0,0 +1,3 @@ +true +false +true diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 5d992a945..8030fe115 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -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 view has no `basic_json` value to point at, so the exception is created without one, regardless of how `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. ## Getting values out without copying diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 8b6b6ced8..69cbf5c16 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -281,6 +281,8 @@ nav: - 'operator bool': api/basic_json_view/operator_bool.md - 'operator<<': api/basic_json_view/operator_ltlt.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 - 'source_offset': api/basic_json_view/source_offset.md - 'type': api/basic_json_view/type.md