From 0adb7783a408b7904068b147257cfd0322764be6 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 6 Oct 2026 10:48:06 +0200 Subject: [PATCH] Add element access, iteration, values, and JSON pointers to json_view Give basic_json_view the read-only access functions of basic_json: operator[] and at() with keys and indices, front()/back(), find(), contains(), count(), begin()/end() and cbegin()/cend(), items() with structured bindings from C++17 on, and type_name(). Exceptions have the ids and messages of the const functions of basic_json. Where basic_json has undefined behavior the view answers safely: operator[] with a missing key or an out-of-range index returns a discarded view, and front()/back() of an empty container throw invalid_iterator.214. Objects are iterated in document order, and all members are visited; duplicate-key lookups find the first member (as yyjson and simdjson do), while parse(), materialize(), and the map conversions keep the last value, as parse() does. Keys of up to 16 bytes are compared with two overlapping loads. Add value conversions: get()/get_to() for arithmetic types, bool, nullptr_t, strings (std::basic_string copied, string_view_t without a copy), BasicJsonType, views, std::vector, and maps with string keys; get_string() for the string without a copy; number_token() for the number exactly as written in the source; value() with keys and JSON pointers; and operator[]/at()/ contains() with JSON pointers. Everything else, including types with from_json(), goes through materialize() of that subtree. get() of arithmetic types is inlined down to the conversion, so reading an integer needs no call. detail::json_pointer_access exposes a pointer's reference tokens to code outside basic_json. Signed-off-by: Niels Lohmann --- BUILD.bazel | 4 + docs/docset/docSet.sql | 18 + docs/mkdocs/docs/api/basic_json/at.md | 1 + docs/mkdocs/docs/api/basic_json/back.md | 1 + docs/mkdocs/docs/api/basic_json/begin.md | 2 + docs/mkdocs/docs/api/basic_json/cbegin.md | 1 + docs/mkdocs/docs/api/basic_json/cend.md | 1 + docs/mkdocs/docs/api/basic_json/contains.md | 1 + docs/mkdocs/docs/api/basic_json/count.md | 1 + docs/mkdocs/docs/api/basic_json/end.md | 1 + docs/mkdocs/docs/api/basic_json/find.md | 1 + docs/mkdocs/docs/api/basic_json/front.md | 1 + docs/mkdocs/docs/api/basic_json/get.md | 2 + docs/mkdocs/docs/api/basic_json/get_ref.md | 2 + docs/mkdocs/docs/api/basic_json/get_to.md | 1 + docs/mkdocs/docs/api/basic_json/items.md | 2 + docs/mkdocs/docs/api/basic_json/operator[].md | 2 + docs/mkdocs/docs/api/basic_json/type_name.md | 1 + docs/mkdocs/docs/api/basic_json/value.md | 1 + docs/mkdocs/docs/api/basic_json_view/at.md | 132 ++ docs/mkdocs/docs/api/basic_json_view/back.md | 64 + .../api/basic_json_view/basic_json_view.md | 5 +- docs/mkdocs/docs/api/basic_json_view/begin.md | 61 + .../mkdocs/docs/api/basic_json_view/cbegin.md | 49 + docs/mkdocs/docs/api/basic_json_view/cend.md | 49 + .../docs/api/basic_json_view/contains.md | 101 ++ docs/mkdocs/docs/api/basic_json_view/count.md | 66 + docs/mkdocs/docs/api/basic_json_view/end.md | 54 + docs/mkdocs/docs/api/basic_json_view/find.md | 63 + docs/mkdocs/docs/api/basic_json_view/front.md | 61 + docs/mkdocs/docs/api/basic_json_view/get.md | 130 ++ .../docs/api/basic_json_view/get_string.md | 71 + .../mkdocs/docs/api/basic_json_view/get_to.md | 65 + docs/mkdocs/docs/api/basic_json_view/index.md | 39 +- docs/mkdocs/docs/api/basic_json_view/items.md | 86 ++ .../docs/api/basic_json_view/number_token.md | 74 ++ .../docs/api/basic_json_view/operator[].md | 156 +++ docs/mkdocs/docs/api/basic_json_view/size.md | 6 + .../docs/api/basic_json_view/type_name.md | 63 + docs/mkdocs/docs/api/basic_json_view/value.md | 131 ++ .../docs/examples/basic_json_view__at.cpp | 36 + .../docs/examples/basic_json_view__at.output | 3 + .../basic_json_view__at_json_pointer.cpp | 34 + .../basic_json_view__at_json_pointer.output | 3 + .../docs/examples/basic_json_view__back.cpp | 25 + .../examples/basic_json_view__back.output | 2 + .../docs/examples/basic_json_view__begin.cpp | 18 + .../examples/basic_json_view__begin.output | 3 + .../docs/examples/basic_json_view__cbegin.cpp | 24 + .../examples/basic_json_view__cbegin.output | 1 + .../docs/examples/basic_json_view__cend.cpp | 24 + .../examples/basic_json_view__cend.output | 1 + .../examples/basic_json_view__contains.cpp | 30 + .../examples/basic_json_view__contains.output | 1 + ...basic_json_view__contains_json_pointer.cpp | 39 + ...ic_json_view__contains_json_pointer.output | 4 + .../docs/examples/basic_json_view__count.cpp | 29 + .../examples/basic_json_view__count.output | 2 + .../docs/examples/basic_json_view__end.cpp | 27 + .../docs/examples/basic_json_view__end.output | 1 + .../docs/examples/basic_json_view__find.cpp | 29 + .../examples/basic_json_view__find.output | 2 + .../docs/examples/basic_json_view__front.cpp | 24 + .../examples/basic_json_view__front.output | 2 + .../docs/examples/basic_json_view__get.cpp | 55 + .../docs/examples/basic_json_view__get.output | 3 + .../examples/basic_json_view__get_string.cpp | 31 + .../basic_json_view__get_string.output | 3 + .../docs/examples/basic_json_view__get_to.cpp | 28 + .../examples/basic_json_view__get_to.output | 2 + .../docs/examples/basic_json_view__items.cpp | 22 + .../examples/basic_json_view__items.output | 5 + .../basic_json_view__number_token.cpp | 29 + .../basic_json_view__number_token.output | 5 + .../examples/basic_json_view__operator[].cpp | 42 + .../basic_json_view__operator[].output | 2 + ...sic_json_view__operator[]_json_pointer.cpp | 47 + ..._json_view__operator[]_json_pointer.output | 3 + .../examples/basic_json_view__type_name.cpp | 30 + .../basic_json_view__type_name.output | 3 + .../docs/examples/basic_json_view__value.cpp | 29 + .../examples/basic_json_view__value.output | 3 + .../basic_json_view__value_json_pointer.cpp | 21 + ...basic_json_view__value_json_pointer.output | 3 + docs/mkdocs/docs/features/json_view.md | 60 +- docs/mkdocs/mkdocs.yml | 18 + include/nlohmann/detail/json_pointer.hpp | 21 + include/nlohmann/detail/value_t.hpp | 33 + include/nlohmann/detail/view/errors.hpp | 6 + include/nlohmann/detail/view/iterator.hpp | 263 ++++ include/nlohmann/detail/view/lookup.hpp | 139 ++ include/nlohmann/detail/view/pointer.hpp | 171 +++ include/nlohmann/detail/view/value.hpp | 108 ++ include/nlohmann/json.hpp | 24 +- include/nlohmann/json_view.hpp | 449 ++++++- single_include/nlohmann/json.hpp | 78 +- single_include/nlohmann/json_view.hpp | 1154 ++++++++++++++++- tests/src/unit-json_view.cpp | 674 +++++++++- 98 files changed, 5436 insertions(+), 62 deletions(-) create mode 100644 docs/mkdocs/docs/api/basic_json_view/at.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/back.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/begin.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/cbegin.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/cend.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/contains.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/count.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/end.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/find.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/front.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/get.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/get_string.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/get_to.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/items.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/number_token.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/operator[].md create mode 100644 docs/mkdocs/docs/api/basic_json_view/type_name.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/value.md create mode 100644 docs/mkdocs/docs/examples/basic_json_view__at.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__at.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__back.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__back.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__begin.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__begin.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__cbegin.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__cend.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__cend.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__contains.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__contains.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__count.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__count.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__end.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__end.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__find.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__find.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__front.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__front.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get_string.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get_string.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get_to.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get_to.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__items.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__items.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__number_token.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__number_token.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__operator[].cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__operator[].output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__type_name.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__type_name.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__value.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__value.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.output create mode 100644 include/nlohmann/detail/view/iterator.hpp create mode 100644 include/nlohmann/detail/view/lookup.hpp create mode 100644 include/nlohmann/detail/view/pointer.hpp create mode 100644 include/nlohmann/detail/view/value.hpp diff --git a/BUILD.bazel b/BUILD.bazel index a7aa7228c..055646978 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -71,13 +71,17 @@ cc_library( "include/nlohmann/detail/view/document_data.hpp", "include/nlohmann/detail/view/errors.hpp", "include/nlohmann/detail/view/input.hpp", + "include/nlohmann/detail/view/iterator.hpp", + "include/nlohmann/detail/view/lookup.hpp", "include/nlohmann/detail/view/macro_scope.hpp", "include/nlohmann/detail/view/macro_unscope.hpp", "include/nlohmann/detail/view/materialize.hpp", "include/nlohmann/detail/view/node.hpp", "include/nlohmann/detail/view/number.hpp", + "include/nlohmann/detail/view/pointer.hpp", "include/nlohmann/detail/view/scan.hpp", "include/nlohmann/detail/view/string_ref.hpp", + "include/nlohmann/detail/view/value.hpp", "include/nlohmann/json.hpp", "include/nlohmann/json_fwd.hpp", "include/nlohmann/json_literals.hpp", diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 6f46a2f15..4764185e1 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -147,7 +147,20 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::shrink_t INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::source', 'Method', 'api/basic_json_document/source/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view', 'Class', 'api/basic_json_view/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::basic_json_view', 'Constructor', 'api/basic_json_view/basic_json_view/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::at', 'Method', 'api/basic_json_view/at/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::back', 'Method', 'api/basic_json_view/back/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::begin', 'Method', 'api/basic_json_view/begin/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::cbegin', 'Method', 'api/basic_json_view/cbegin/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::cend', 'Method', 'api/basic_json_view/cend/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::contains', 'Method', 'api/basic_json_view/contains/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::count', 'Method', 'api/basic_json_view/count/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::empty', 'Method', 'api/basic_json_view/empty/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::end', 'Method', 'api/basic_json_view/end/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::find', 'Method', 'api/basic_json_view/find/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::front', 'Method', 'api/basic_json_view/front/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get', 'Method', 'api/basic_json_view/get/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get_string', 'Method', 'api/basic_json_view/get_string/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get_to', 'Method', 'api/basic_json_view/get_to/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_array', 'Method', 'api/basic_json_view/is_array/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_binary', 'Method', 'api/basic_json_view/is_binary/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_boolean', 'Method', 'api/basic_json_view/is_boolean/index.html'); @@ -161,11 +174,16 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_object', INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_primitive', 'Method', 'api/basic_json_view/is_primitive/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_string', 'Method', 'api/basic_json_view/is_string/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_structured', 'Method', 'api/basic_json_view/is_structured/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::items', 'Method', 'api/basic_json_view/items/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::materialize', 'Method', 'api/basic_json_view/materialize/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_token', 'Method', 'api/basic_json_view/number_token/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[]/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'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type_name', 'Method', 'api/basic_json_view/type_name/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::value', 'Method', 'api/basic_json_view/value/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_document', 'Class', 'api/json_document/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_view', 'Class', 'api/json_view/index.html'); diff --git a/docs/mkdocs/docs/api/basic_json/at.md b/docs/mkdocs/docs/api/basic_json/at.md index 18f108964..530a426ae 100644 --- a/docs/mkdocs/docs/api/basic_json/at.md +++ b/docs/mkdocs/docs/api/basic_json/at.md @@ -233,6 +233,7 @@ Strong exception safety: if an exception occurs, the original value stays intact - documentation on [checked access](../../features/element_access/checked_access.md) - [`operator[]`](operator%5B%5D.md) for unchecked access by reference - [`value`](value.md) for access with default value +- [basic_json_view::at](../basic_json_view/at.md) - the same access on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/back.md b/docs/mkdocs/docs/api/basic_json/back.md index b717e16e0..685c1c84b 100644 --- a/docs/mkdocs/docs/api/basic_json/back.md +++ b/docs/mkdocs/docs/api/basic_json/back.md @@ -58,6 +58,7 @@ Constant. ## See also - [front](front.md) to access the first element +- [basic_json_view::back](../basic_json_view/back.md) - the same access on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/begin.md b/docs/mkdocs/docs/api/basic_json/begin.md index 24671cee1..72583ce57 100644 --- a/docs/mkdocs/docs/api/basic_json/begin.md +++ b/docs/mkdocs/docs/api/basic_json/begin.md @@ -44,6 +44,8 @@ Constant. - [rbegin](rbegin.md) returns a reverse iterator to the last element - [items](items.md) returns an iteration proxy to access keys and values during range-based for loops - [Iterators](../../features/iterators.md) - the article on iterators +- [basic_json_view::begin](../basic_json_view/begin.md) - the same iteration on a zero-copy view (in document order, + not sorted by key) ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/cbegin.md b/docs/mkdocs/docs/api/basic_json/cbegin.md index c0ecebcf3..b4c3e588f 100644 --- a/docs/mkdocs/docs/api/basic_json/cbegin.md +++ b/docs/mkdocs/docs/api/basic_json/cbegin.md @@ -42,6 +42,7 @@ Constant. - [cend](cend.md) returns a const iterator to one past the last element - [crbegin](crbegin.md) returns a const reverse iterator to the last element - [Iterators](../../features/iterators.md) - the article on iterators +- [basic_json_view::cbegin](../basic_json_view/cbegin.md) - the same iteration on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/cend.md b/docs/mkdocs/docs/api/basic_json/cend.md index 0f944b48c..fb568762a 100644 --- a/docs/mkdocs/docs/api/basic_json/cend.md +++ b/docs/mkdocs/docs/api/basic_json/cend.md @@ -42,6 +42,7 @@ Constant. - [cbegin](cbegin.md) returns a const iterator to the first element - [crend](crend.md) returns a const reverse iterator to one before the first element - [Iterators](../../features/iterators.md) - the article on iterators +- [basic_json_view::cend](../basic_json_view/cend.md) - the same iteration on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/contains.md b/docs/mkdocs/docs/api/basic_json/contains.md index 63ec87a1d..f23339e61 100644 --- a/docs/mkdocs/docs/api/basic_json/contains.md +++ b/docs/mkdocs/docs/api/basic_json/contains.md @@ -127,6 +127,7 @@ Logarithmic in the size of the JSON object. - [find](find.md) find a value in an object - [count](count.md) returns the number of occurrences of a key +- [basic_json_view::contains](../basic_json_view/contains.md) - the same check on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/count.md b/docs/mkdocs/docs/api/basic_json/count.md index 14b707525..67a8f4e12 100644 --- a/docs/mkdocs/docs/api/basic_json/count.md +++ b/docs/mkdocs/docs/api/basic_json/count.md @@ -80,6 +80,7 @@ Logarithmic in the size of the JSON object. - [find](find.md) find a value in an object - [contains](contains.md) checks whether a key exists +- [basic_json_view::count](../basic_json_view/count.md) - the same check on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/end.md b/docs/mkdocs/docs/api/basic_json/end.md index bb735c6df..e94ec78a2 100644 --- a/docs/mkdocs/docs/api/basic_json/end.md +++ b/docs/mkdocs/docs/api/basic_json/end.md @@ -43,6 +43,7 @@ Constant. - [cend](cend.md) returns a const iterator to one past the last element - [rend](rend.md) returns a reverse iterator to one before the first element - [Iterators](../../features/iterators.md) - the article on iterators +- [basic_json_view::end](../basic_json_view/end.md) - the same iteration on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/find.md b/docs/mkdocs/docs/api/basic_json/find.md index 59c2eab68..839d73675 100644 --- a/docs/mkdocs/docs/api/basic_json/find.md +++ b/docs/mkdocs/docs/api/basic_json/find.md @@ -84,6 +84,7 @@ Logarithmic in the size of the JSON object. - [count](count.md) returns the number of occurrences of a key - [contains](contains.md) checks whether a key exists +- [basic_json_view::find](../basic_json_view/find.md) - the same lookup on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/front.md b/docs/mkdocs/docs/api/basic_json/front.md index b5fea1135..ef4f0d262 100644 --- a/docs/mkdocs/docs/api/basic_json/front.md +++ b/docs/mkdocs/docs/api/basic_json/front.md @@ -51,6 +51,7 @@ Constant. ## See also - [back](back.md) to access the last element +- [basic_json_view::front](../basic_json_view/front.md) - the same access on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get.md b/docs/mkdocs/docs/api/basic_json/get.md index 0dcd3189f..fef1be766 100644 --- a/docs/mkdocs/docs/api/basic_json/get.md +++ b/docs/mkdocs/docs/api/basic_json/get.md @@ -183,6 +183,8 @@ overload (3). - [get_ref](get_ref.md) get a reference to the stored value - [operator ValueType](operator_ValueType.md) get a value via implicit conversion - [Converting values](../../features/conversions.md) - the type conversions article +- [basic_json_view::get](../basic_json_view/get.md) - the same conversion on a zero-copy view (many types are + converted without ever building a `basic_json` value) ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get_ref.md b/docs/mkdocs/docs/api/basic_json/get_ref.md index 73b20b0e0..4177e7483 100644 --- a/docs/mkdocs/docs/api/basic_json/get_ref.md +++ b/docs/mkdocs/docs/api/basic_json/get_ref.md @@ -61,6 +61,8 @@ Constant. ## See also - [get_ptr()](get_ptr.md) get a pointer value +- [basic_json_view::get_string](../basic_json_view/get_string.md) - the closest counterpart on a zero-copy view: a + string without a copy, but as a view rather than a reference to a value that must already exist ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get_to.md b/docs/mkdocs/docs/api/basic_json/get_to.md index 1839e959a..29a78f064 100644 --- a/docs/mkdocs/docs/api/basic_json/get_to.md +++ b/docs/mkdocs/docs/api/basic_json/get_to.md @@ -72,6 +72,7 @@ Depends on the `json_serializer::from_json()` implementation. - [get_ref](get_ref.md) get a reference to the stored value - [get_ptr](get_ptr.md) get a pointer to the stored value - [Converting values](../../features/conversions.md) - the type conversions article +- [basic_json_view::get_to](../basic_json_view/get_to.md) - the same conversion on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/items.md b/docs/mkdocs/docs/api/basic_json/items.md index 65637001b..5fb8a7bf1 100644 --- a/docs/mkdocs/docs/api/basic_json/items.md +++ b/docs/mkdocs/docs/api/basic_json/items.md @@ -99,6 +99,8 @@ When iterating over an array, `key()` will return the index of the element as st - [begin](begin.md) returns an iterator to the first element - [end](end.md) returns an iterator to one past the last element +- [basic_json_view::items](../basic_json_view/items.md) - the same range on a zero-copy view (`#!cpp const auto`, + not `#!cpp const auto&`: items are produced on the fly) ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index 74996d629..248dfea70 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -275,6 +275,8 @@ Strong exception safety: if an exception occurs, the original value stays intact - documentation on [runtime assertions](../../features/assertions.md) - see [`at`](at.md) for access by reference with range checking - see [`value`](value.md) for access with default value +- [basic_json_view::operator[]](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (always + returns a discarded view instead of assuming undefined behavior) ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/type_name.md b/docs/mkdocs/docs/api/basic_json/type_name.md index 0df2de752..d43ed4d71 100644 --- a/docs/mkdocs/docs/api/basic_json/type_name.md +++ b/docs/mkdocs/docs/api/basic_json/type_name.md @@ -56,6 +56,7 @@ Constant. - [type](type.md) returns the type of the JSON value - [value_t](value_t.md) the enumeration of JSON types +- [basic_json_view::type_name](../basic_json_view/type_name.md) - the same function on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/value.md b/docs/mkdocs/docs/api/basic_json/value.md index 80f5691dc..d8e6fa53b 100644 --- a/docs/mkdocs/docs/api/basic_json/value.md +++ b/docs/mkdocs/docs/api/basic_json/value.md @@ -216,6 +216,7 @@ changes to any JSON value. - see [`at`](at.md) for access by reference with range checking - see [`operator[]`](operator%5B%5D.md) for unchecked access by reference +- [basic_json_view::value](../basic_json_view/value.md) - the same access on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/at.md b/docs/mkdocs/docs/api/basic_json_view/at.md new file mode 100644 index 000000000..d1b1732bb --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/at.md @@ -0,0 +1,132 @@ +# nlohmann::basic_json_view::at + +```cpp +// (1) +basic_json_view at(string_view_t key) const; +basic_json_view at(const char* key) const; +basic_json_view at(const string_t& key) const; + +// (2) +basic_json_view at(size_type idx) const; +basic_json_view at(int idx) const; + +// (3) +basic_json_view at(const json_pointer& ptr) const; +``` + +1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see + [Notes on duplicate keys](operator[].md#notes)). +2. Returns the array element at index `idx`. +3. Returns the value a JSON pointer `ptr` refers to, starting at this value. + +## Parameters + +`key` (in) +: object key of the element to access + +`idx` (in) +: index of the element to access + +`ptr` (in) +: JSON pointer to the element to access + +## Return value + +1. the value of the first member with key `key` +2. the element at index `idx` +3. the value `ptr` resolves to, starting at this value + +## Exception safety + +Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. + +## Exceptions + +1. The function can throw the following exceptions, both with the same message as the corresponding call to + [`BasicJsonType::at`](../basic_json/at.md): + - Throws [`type_error.304`](../../home/exceptions.md#jsonexceptiontype_error304) if the value is not an object. + - Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if no member has key `key`. +2. The function can throw the following exceptions, both with the same message as the corresponding call to + [`BasicJsonType::at`](../basic_json/at.md): + - Throws [`type_error.304`](../../home/exceptions.md#jsonexceptiontype_error304) if the value is not an array. + - Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `#!cpp idx >= size()`. +3. The function can throw the following exceptions, all with the same message as the corresponding call to + [`BasicJsonType::at`](../basic_json/at.md): + - Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in `ptr` + begins with `#!cpp '0'`. + - Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is + not a number. + - Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if an array index in `ptr` + is out of range. + - Throws [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if a reference token is + `#!cpp "-"` at an array -- `at` never inserts an element, so `#!cpp "-"` is always invalid. + - Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a reference token names + an object member that does not exist. + - Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved + because a reference token is used on a primitive value. + +None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no +`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with +`JSON_DIAGNOSTICS` enabled. + +## Complexity + +1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after + another, in document order, stopping at the first match. Each comparison first checks the key's length -- + already known from the index, without reading the key bytes -- before comparing its content. +2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the + index (unlike `BasicJsonType`'s array, which is random-access). +3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at + that level (as 1.) or the index into the array (as 2.). + +## Notes + +Unlike [`operator[]`](operator[].md), which returns a [discarded](is_discarded.md) view for a missing key or an +out-of-range index, `at` always throws -- exactly as `BasicJsonType::at` does, and with the same messages, so +existing error handling written against `BasicJsonType::at` keeps working unchanged when switched to a view. This +also holds for overload 3: unlike [`operator[]`](operator[].md) with a JSON pointer, which returns a discarded view +for a missing key or an out-of-range index, `at` throws for those too (`out_of_range.403`/`out_of_range.401`). + +## Examples + +??? example "Example: (1)/(2) access specified element with bounds checking" + + The example below reads required fields out of a service configuration with `at`, and shows that the exceptions + it throws -- for a wrong type and for a missing key -- carry the same messages + [`BasicJsonType::at`](../basic_json/at.md) would produce for the same JSON text. + + ```cpp + --8<-- "examples/basic_json_view__at.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__at.output" + ``` + +??? example "Example: (3) access specified element via JSON pointer with bounds checking" + + The example below shows that `at` with a JSON pointer throws exactly the exceptions, with exactly the messages, + that [`BasicJsonType::at`](../basic_json/at.md) throws for the same pointer and the same document. + + ```cpp + --8<-- "examples/basic_json_view__at_json_pointer.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__at_json_pointer.output" + ``` + +## See also + +- [operator[]](operator[].md) - access specified element (returns a discarded view instead of throwing) +- [front](front.md), [back](back.md) - access the first or last element +- [`BasicJsonType::at`](../basic_json/at.md) - the corresponding function of `basic_json` +- [`json_pointer`](../json_pointer/index.md) - JSON pointer type used by overload 3 + +## Version history + +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json_view/back.md b/docs/mkdocs/docs/api/basic_json_view/back.md new file mode 100644 index 000000000..5ddb1fb35 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/back.md @@ -0,0 +1,64 @@ +# nlohmann::basic_json_view::back + +```cpp +basic_json_view back() const; +``` + +Returns the last element of an array, the last member value of an object, or the value itself if it is primitive +(as for [`BasicJsonType::back()`](../basic_json/back.md), a primitive value is a range of one element). + +## Return value + +The last element or member value. For a primitive value (number, string, boolean), the value itself. + +## Exception safety + +Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. + +## Exceptions + +Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is +[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object. + +This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no +`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with +`JSON_DIAGNOSTICS` enabled. + +## Complexity + +Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first +element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the +index. + +## Notes + +Unlike [`BasicJsonType::back()`](../basic_json/back.md), which has undefined behavior for an empty array or object, +`back()` throws `invalid_iterator.214` in that case -- the same way it already does for `#!json null` and for a +discarded view, where `BasicJsonType::back()` also throws. + +## Examples + +??? example + + The example below reads only the final status of a build log with `back()`. Even though `back()` is linear in + the number of events (unlike [`front()`](front.md), which is constant), it still avoids building a + `BasicJsonType` value for the events that are not needed. + + ```cpp + --8<-- "examples/basic_json_view__back.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__back.output" + ``` + +## See also + +- [front](front.md) - access the first element +- [`BasicJsonType::back`](../basic_json/back.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/basic_json_view.md b/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md index 143ea2bea..031c3b29d 100644 --- a/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md +++ b/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md @@ -8,8 +8,9 @@ Creates an invalid (discarded) view: [`type()`](type.md) is `#!cpp value_t::disc [`is_discarded()`](is_discarded.md) is `#!cpp true`, and `#!cpp explicit operator bool()` is `#!cpp false`. This is the only constructor a caller can use directly. Every other view is obtained from a -[`basic_json_document`](../basic_json_document/index.md), via [`root()`](../basic_json_document/root.md) or (once -element access is added) from navigating into a container. +[`basic_json_document`](../basic_json_document/index.md), via [`root()`](../basic_json_document/root.md) or by +navigating into a container with [`operator[]`](operator[].md), [`at`](at.md), [`front`](front.md), [`back`](back.md), +[`find`](find.md), or iteration. ## Exception safety diff --git a/docs/mkdocs/docs/api/basic_json_view/begin.md b/docs/mkdocs/docs/api/basic_json_view/begin.md new file mode 100644 index 000000000..ae2665ee9 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/begin.md @@ -0,0 +1,61 @@ +# nlohmann::basic_json_view::begin + +```cpp +iterator begin() const noexcept; +``` + +Returns an iterator to the first element of an array, or the first member value of an object, in **document order** +-- the order the values appear in the source text, not sorted by key. A primitive value iterates as a range of one +element (itself); `#!json null` and a [discarded](is_discarded.md) view iterate as an empty range. + +## Return value + +Iterator to the first element. + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Constant. + +## Notes + +For an object, iteration visits **every** member, including all occurrences of a duplicate key -- unlike +[`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md), and [`count`](count.md), +which all resolve to the *first* member with a given key. See the +[Notes on duplicate keys](operator[].md#notes) of `operator[]`. + +Because objects are iterated in document order rather than sorted by key, the order seen here can differ from what +iterating the [`materialize()`](materialize.md)d `BasicJsonType` value would produce: a `#!cpp basic_json` object +(`std::map`-backed by default) sorts its keys, while a view does not. + +## Examples + +??? example + + The example below iterates a log record's members with `begin()`/[`end()`](end.md) and prints them in the order + they were written. Materializing the record into a `BasicJsonType` object and iterating that would instead print + the members sorted by key. + + ```cpp + --8<-- "examples/basic_json_view__begin.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__begin.output" + ``` + +## See also + +- [end](end.md) - returns an iterator to one past the last element +- [cbegin](cbegin.md) - returns a const iterator to the first element +- [items](items.md) - access iterator member functions in range-based for +- [`BasicJsonType::begin`](../basic_json/begin.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/cbegin.md b/docs/mkdocs/docs/api/basic_json_view/cbegin.md new file mode 100644 index 000000000..25d17bdad --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/cbegin.md @@ -0,0 +1,49 @@ +# nlohmann::basic_json_view::cbegin + +```cpp +iterator cbegin() const noexcept; +``` + +Returns an iterator to the first element, in [document order](begin.md). Equivalent to [`begin()`](begin.md): a view +is always read-only, so `#!cpp const_iterator` and `#!cpp iterator` are the same type, and `cbegin()` exists only so +that generic code that expects a `cbegin()`/`cend()` pair works with a view too. + +## Return value + +Iterator to the first element; identical to what [`begin()`](begin.md) returns. + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Constant. + +## Examples + +??? example + + The example below sums many measurements with `std::accumulate`, using `cbegin()`/[`cend()`](cend.md) as the + range -- the same way it would for any standard container -- without ever materializing the whole array into a + `BasicJsonType` value. + + ```cpp + --8<-- "examples/basic_json_view__cbegin.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__cbegin.output" + ``` + +## See also + +- [begin](begin.md) - returns an iterator to the first element +- [cend](cend.md) - returns a const iterator to one past the last element +- [`BasicJsonType::cbegin`](../basic_json/cbegin.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/cend.md b/docs/mkdocs/docs/api/basic_json_view/cend.md new file mode 100644 index 000000000..6d19530ce --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/cend.md @@ -0,0 +1,49 @@ +# nlohmann::basic_json_view::cend + +```cpp +iterator cend() const noexcept; +``` + +Returns an iterator to one past the last element, in [document order](begin.md). Equivalent to [`end()`](end.md): a +view is always read-only, so `#!cpp const_iterator` and `#!cpp iterator` are the same type, and `cend()` exists only +so that generic code that expects a `cbegin()`/`cend()` pair works with a view too. + +## Return value + +Iterator one past the last element; identical to what [`end()`](end.md) returns. + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Constant. + +## Examples + +??? example + + The example below checks that every record of a batch is an object with `std::all_of`, using + [`cbegin()`](cbegin.md)/`cend()` as the range -- the same way it would for any standard container -- before + materializing any record of the batch. + + ```cpp + --8<-- "examples/basic_json_view__cend.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__cend.output" + ``` + +## See also + +- [end](end.md) - returns an iterator to one past the last element +- [cbegin](cbegin.md) - returns a const iterator to the first element +- [`BasicJsonType::cend`](../basic_json/cend.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/contains.md b/docs/mkdocs/docs/api/basic_json_view/contains.md new file mode 100644 index 000000000..bbd791887 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/contains.md @@ -0,0 +1,101 @@ +# nlohmann::basic_json_view::contains + +```cpp +// (1) +bool contains(string_view_t key) const; +bool contains(const char* key) const; +bool contains(const string_t& key) const; + +// (2) +bool contains(const json_pointer& ptr) const; +``` + +1. Checks whether the value is an object with a member with key `key`. +2. Checks whether a JSON pointer `ptr` can be resolved, starting at this value. + +## Parameters + +`key` (in) +: key value to check its existence + +`ptr` (in) +: JSON pointer to check its existence + +## Return value + +1. `#!cpp true` if the value is an object and has a member with key `key`, `#!cpp false` otherwise +2. `#!cpp true` if `ptr` can be resolved to a value starting at this view, `#!cpp false` otherwise + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after + another, in document order, stopping at the first match. Each comparison first checks the key's length -- already + known from the index, without reading the key bytes -- before comparing its content. +2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at + that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and + [`at`](at.md#complexity) with a JSON pointer. + +## Notes + +Overload 1 always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md) +view. + +!!! info "Postconditions" + + If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md). If + `#!cpp v.contains(ptr)` returns `#!cpp true`, then `#!cpp v[ptr]` is not discarded and `#!cpp v.at(ptr)` does not + throw. + +!!! info "Overload 2 never throws" + + Unlike [`BasicJsonType::contains(const json_pointer&)`](../basic_json/contains.md), which can throw for certain + malformed pointers (for instance an empty array-index reference token), overload 2 never throws: a missing key, + an out-of-range or malformed array index, a `#!cpp "-"` index, or a reference token used on a primitive all + simply make it return `#!cpp false`. + +## Examples + +??? example "Example: (1) check with key" + + The example below counts how many of a batch of records carry an optional `retry_of` field, using `contains()` + to check without ever materializing a single record of the batch. + + ```cpp + --8<-- "examples/basic_json_view__contains.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__contains.output" + ``` + +??? example "Example: (2) check with JSON pointer" + + The example below checks an optional, nested field with a JSON pointer, and shows two pointers that + `#!cpp contains()` resolves to `#!cpp false` without throwing. + + ```cpp + --8<-- "examples/basic_json_view__contains_json_pointer.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__contains_json_pointer.output" + ``` + +## See also + +- [find](find.md) - find a value in an object +- [count](count.md) - returns the number of occurrences of a key +- [at](at.md), [operator[]](operator[].md) - resolve a JSON pointer and throw, or return a discarded view +- [`BasicJsonType::contains`](../basic_json/contains.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/count.md b/docs/mkdocs/docs/api/basic_json_view/count.md new file mode 100644 index 000000000..6a599477e --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/count.md @@ -0,0 +1,66 @@ +# nlohmann::basic_json_view::count + +```cpp +size_type count(string_view_t key) const; +size_type count(const char* key) const; +size_type count(const string_t& key) const; +``` + +Returns `#!cpp 1` if the value is an object with a member with key `key`, `#!cpp 0` otherwise. + +## Parameters + +`key` (in) +: key value of the element to count + +## Return value + +`#!cpp 1` if the value is an object and has a member with key `key`, `#!cpp 0` otherwise. + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after +another, in document order, stopping at the first match. Each comparison first checks the key's length -- already +known from the index, without reading the key bytes -- before comparing its content. + +## Notes + +This method always returns `#!cpp 0` when the value is not an object -- including a [discarded](is_discarded.md) +view. + +Unlike [`BasicJsonType::count()`](../basic_json/count.md), whose return value can in principle exceed `#!cpp 1` for +an `ObjectType` that allows multiple entries per key, `count()` here never does: it is exactly +[`contains()`](contains.md) as `#!cpp 0`/`#!cpp 1`. This holds even if the source text has a duplicate key -- see the +[Notes on duplicate keys](operator[].md#notes) of `operator[]` -- because a `#!cpp count() > 1` result would require +counting every member with a matching key, not just finding the first one. + +## Examples + +??? example + + The example below validates that every transaction of a batch carries a mandatory `amount` field, using + `count()` before deciding whether to materialize a transaction at all. + + ```cpp + --8<-- "examples/basic_json_view__count.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__count.output" + ``` + +## See also + +- [find](find.md) - find a value in an object +- [contains](contains.md) - checks whether a key exists +- [`BasicJsonType::count`](../basic_json/count.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/end.md b/docs/mkdocs/docs/api/basic_json_view/end.md new file mode 100644 index 000000000..d0346c049 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/end.md @@ -0,0 +1,54 @@ +# nlohmann::basic_json_view::end + +```cpp +iterator end() const noexcept; +``` + +Returns an iterator to one past the last element of an array, one past the last member value of an object, in +**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself); +`#!json null` and a [discarded](is_discarded.md) view iterate as an empty range, so `#!cpp begin() == end()` for +them. + +## Return value + +Iterator one past the last element. + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Constant. + +## Notes + +`#!cpp iterator` is a forward iterator: unlike `BasicJsonType::iterator`, it cannot be decremented, so there is no +way to reach the last element by stepping back from `end()`. Use [`back()`](back.md) instead. + +## Examples + +??? example + + The example below scans a (possibly large) array of readings for the first one over a threshold, stopping the + loop at `end()` as soon as one is found. Only the matching reading, if any, is ever materialized. + + ```cpp + --8<-- "examples/basic_json_view__end.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__end.output" + ``` + +## See also + +- [begin](begin.md) - returns an iterator to the first element +- [cend](cend.md) - returns a const iterator to one past the last element +- [`BasicJsonType::end`](../basic_json/end.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/find.md b/docs/mkdocs/docs/api/basic_json_view/find.md new file mode 100644 index 000000000..f7ad92e9f --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/find.md @@ -0,0 +1,63 @@ +# nlohmann::basic_json_view::find + +```cpp +iterator find(string_view_t key) const; +iterator find(const char* key) const; +iterator find(const string_t& key) const; +``` + +Finds a member with key `key` -- the first one, should the key occur more than once (see +[Notes on duplicate keys](operator[].md#notes)). If the value is not an object, or no member has this key, +[`end()`](end.md) is returned. + +## Parameters + +`key` (in) +: key value of the element to search for + +## Return value + +An iterator to the member with key `key`, or [`end()`](end.md) if there is none or the value is not an object. + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after +another, in document order, stopping at the first match. Each comparison first checks the key's length -- already +known from the index, without reading the key bytes -- before comparing its content. + +## Notes + +Unlike [`BasicJsonType::find`](../basic_json/find.md), which always returns `#!cpp end()` for a non-object type, this +also does so for a [discarded](is_discarded.md) view -- there is no separate "invalid" iterator to return. + +## Examples + +??? example + + The example below scans a batch of events for those that carry an optional `user_id` field, using `find()` + instead of [`operator[]`](operator[].md) (which would throw for the events that are not objects at all) or + [`contains()`](contains.md) followed by a second lookup. Events without a match are never materialized. + + ```cpp + --8<-- "examples/basic_json_view__find.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__find.output" + ``` + +## See also + +- [count](count.md) - returns the number of occurrences of a key +- [contains](contains.md) - checks whether a key exists +- [`BasicJsonType::find`](../basic_json/find.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/front.md b/docs/mkdocs/docs/api/basic_json_view/front.md new file mode 100644 index 000000000..c96a35eaf --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/front.md @@ -0,0 +1,61 @@ +# nlohmann::basic_json_view::front + +```cpp +basic_json_view front() const; +``` + +Returns the first element of an array, the first member value of an object, or the value itself if it is primitive +(as for [`BasicJsonType::front()`](../basic_json/front.md), a primitive value is a range of one element). + +## Return value + +The first element or member value. For a primitive value (number, string, boolean), the value itself. + +## Exception safety + +Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. + +## Exceptions + +Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is +[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object. + +This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no +`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with +`JSON_DIAGNOSTICS` enabled. + +## Complexity + +Constant. + +## Notes + +Unlike [`BasicJsonType::front()`](../basic_json/front.md), which has undefined behavior for an empty array or +object, `front()` throws `invalid_iterator.214` in that case -- the same way it already does for `#!json null` and +for a discarded view, where `BasicJsonType::front()` also throws. + +## Examples + +??? example + + The example below reads only the earliest entry of a build log with `front()`, without materializing the rest + of the (possibly long) log. + + ```cpp + --8<-- "examples/basic_json_view__front.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__front.output" + ``` + +## See also + +- [back](back.md) - access the last element +- [`BasicJsonType::front`](../basic_json/front.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/get.md b/docs/mkdocs/docs/api/basic_json_view/get.md new file mode 100644 index 000000000..d8ec333c9 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -0,0 +1,130 @@ +# nlohmann::basic_json_view::get + +```cpp +template +T get() const; +``` + +Converts the value to `T`. + +For the types below, the conversion works directly on the flat index -- no `BasicJsonType` value is built for it: + +- `#!cpp bool` +- arithmetic types other than `#!cpp bool` (from a number; from a boolean, as `#!cpp 0`/`#!cpp 1`, exactly as + [`BasicJsonType::get()`](../basic_json/get.md) converts a boolean) +- `#!cpp std::nullptr_t` +- `#!cpp std::basic_string` (including `string_t`) -- a copy of the string +- [`string_view_t`](index.md#member-types) -- **no copy**: the returned view points into the document's + [`source()`](../basic_json_document/source.md) text, or, for a string that contains escape sequences, into the + document's own buffer of decoded strings (see [`get_string()`](get_string.md)) +- `BasicJsonType` -- equivalent to [`materialize()`](materialize.md) +- `basic_json_view` -- returns `#!cpp *this` +- `#!cpp std::vector` -- element by element, each converted with `#!cpp get()`; `#!cpp + std::vector` keeps a view of every element instead of a value +- `#!cpp std::map` and `#!cpp std::unordered_map`, if `K` is constructible from a `#!cpp + (const char*, std::size_t)` pair -- member by member, each value converted with `#!cpp get()`; with a repeated + key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and + [`materialize()`](materialize.md)) does; `#!cpp std::map` keeps views of the members + instead of values + +Every other `T` -- `#!cpp std::list`, `#!cpp std::pair`, `#!cpp std::array`, enumerations, user types with a +`from_json()`, ... -- is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` +value first (as [`BasicJsonType::parse()`](../basic_json/parse.md) would), and converted from there exactly as +[`BasicJsonType::get()`](../basic_json/get.md) would convert it. + +## Template parameters + +`T` +: the type to convert the value to + +## Return value + +the value, converted to `T` + +## Exception safety + +Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. + +## Exceptions + +- For the directly-converted types listed above (other than `BasicJsonType` and `basic_json_view`, which never + throw): throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value's type does not + match `T` -- the same exception, with the same message, that [`BasicJsonType::get()`](../basic_json/get.md) + throws for the same JSON type and `T`. +- For `#!cpp std::vector`: throws `type_error.302` if the value is not an array; otherwise, whatever converting + an element to `U` throws. +- For `#!cpp std::map`/`#!cpp std::unordered_map`: throws `type_error.302` if the value is not an object; otherwise, + whatever converting a member to the mapped type throws. +- For every other `T`: whatever [`materialize().get()`](../basic_json/get.md) throws -- typically `type_error.302`, + or whatever a user-provided `from_json()` throws. + +None of the exceptions thrown directly by this function (the first three bullets above) carry a +[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An +exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real +`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled. + +## Complexity + +- `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `basic_json_view`: + constant. +- `#!cpp std::basic_string`: constant, plus one allocation and a copy of the string's bytes. +- `BasicJsonType`: linear in the size of the subtree, see [`materialize()`](materialize.md). +- `#!cpp std::vector`: linear in the number of elements, times the complexity of converting one element to `U`. +- `#!cpp std::map`/`#!cpp std::unordered_map`: linear in the number of members for walking them, plus the container's + own insertion cost per member (logarithmic for `#!cpp std::map`, amortized constant for `#!cpp + std::unordered_map`), times the complexity of converting one member to the mapped type. +- every other `T`: linear in the size of the subtree (building the `BasicJsonType` value), plus the complexity of + [`BasicJsonType::get()`](../basic_json/get.md) on it. + +## Notes + +!!! info "Floating-point values" + + A floating-point `T` is converted from the same digits the lexer would see during `#!cpp BasicJsonType::parse()`, + using the same conversion, so the result is bit-for-bit identical to `#!cpp BasicJsonType::parse(text).get()` + for the same source text. + +!!! info "Duplicate keys" + + `#!cpp std::map`/`#!cpp std::unordered_map` conversions keep the *last* value of a repeated key, like + [`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the opposite + of [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md), which resolve to the + *first* occurrence (see the [Notes on duplicate keys](operator[].md#notes)). + +!!! info "No pointers, references, or implicit conversion" + + Unlike `BasicJsonType`, `basic_json_view` has no stored value anywhere to hand out a pointer or a reference to, so + it provides neither `#!cpp get_ptr()`, `#!cpp get_ref()`, nor `#!cpp operator ValueType()`. + [`get_string()`](get_string.md) (equivalently, `#!cpp get()`) is the zero-copy alternative for + strings. + +## Examples + +??? example + + The example below reads typed fields straight into C++ variables, collects a view of every array element with + `#!cpp get>()` instead of a value, and converts a nested object into a user type + through its `from_json()` -- which runs on a `BasicJsonType` value `materialize()` builds for just that one + member. + + ```cpp + --8<-- "examples/basic_json_view__get.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__get.output" + ``` + +## See also + +- [get_to](get_to.md) - convert and write into a passed value +- [get_string](get_string.md) - the string, without a copy +- [number_token](number_token.md) - a number's token text, without a copy +- [materialize](materialize.md) - build the `BasicJsonType` value of this subtree +- [`BasicJsonType::get`](../basic_json/get.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/get_string.md b/docs/mkdocs/docs/api/basic_json_view/get_string.md new file mode 100644 index 000000000..1222e3bd6 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/get_string.md @@ -0,0 +1,71 @@ +# nlohmann::basic_json_view::get_string + +```cpp +string_view_t get_string() const; +``` + +Returns the string value as a [`string_view_t`](index.md#member-types), without copying it. + +## Return value + +The string, as a [`string_view_t`](index.md#member-types) that points either into the document's +[`source()`](../basic_json_document/source.md) text (a string with no escape sequences), or into the document's own +buffer of decoded strings (a string that contains escape sequences, such as `#!json "\n"` or `#!json "\u00e9"`, which +had to be decoded once when the document was parsed). + +## Exception safety + +Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. + +## Exceptions + +Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example: +`"type must be string, but is array"`. + +This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no +`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with +`JSON_DIAGNOSTICS` enabled. + +## Complexity + +Constant. + +## Notes + +`basic_json_view` has no `BasicJsonType` value stored anywhere, so unlike `BasicJsonType`, it has no `get_ref()` to +hand out a reference to a stored `string_t`. `get_string()` (equivalently, [`get()`](get.md)) is the +zero-copy alternative: [`BasicJsonType::get_ref()`](../basic_json/get_ref.md) is its closest +counterpart, except that it returns a view instead of a reference to a value that must already exist. + +The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it -- see the +[validity rules](index.md) of `basic_json_view` -- and, for a string with no escapes, for as long as the document's +source text. + +## Examples + +??? example + + The example below pulls one field out of a JSON text that stands in for a large API response, and shows that no + `#!cpp std::string` was allocated for it: the returned view still points inside the original buffer. A field that + contains an escape sequence cannot point into the original text -- it was decoded once into the document's own + buffer instead -- but still avoids a per-field allocation. + + ```cpp + --8<-- "examples/basic_json_view__get_string.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__get_string.output" + ``` + +## See also + +- [get](get.md) - convert the value to a given type (`#!cpp get()` is equivalent to this function) +- [number_token](number_token.md) - a number's token text, without a copy +- [`BasicJsonType::get_ref`](../basic_json/get_ref.md) - the closest counterpart of `basic_json` + +## Version history + +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json_view/get_to.md b/docs/mkdocs/docs/api/basic_json_view/get_to.md new file mode 100644 index 000000000..6eabe7899 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/get_to.md @@ -0,0 +1,65 @@ +# nlohmann::basic_json_view::get_to + +```cpp +template +T& get_to(T& v) const; +``` + +Converts the value to `T` and assigns it to `v`. Equivalent to + +```cpp +v = get(); +return v; +``` + +## Template parameters + +`T` +: the type to convert the value to + +## Parameters + +`v` (out) +: the variable to store the converted value in + +## Return value + +`v`, allowing calls to chain + +## Exception safety + +Strong exception safety: if an exception is thrown, `v` is not modified. + +## Exceptions + +Whatever [`get()`](get.md) throws for the same value and `T`. + +## Complexity + +Whatever [`get()`](get.md) has for the same `T`. + +## Examples + +??? example + + The example below reads several fields of a service configuration directly into existing variables, then uses + the returned reference to fold the `#!cpp host`/`#!cpp port` pair into a single string in the same expression. + + ```cpp + --8<-- "examples/basic_json_view__get_to.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__get_to.output" + ``` + +## See also + +- [get](get.md) - convert the value to a given type +- [`BasicJsonType::get_to`](../basic_json/get_to.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/index.md b/docs/mkdocs/docs/api/basic_json_view/index.md index 564406667..50bcaa24b 100644 --- a/docs/mkdocs/docs/api/basic_json_view/index.md +++ b/docs/mkdocs/docs/api/basic_json_view/index.md @@ -19,9 +19,12 @@ to the document and a pointer into its index), trivially copyable. A view is val Moving the document itself does not invalidate its views: the index is heap-allocated independently of the `basic_json_document` object. -`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, and -[`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on demand. It does not (yet) provide -element access, iteration, `get()`, JSON Pointer support, `dump()`, or comparison. +`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), +[`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 `dump()` or +comparison. ## Template parameters @@ -41,6 +44,9 @@ element access, iteration, `get()`, JSON Pointer support, `dump()`, or compar member types of `BasicJsonType` - **size_type** - `#!cpp std::size_t` - **string_view_t** - `#!cpp std::string_view` on C++17 and newer, a minimal internal substitute otherwise +- **iterator**, **const_iterator** - a forward iterator over the elements of an array or the member values of an + object, in document order; both names refer to the same type, since a view is always read-only +- **item** - a (key, value) pair produced by [`items()`](items.md) ## Member functions @@ -49,6 +55,7 @@ element access, iteration, `get()`, JSON Pointer support, `dump()`, or compar ### Object inspection - [**type**](type.md) - return the type of the value +- [**type_name**](type_name.md) - return the type as string - [**is_null**](is_null.md) - return whether the value is null - [**is_boolean**](is_boolean.md) - return whether the value is a boolean - [**is_number**](is_number.md) - return whether the value is a number @@ -64,6 +71,28 @@ element access, iteration, `get()`, JSON Pointer support, `dump()`, or compar - [**is_discarded**](is_discarded.md) - return whether the view is invalid - [**operator bool**](operator_bool.md) - return whether the view refers to a value +### Element access + +- [**at**](at.md) - access specified element with bounds checking +- [**operator[]**](operator[].md) - access specified element +- [**value**](value.md) - access specified element with default value +- [**front**](front.md) - access the first element +- [**back**](back.md) - access the last element + +### Lookup + +- [**find**](find.md) - find an element in an object +- [**count**](count.md) - returns the number of occurrences of a key in an object +- [**contains**](contains.md) - check the existence of an element in an object + +### Iterators + +- [**begin**](begin.md) - returns an iterator to the first element +- [**cbegin**](cbegin.md) - returns a const iterator to the first element +- [**end**](end.md) - returns an iterator to one past the last element +- [**cend**](cend.md) - returns a const iterator to one past the last element +- [**items**](items.md) - wrapper to access iterator member functions in range-based for + ### Capacity - [**size**](size.md) - return the number of elements @@ -71,6 +100,10 @@ element access, iteration, `get()`, JSON Pointer support, `dump()`, or compar ### Conversion +- [**get**](get.md) - get a value +- [**get_to**](get_to.md) - get a value and write it to a destination +- [**get_string**](get_string.md) - get a string value 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 ### Source access diff --git a/docs/mkdocs/docs/api/basic_json_view/items.md b/docs/mkdocs/docs/api/basic_json_view/items.md new file mode 100644 index 000000000..d54f8d723 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/items.md @@ -0,0 +1,86 @@ +# nlohmann::basic_json_view::items + +```cpp +/* unspecified */ items() const noexcept; +``` + +Returns a range of [`item`](index.md#member-types) values -- (key, value) pairs -- for use in range-based for loops. +The key of an array element is its index, converted to a string, as for +[`BasicJsonType::items()`](../basic_json/items.md). + +The returned type is not part of the public API and may change between versions; use a range-based for loop (see the +example), or `#!cpp decltype(v.items())` if you need to name it. + +```cpp +for (const auto& item : v.items()) +{ + std::cout << "key: " << item.key() << ", value: " << item.value() << '\n'; +} +``` + +On C++17, `item` also supports [structured bindings](https://en.cppreference.com/w/cpp/language/structured_binding): + +```cpp +for (const auto [key, value] : v.items()) +{ + std::cout << "key: " << key << ", value: " << value << '\n'; +} +``` + +Note the `#!cpp const auto` (by value), not `#!cpp const auto&`: unlike `BasicJsonType::items()`, whose elements are +references into an existing object, a view's `item` is produced on the fly for each step of the iteration, so there +is nothing for a reference to bind to. + +## Return value + +A range whose iterators dereference to [`item`](index.md#member-types) and whose `#!cpp begin()`/`#!cpp end()` are +equivalent to [`basic_json_view::begin()`](begin.md)/[`end()`](end.md), in document order. + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Constant. + +## Notes + +As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all +occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), +[`contains`](contains.md), and [`count`](count.md), which resolve to the *first* member with a given key. See the +[Notes on duplicate keys](operator[].md#notes) of `operator[]`. + +!!! danger "Lifetime issues" + + As for `BasicJsonType::items()`, calling `items()` on a temporary view (or a temporary document) is dangerous: + the range refers back to the document, so the document must outlive the loop. See + [#2040](https://github.com/nlohmann/json/issues/2040) for the `BasicJsonType` background. + +## Examples + +??? example + + The example below shows a settings object whose source text records every update to a key as a duplicate + member, in the order they happened. `items()` walks all of them, so the update history is visible, while + [`operator[]`](operator[].md) only ever sees the *first* one and [`materialize()`](materialize.md) -- like + [`BasicJsonType::parse()`](../basic_json/parse.md) -- keeps only the *last*. + + ```cpp + --8<-- "examples/basic_json_view__items.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__items.output" + ``` + +## See also + +- [begin](begin.md), [end](end.md) - the iterators `items()` is built on +- [`BasicJsonType::items`](../basic_json/items.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/number_token.md b/docs/mkdocs/docs/api/basic_json_view/number_token.md new file mode 100644 index 000000000..25a82992a --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/number_token.md @@ -0,0 +1,74 @@ +# nlohmann::basic_json_view::number_token + +```cpp +string_view_t number_token() const; +``` + +Returns the number exactly as it appears in the source text, without parsing or rounding it. + +## Return value + +The number's token text, as a [`string_view_t`](index.md#member-types) into the document's +[`source()`](../basic_json_document/source.md) text -- for example `#!cpp "1.50"`, `#!cpp "1E2"`, `#!cpp "-0"`, or an +integer literal with more digits than any number type holds (such as a 30-digit integer, which [`get()`](get.md) +and [`materialize()`](materialize.md) can only represent approximately, as a `number_float_t`). + +## Exception safety + +Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. + +## Exceptions + +Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example: +`"type must be number, but is string"`. + +This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no +`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with +`JSON_DIAGNOSTICS` enabled. + +## Complexity + +Constant. + +## Notes + +`BasicJsonType` has no counterpart to this function: once a number is parsed into `number_integer_t`, +`number_unsigned_t`, or `number_float_t`, its original textual form (leading zeros aside, which are already rejected +by the grammar; trailing zeros in the fraction; the case and sign of the exponent; ...) is gone. `number_token()` is +useful precisely where that form must survive -- a price or an identifier that must be reproduced exactly, or a +number too large for any of `BasicJsonType`'s number types to hold without loss. + +The returned [`string_view_t`](index.md#member-types) always points into the document's +[`source()`](../basic_json_document/source.md) text -- numbers are never decoded into the document's separate string +buffer -- and is valid exactly as long as that text is, see the [validity rules](index.md) of `basic_json_view`. + +## Examples + +??? example + + The example below keeps a price and an order ID exactly as they were written in an incoming order, where + converting them with [`get()`](get.md) would lose information: the price picks up floating-point rounding, and + the order ID -- more digits than a 64-bit integer holds -- can only be approximated as a `#!cpp double` once + `#!cpp materialize()`d. + + ```cpp + --8<-- "examples/basic_json_view__number_token.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__number_token.output" + ``` + +## See also + +- [get](get.md) - convert the value to a given type +- [get_string](get_string.md) - the string, without a copy +- [`BasicJsonType::number_integer_t`](../basic_json/number_integer_t.md), + [`number_unsigned_t`](../basic_json/number_unsigned_t.md), [`number_float_t`](../basic_json/number_float_t.md) - the + number types `#!cpp get()` and `materialize()` convert into + +## Version history + +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json_view/operator[].md b/docs/mkdocs/docs/api/basic_json_view/operator[].md new file mode 100644 index 000000000..2361bf77c --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/operator[].md @@ -0,0 +1,156 @@ +# nlohmann::basic_json_view::operator[] + +```cpp +// (1) +basic_json_view operator[](string_view_t key) const; +basic_json_view operator[](const char* key) const; +basic_json_view operator[](const string_t& key) const; + +// (2) +basic_json_view operator[](size_type idx) const; +basic_json_view operator[](int idx) const; + +// (3) +basic_json_view operator[](const json_pointer& ptr) const; +``` + +1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see + the [Notes](#notes) below) -- or a [discarded](is_discarded.md) view if there is no such member. +2. Returns the array element at index `idx`, or a [discarded](is_discarded.md) view if `idx` is out of range. (The + `#!cpp int` overload only exists so that an integer literal is not ambiguous between this overload and 1.) +3. Returns the value a JSON pointer `ptr` refers to, starting at this value, or a [discarded](is_discarded.md) view + wherever resolving it further is not possible without inserting into or extending the document (see + [Return value](#return-value) and [Exceptions](#exceptions) below). + +## Parameters + +`key` (in) +: object key of the element to access + +`idx` (in) +: index of the element to access + +`ptr` (in) +: JSON pointer to the element to access + +## Return value + +1. the value of the first member with key `key`, or a discarded view if `#!cpp is_object()` is `#!cpp false` or no + member has this key +2. the element at index `idx`, or a discarded view if `#!cpp is_array()` is `#!cpp false` or `#!cpp idx >= size()` +3. the value `ptr` resolves to, starting at this value, or a discarded view for exactly the reference tokens where the + **const** overload of [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) invokes undefined behavior for + the same pointer and the same document: an object member that does not exist, or an array index that is out of + range + +## Exception safety + +Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. + +## Exceptions + +1. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an object -- + the same exception, with the same message, that the **const** overload of + [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a string argument on a non-object value. +2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an array -- + the same exception, with the same message, that the **const** overload of + [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a numeric argument on a non-array value. +3. Throws the same exceptions, with the same messages, that the **const** overload of + [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for the same pointer and the same document: + - [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if a reference token is `#!cpp "-"` + at an array. + - [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token cannot be + resolved because it is used on a primitive value. + - [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in `ptr` begins + with `#!cpp '0'`. + - [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a + number. + +None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no +`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with +`JSON_DIAGNOSTICS` enabled. + +## Complexity + +1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after + another, in document order, stopping at the first match. Each comparison first checks the key's length -- + already known from the index, without reading the key bytes -- before comparing its content, so a key of a + different length than `key` is rejected without touching the source text. +2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the + index (unlike `BasicJsonType`'s array, which is random-access). +3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at + that level (as 1.) or the index into the array (as 2.). + +## Notes + +Unlike `BasicJsonType::operator[]`, which is undefined behavior (guarded by a +[runtime assertion](../../features/assertions.md)) for a missing key on a **const** value, this operator always +returns a safe, testable result: a [discarded](is_discarded.md) view, which is `#!cpp false` in a boolean context. +There is also no non-const overload that inserts a missing key or extends an array -- a view never modifies the +document. + +!!! info "Duplicate keys" + + If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md), + [`contains`](contains.md), [`count`](count.md)) all resolve to the *first* member with that key, because a + lookup can stop as soon as it finds a match. This is different from + [`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every + member in order and so end up keeping the *last* value for a repeated key -- there is no reason for them to stop + early. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including + duplicates, in document order. See the example below and [`size()`](size.md#notes). + +!!! info "JSON pointer resolution" + + Overload 3 walks `ptr` one reference token at a time, starting at this value, the same way [`at`](at.md) and + [`contains`](contains.md) do. It only ever returns a discarded view where the **const** overload of + `BasicJsonType::operator[]` would be undefined behavior for the same pointer -- a missing object member or an + out-of-range array index -- and still throws for every other way `ptr` can fail to resolve. See + [`at`](at.md#exceptions) for the checked version, which throws in every case instead, and + [`contains`](contains.md) for a version that never throws. + +## Examples + +??? example "Example: (1)/(2) access specified element" + + The example below reads a couple of fields out of a batch of user records without ever materializing a full + `BasicJsonType` value for the batch. `operator[]` is used both to look up an optional object member and to index + into an array -- in both cases, a missing value comes back as a discarded view that can be tested with a plain + `#!cpp if`, instead of relying on undefined behavior. + + ```cpp + --8<-- "examples/basic_json_view__operator[].cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__operator[].output" + ``` + +??? example "Example: (3) access specified element via JSON pointer" + + The example below reaches straight into one deeply nested field of a large document with a single JSON pointer, + without ever building a tree for the rest of it, and shows the discarded-view and throwing outcomes of a pointer + that cannot be fully resolved. + + ```cpp + --8<-- "examples/basic_json_view__operator[]_json_pointer.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__operator[]_json_pointer.output" + ``` + +## See also + +- [at](at.md) - access specified element with bounds checking (throws instead of returning a discarded view) +- [front](front.md), [back](back.md) - access the first or last element +- [find](find.md), [contains](contains.md) - look up a member without throwing +- [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) - the corresponding function of `basic_json` +- [`json_pointer`](../json_pointer/index.md) - JSON pointer type used by overload 3 + +## Version history + +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json_view/size.md b/docs/mkdocs/docs/api/basic_json_view/size.md index cd354d2f3..1bb0a5822 100644 --- a/docs/mkdocs/docs/api/basic_json_view/size.md +++ b/docs/mkdocs/docs/api/basic_json_view/size.md @@ -33,6 +33,12 @@ Constant: for an object or array, the element count is stored in the index, not As for [`BasicJsonType::size()`](../basic_json/size.md), this does not return the length of a string value -- it is `1` for a string, regardless of its length. +If the source text has an object with a duplicate key, every occurrence counts towards its `size()` -- unlike +[`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which keeps only the last +value for a repeated key. This means `#!cpp v.size()` can be larger than `#!cpp v.materialize().size()`. See the +[Notes on duplicate keys](operator[].md#notes) of `operator[]` for why lookups and iteration disagree on how many +members there are. + ## Examples ??? example diff --git a/docs/mkdocs/docs/api/basic_json_view/type_name.md b/docs/mkdocs/docs/api/basic_json_view/type_name.md new file mode 100644 index 000000000..b5bde001d --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/type_name.md @@ -0,0 +1,63 @@ +# nlohmann::basic_json_view::type_name + +```cpp +const char* type_name() const noexcept; +``` + +Returns the type name as string to be used in error messages -- usually to indicate that a function was called on a +wrong JSON type. Identical to [`BasicJsonType::type_name()`](../basic_json/type_name.md), including the extra +`#!cpp "discarded"` return value for a [discarded](is_discarded.md) view (`BasicJsonType::type_name()` produces the +same string for a discarded `BasicJsonType` value). + +## Return value + +a string representation of the type ([`value_t`](../basic_json/value_t.md)): + +| Value type | return value | +|-----------------------------------------------------|---------------| +| `#!json null` | `"null"` | +| boolean | `"boolean"` | +| string | `"string"` | +| number (integer, unsigned integer, floating-point) | `"number"` | +| object | `"object"` | +| array | `"array"` | +| discarded | `"discarded"` | + +`type_name()` never returns `#!cpp "binary"`, since a JSON text has no binary values (see +[`is_binary()`](is_binary.md)); it also never returns `#!cpp "invalid"`, since a view's `#!cpp kind` always comes +from a value the parser actually produced. + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Constant. + +## Examples + +??? example + + The example below reports why some parsed messages were rejected, using only `type_name()` -- no + `BasicJsonType` value is ever built for the ones that are wrong, and the message text matches what + [`BasicJsonType::type_name()`](../basic_json/type_name.md) would produce for the same value. + + ```cpp + --8<-- "examples/basic_json_view__type_name.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__type_name.output" + ``` + +## See also + +- [type](type.md) - return the type of the value +- [`BasicJsonType::type_name`](../basic_json/type_name.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/value.md b/docs/mkdocs/docs/api/basic_json_view/value.md new file mode 100644 index 000000000..ff63e32f5 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/value.md @@ -0,0 +1,131 @@ +# nlohmann::basic_json_view::value + +```cpp +// (1) +template +T value(string_view_t key, const T& default_value) const; +string_t value(string_view_t key, const char* default_value) const; + +// (2) +template +T value(const json_pointer& ptr, const T& default_value) const; +string_t value(const json_pointer& ptr, const char* default_value) const; +``` + +1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see + [Notes on duplicate keys](operator[].md#notes)) -- converted to `T`, or `default_value` if there is no such member. +2. Returns the value a JSON pointer `ptr` refers to, starting at this value, converted to `T`, or `default_value` if + `ptr` cannot be resolved. + +Both overloads have a dedicated `#!cpp const char*` overload, so `#!cpp v.value(key, "default")` (and the JSON pointer +equivalent) deduce `string_t`, not `const char*`, for their return type and for the comparison used to pick between +`key` and `default_value`. + +## Template parameters + +`T` +: the type to convert the found value to; also the type of `default_value` + +## Parameters + +`key` (in) +: object key of the element to access + +`ptr` (in) +: JSON pointer to the element to access + +`default_value` (in) +: the value to return if `key`/`ptr` resolves to no value + +## Return value + +1. the first member with key `key`, converted to `T`, or `default_value` +2. the value `ptr` resolves to, converted to `T`, or `default_value` + +## Exception safety + +Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. + +## Exceptions + +1. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the value is not an object -- + the same exception, with the same message, that [`BasicJsonType::value`](../basic_json/value.md) throws for the + same call. If a member with key `key` is found, throws whatever converting it to `T` throws (typically + [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302), with the same message + [`BasicJsonType::value`](../basic_json/value.md) throws for the same mismatch); a missing member never throws. +2. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if this value -- not the value `ptr` + resolves to -- is neither an object nor an array. Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) + or [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if `ptr` contains a malformed array + index. If `ptr` resolves to a value, throws whatever converting it to `T` throws. Every other way `ptr` can fail to + resolve -- a missing key, an out-of-range or "`-`" array index, an unresolvable token on a primitive -- yields + `default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches + `out_of_range` and returns `default_value`. + +None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no +`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with +`JSON_DIAGNOSTICS` enabled. + +## Complexity + +1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after + another, in document order, stopping at the first match. Plus the complexity of converting the found member to + `T` (see [`get`](get.md)). +2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at + that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and + [`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value + to `T`. + +## Notes + +!!! info "Differences to `at` and `operator[]`" + + Unlike [`at`](at.md), this function does not throw if `key`/`ptr` resolves to no value. Unlike + [`operator[]`](operator[].md), it never returns a [discarded](is_discarded.md) view -- it always returns a `T` -- + and it is available on any view, since it never needs to insert a missing element the way the non-const + `BasicJsonType::operator[]` would. + +!!! info "Which values can be asked" + + As for [`BasicJsonType::value`](../basic_json/value.md), the key overload (1) requires an object, and the JSON + pointer overload (2) an object or an array. + +## Examples + +??? example "Example: (1) access specified object element with default value" + + The example below reads a couple of optional configuration fields with a default, so that a missing key never + needs a `#!cpp try`/`#!cpp catch` of its own. + + ```cpp + --8<-- "examples/basic_json_view__value.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__value.output" + ``` + +??? example "Example: (2) access specified element via JSON pointer with default value" + + The example below reads an optional, nested configuration value with a default, given as a JSON pointer. + + ```cpp + --8<-- "examples/basic_json_view__value_json_pointer.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json_view__value_json_pointer.output" + ``` + +## See also + +- [at](at.md) - access specified element with bounds checking (throws instead of returning a default value) +- [operator[]](operator[].md) - access specified element (returns a discarded view instead of a default value) +- [`BasicJsonType::value`](../basic_json/value.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__at.cpp b/docs/mkdocs/docs/examples/basic_json_view__at.cpp new file mode 100644 index 000000000..1e984c55f --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__at.cpp @@ -0,0 +1,36 @@ +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +int main() +{ + // required fields of a service configuration -- at() reports a missing + // or wrong-typed field with the very same exception basic_json::at() + // would throw for the equivalent nlohmann::json value, so error + // handling written against basic_json::at() keeps working unchanged + json_document config = json_document::parse(R"({"name": "cache", "port": "6379"})"); + const json_view service = config.root(); + + std::cout << service.at("port").materialize().dump() << '\n'; + + try + { + // "port" is a string, not an array + static_cast(service.at("port").at(0)); + } + catch (const nlohmann::json::type_error& e) + { + std::cout << e.what() << '\n'; + } + + try + { + static_cast(service.at("timeout")); + } + catch (const nlohmann::json::out_of_range& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__at.output b/docs/mkdocs/docs/examples/basic_json_view__at.output new file mode 100644 index 000000000..fe716b135 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__at.output @@ -0,0 +1,3 @@ +"6379" +[json.exception.type_error.304] cannot use at() with string +[json.exception.out_of_range.403] key 'timeout' not found diff --git a/docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.cpp b/docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.cpp new file mode 100644 index 000000000..47ed9419c --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.cpp @@ -0,0 +1,34 @@ +#include +#include + +using json_document = nlohmann::json_document; +using json_pointer = nlohmann::json::json_pointer; + +int main() +{ + json_document doc = json_document::parse(R"({"region": "eu", "servers": ["eu-1", "eu-2"]})"); + const auto root = doc.root(); + + std::cout << root.at(json_pointer("/servers/1")).materialize().dump() << '\n'; + + // at() throws for every resolution failure -- with the very same + // message json::at(ptr) would throw for the same pointer and the same + // document + try + { + static_cast(root.at(json_pointer("/servers/5"))); + } + catch (const nlohmann::json::out_of_range& e) + { + std::cout << e.what() << '\n'; + } + + try + { + static_cast(root.at(json_pointer("/missing"))); + } + catch (const nlohmann::json::out_of_range& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.output b/docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.output new file mode 100644 index 000000000..8d3603328 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.output @@ -0,0 +1,3 @@ +"eu-2" +[json.exception.out_of_range.401] array index 5 is out of range +[json.exception.out_of_range.403] key 'missing' not found diff --git a/docs/mkdocs/docs/examples/basic_json_view__back.cpp b/docs/mkdocs/docs/examples/basic_json_view__back.cpp new file mode 100644 index 000000000..606cb8966 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__back.cpp @@ -0,0 +1,25 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // the same build log; back() reads only the final status. It is linear + // in the number of events (unlike front(), which is constant), but + // still far less work than materializing the whole array + json_document log = json_document::parse(R"(["queued", "started", "compiling", "linking", "done"])"); + std::cout << log.root().back().materialize().dump() << '\n'; + + // an empty log -- back() throws instead of the undefined behavior + // basic_json::back() has for an empty array + json_document empty_log = json_document::parse("[]"); + try + { + static_cast(empty_log.root().back()); + } + catch (const nlohmann::json::invalid_iterator& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__back.output b/docs/mkdocs/docs/examples/basic_json_view__back.output new file mode 100644 index 000000000..8362099a1 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__back.output @@ -0,0 +1,2 @@ +"done" +[json.exception.invalid_iterator.214] cannot get value diff --git a/docs/mkdocs/docs/examples/basic_json_view__begin.cpp b/docs/mkdocs/docs/examples/basic_json_view__begin.cpp new file mode 100644 index 000000000..5989ae404 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__begin.cpp @@ -0,0 +1,18 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // a log record: the fields matter in the order they were written, e.g. + // to reproduce the record as it was logged. A nlohmann::json object + // sorts its keys, so materializing and iterating it would instead print + // them alphabetically ("level", "message", "time") + json_document record = json_document::parse(R"({"time": "10:00:01", "level": "info", "message": "started"})"); + + for (auto it = record.root().begin(); it != record.root().end(); ++it) + { + std::cout << it.key() << '=' << it->materialize().dump() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__begin.output b/docs/mkdocs/docs/examples/basic_json_view__begin.output new file mode 100644 index 000000000..1c92e0f83 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__begin.output @@ -0,0 +1,3 @@ +time="10:00:01" +level="info" +message="started" diff --git a/docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp b/docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp new file mode 100644 index 000000000..4e67a8041 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +int main() +{ + // sum many measurements with std::accumulate; cbegin()/cend() (identical + // to begin()/end() here -- the view is always read-only) let the view be + // used with standard algorithms without ever materializing the whole + // array into a nlohmann::json value + json_document measurements = json_document::parse("[3, 1, 4, 1, 5, 9, 2, 6]"); + const auto values = measurements.root(); + + const int sum = std::accumulate(values.cbegin(), values.cend(), 0, + [](int total, const json_view & v) + { + return total + v.materialize().get(); + }); + + std::cout << sum << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__cbegin.output b/docs/mkdocs/docs/examples/basic_json_view__cbegin.output new file mode 100644 index 000000000..e85087aff --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__cbegin.output @@ -0,0 +1 @@ +31 diff --git a/docs/mkdocs/docs/examples/basic_json_view__cend.cpp b/docs/mkdocs/docs/examples/basic_json_view__cend.cpp new file mode 100644 index 000000000..c72990673 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__cend.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +int main() +{ + // check that every element of a (possibly large) batch is an object, + // before materializing any of them -- cbegin()/cend() (identical to + // begin()/end() here) work as the range for std::all_of like they would + // for any standard container + json_document batch = json_document::parse(R"([{"id": 1}, {"id": 2}, {"id": 3}])"); + const auto records = batch.root(); + + const bool all_objects = std::all_of(records.cbegin(), records.cend(), + [](const json_view & v) + { + return v.is_object(); + }); + + std::cout << std::boolalpha << all_objects << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__cend.output b/docs/mkdocs/docs/examples/basic_json_view__cend.output new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__cend.output @@ -0,0 +1 @@ +true diff --git a/docs/mkdocs/docs/examples/basic_json_view__contains.cpp b/docs/mkdocs/docs/examples/basic_json_view__contains.cpp new file mode 100644 index 000000000..e339ee79a --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__contains.cpp @@ -0,0 +1,30 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // count how many of many incoming records carry an optional "retry_of" + // field -- contains() only walks the flat index, so scanning a large + // batch like this never builds a single nlohmann::json value + json_document batch = json_document::parse(R"( + [ + {"id": 1}, + {"id": 2, "retry_of": 1}, + {"id": 3}, + {"id": 4, "retry_of": 3} + ] + )"); + + const auto records = batch.root(); + std::size_t retries = 0; + for (std::size_t i = 0; i < records.size(); ++i) + { + if (records[i].contains("retry_of")) + { + ++retries; + } + } + std::cout << retries << " of " << records.size() << " records are retries\n"; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__contains.output b/docs/mkdocs/docs/examples/basic_json_view__contains.output new file mode 100644 index 000000000..f06576214 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__contains.output @@ -0,0 +1 @@ +2 of 4 records are retries diff --git a/docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.cpp b/docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.cpp new file mode 100644 index 000000000..260bac5eb --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.cpp @@ -0,0 +1,39 @@ +#include +#include + +using json_document = nlohmann::json_document; +using json_pointer = nlohmann::json::json_pointer; + +int main() +{ + // "retry_of" is only present on some records, nested a level down + // inside "meta" + json_document batch = json_document::parse(R"( + [ + {"id": 1, "meta": {}}, + {"id": 2, "meta": {"retry_of": 1}} + ] + )"); + + const auto records = batch.root(); + const json_pointer retry_of("/meta/retry_of"); + for (std::size_t i = 0; i < records.size(); ++i) + { + const auto record = records[i]; + if (record.contains(retry_of)) + { + std::cout << "record " << i << " is a retry of " << record[retry_of].get() << '\n'; + } + else + { + std::cout << "record " << i << " is original\n"; + } + } + + // contains() with a JSON pointer never throws -- not even for a + // pointer that indexes into a primitive ("/0/id/x") or uses a + // malformed array index ("/01"), either of which would need a + // try/catch with json::contains(ptr) + std::cout << std::boolalpha << records.contains(json_pointer("/0/id/x")) << '\n'; + std::cout << std::boolalpha << records.contains(json_pointer("/01")) << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.output b/docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.output new file mode 100644 index 000000000..02f089137 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.output @@ -0,0 +1,4 @@ +record 0 is original +record 1 is a retry of 1 +false +false diff --git a/docs/mkdocs/docs/examples/basic_json_view__count.cpp b/docs/mkdocs/docs/examples/basic_json_view__count.cpp new file mode 100644 index 000000000..648142e21 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__count.cpp @@ -0,0 +1,29 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // validate that every transaction of a batch carries a mandatory + // "amount" field before materializing any of them into a nlohmann::json + // value -- count() returns 0 or 1 for an object + json_document batch = json_document::parse(R"( + [ + {"id": 1, "amount": 9.99}, + {"id": 2} + ] + )"); + + const auto transactions = batch.root(); + for (std::size_t i = 0; i < transactions.size(); ++i) + { + const auto transaction = transactions[i]; + if (transaction.count("amount") == 0) + { + std::cout << "transaction " << i << " is missing \"amount\"\n"; + continue; + } + std::cout << "transaction " << i << ": " << transaction["amount"].materialize().dump() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__count.output b/docs/mkdocs/docs/examples/basic_json_view__count.output new file mode 100644 index 000000000..a6344a13b --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__count.output @@ -0,0 +1,2 @@ +transaction 0: 9.99 +transaction 1 is missing "amount" diff --git a/docs/mkdocs/docs/examples/basic_json_view__end.cpp b/docs/mkdocs/docs/examples/basic_json_view__end.cpp new file mode 100644 index 000000000..6808a6c95 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__end.cpp @@ -0,0 +1,27 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // scan a (possibly large) array of readings for the first one over a + // threshold; the loop stops at end() as soon as one is found, and only + // the matching reading is ever materialized + json_document readings = json_document::parse("[12, 18, 25, 31, 9]"); + const auto values = readings.root(); + + auto it = values.begin(); + for (; it != values.end(); ++it) + { + if (it->materialize().get() > 20) + { + break; + } + } + + if (it != values.end()) + { + std::cout << "first reading over 20: " << it->materialize().dump() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__end.output b/docs/mkdocs/docs/examples/basic_json_view__end.output new file mode 100644 index 000000000..b36dc9c70 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__end.output @@ -0,0 +1 @@ +first reading over 20: 25 diff --git a/docs/mkdocs/docs/examples/basic_json_view__find.cpp b/docs/mkdocs/docs/examples/basic_json_view__find.cpp new file mode 100644 index 000000000..06141b65a --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__find.cpp @@ -0,0 +1,29 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // a batch of incoming events; only some carry a "user_id" -- find() + // locates it without throwing for the events that turn out to not be + // objects, and without materializing an event that does not match + json_document batch = json_document::parse(R"( + [ + {"type": "click", "user_id": 42}, + {"type": "ping"}, + {"type": "click", "user_id": 7} + ] + )"); + + const auto events = batch.root(); + for (std::size_t i = 0; i < events.size(); ++i) + { + const auto event = events[i]; + const auto it = event.find("user_id"); + if (it != event.end()) + { + std::cout << "user " << it->materialize().dump() << '\n'; + } + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__find.output b/docs/mkdocs/docs/examples/basic_json_view__find.output new file mode 100644 index 000000000..30637e7e3 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__find.output @@ -0,0 +1,2 @@ +user 42 +user 7 diff --git a/docs/mkdocs/docs/examples/basic_json_view__front.cpp b/docs/mkdocs/docs/examples/basic_json_view__front.cpp new file mode 100644 index 000000000..ed743f77e --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__front.cpp @@ -0,0 +1,24 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // the build log of a running job; front() reads just the earliest event + // without materializing the (possibly long) rest of the log + json_document log = json_document::parse(R"(["queued", "started", "compiling", "linking", "done"])"); + std::cout << log.root().front().materialize().dump() << '\n'; + + // an empty log -- front() throws instead of the undefined behavior + // basic_json::front() has for an empty array + json_document empty_log = json_document::parse("[]"); + try + { + static_cast(empty_log.root().front()); + } + catch (const nlohmann::json::invalid_iterator& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__front.output b/docs/mkdocs/docs/examples/basic_json_view__front.output new file mode 100644 index 000000000..a0de5266a --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__front.output @@ -0,0 +1,2 @@ +"queued" +[json.exception.invalid_iterator.214] cannot get value diff --git a/docs/mkdocs/docs/examples/basic_json_view__get.cpp b/docs/mkdocs/docs/examples/basic_json_view__get.cpp new file mode 100644 index 000000000..43e262035 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__get.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +// address has no direct conversion in get(), so get
() falls back +// to materialize().get
() -- a real nlohmann::json value is built +// for just this one member, and its own from_json() runs on that +struct address +{ + std::string city; + int zip = 0; +}; + +void from_json(const nlohmann::json& j, address& a) +{ + j.at("city").get_to(a.city); + j.at("zip").get_to(a.zip); +} + +int main() +{ + json_document doc = json_document::parse(R"( + { + "name": "Alice", + "active": true, + "orders": [1, 2, 3], + "address": {"city": "Berlin", "zip": 10115} + } + )"); + const json_view customer = doc.root(); + + // read typed fields straight into C++ variables -- none of these build + // a nlohmann::json value + const std::string name = customer["name"].get(); + const bool active = customer["active"].get(); + std::cout << name << (active ? " (active)" : " (inactive)") << '\n'; + + // std::vector keeps views of the array elements instead of + // copies of their values + bool first = true; + for (const json_view order : customer["orders"].get>()) + { + std::cout << (first ? "" : " ") << order.get(); + first = false; + } + std::cout << '\n'; + + // everything else goes through materialize() + const address a = customer["address"].get
(); + std::cout << a.city << ' ' << a.zip << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__get.output b/docs/mkdocs/docs/examples/basic_json_view__get.output new file mode 100644 index 000000000..3c4b4b444 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__get.output @@ -0,0 +1,3 @@ +Alice (active) +1 2 3 +Berlin 10115 diff --git a/docs/mkdocs/docs/examples/basic_json_view__get_string.cpp b/docs/mkdocs/docs/examples/basic_json_view__get_string.cpp new file mode 100644 index 000000000..66ae1bbb3 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__get_string.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +int main() +{ + // a "large response" stand-in: only the "id" field is ever read out of it + const std::string text = + R"({"id": "8f14e45f-ceea-467e-bb92-963f5e3c7a08", "note": "created via API\n", "payload": "..."})"; + const json_document doc = json_document::parse(text); + const json_view response = doc.root(); + + const json_view::string_view_t id = response["id"].get_string(); + std::cout << id << '\n'; + + // no std::string was allocated for "id": its bytes still live inside + // the original buffer, so id's data lies inside [text.data(), + // text.data() + text.size()) + const bool id_in_source = id.data() >= text.data() && id.data() + id.size() <= text.data() + text.size(); + std::cout << std::boolalpha << id_in_source << '\n'; + + // "note" contains an escape sequence ('\n'), so it was decoded once + // into the document's own buffer -- get_string() still avoids a copy + // into a new std::string, but the bytes no longer live inside "text" + const json_view::string_view_t note = response["note"].get_string(); + const bool note_in_source = note.data() >= text.data() && note.data() + note.size() <= text.data() + text.size(); + std::cout << std::boolalpha << note_in_source << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__get_string.output b/docs/mkdocs/docs/examples/basic_json_view__get_string.output new file mode 100644 index 000000000..d5fd0cba6 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__get_string.output @@ -0,0 +1,3 @@ +8f14e45f-ceea-467e-bb92-963f5e3c7a08 +true +false diff --git a/docs/mkdocs/docs/examples/basic_json_view__get_to.cpp b/docs/mkdocs/docs/examples/basic_json_view__get_to.cpp new file mode 100644 index 000000000..748270759 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__get_to.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +int main() +{ + json_document doc = json_document::parse(R"({"host": "db.example.com", "port": 5432, "ssl": true})"); + const json_view config = doc.root(); + + // get_to() writes directly into existing variables -- handy for filling + // in the members of a struct one field at a time, without an + // intermediate value from get() for each one + std::string host; + int port = 0; + bool ssl = false; + config["host"].get_to(host); + config["port"].get_to(port); + config["ssl"].get_to(ssl); + std::cout << host << ':' << port << (ssl ? " (tls)" : "") << '\n'; + + // the return value is a reference to the argument, so a call can be + // used directly in a larger expression + std::string other_host; + std::cout << config["host"].get_to(other_host).size() << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__get_to.output b/docs/mkdocs/docs/examples/basic_json_view__get_to.output new file mode 100644 index 000000000..a5cdf69cf --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__get_to.output @@ -0,0 +1,2 @@ +db.example.com:5432 (tls) +14 diff --git a/docs/mkdocs/docs/examples/basic_json_view__items.cpp b/docs/mkdocs/docs/examples/basic_json_view__items.cpp new file mode 100644 index 000000000..3f7779c19 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__items.cpp @@ -0,0 +1,22 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // a settings object whose source text records every update to a key as + // a duplicate member. items() visits all of them, in document order, so + // the update history is visible; operator[] only ever sees the first + // one, and materialize() -- like basic_json::parse() -- keeps the last + json_document updates = json_document::parse(R"({"retries": 1, "timeout": 30, "retries": 5})"); + const auto settings = updates.root(); + + for (const auto& item : settings.items()) + { + std::cout << item.key() << '=' << item.value().materialize().dump() << '\n'; + } + + std::cout << "first \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n'; + std::cout << "last \"retries\" kept by materialize(): " << settings.materialize()["retries"].dump() << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__items.output b/docs/mkdocs/docs/examples/basic_json_view__items.output new file mode 100644 index 000000000..78609c8ee --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__items.output @@ -0,0 +1,5 @@ +retries=1 +timeout=30 +retries=5 +first "retries" seen by operator[]: 1 +last "retries" kept by materialize(): 5 diff --git a/docs/mkdocs/docs/examples/basic_json_view__number_token.cpp b/docs/mkdocs/docs/examples/basic_json_view__number_token.cpp new file mode 100644 index 000000000..dec63371c --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__number_token.cpp @@ -0,0 +1,29 @@ +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +int main() +{ + // a price and an order id from an incoming order -- both need to be + // reproduced exactly, e.g. for an invoice or an audit log + json_document doc = json_document::parse(R"( + {"price": 19.90, "order_id": 1234567890123456789012345, "quantity": 3} + )"); + const json_view order = doc.root(); + + // number_token() returns the number exactly as written in the source + std::cout << order["price"].number_token() << '\n'; + std::cout << order["order_id"].number_token() << '\n'; + + // get() converts it instead -- the exact source text is gone: + // "19.90" becomes the double closest to 19.9, printed without the + // trailing zero, and the 25-digit order id -- far beyond any 64-bit + // integer -- can only be approximated as a double + std::cout << order["price"].get() << '\n'; + std::cout << order.materialize()["order_id"].dump() << '\n'; + + // an ordinary quantity has nothing to lose either way + std::cout << order["quantity"].number_token() << " == " << order["quantity"].get() << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__number_token.output b/docs/mkdocs/docs/examples/basic_json_view__number_token.output new file mode 100644 index 000000000..c3f59e9c7 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__number_token.output @@ -0,0 +1,5 @@ +19.90 +1234567890123456789012345 +19.9 +1.2345678901234568e+24 +3 == 3 diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator[].cpp b/docs/mkdocs/docs/examples/basic_json_view__operator[].cpp new file mode 100644 index 000000000..e17919a9d --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__operator[].cpp @@ -0,0 +1,42 @@ +#include +#include + +using json_document = nlohmann::json_document; + +int main() +{ + // two user records from a large API response; only the fields that are + // actually read are ever touched, and no nlohmann::json tree is built + // for the batch + json_document batch = json_document::parse(R"( + [ + {"name": "Alice", "email": "alice@example.com", "tags": ["admin", "ops"]}, + {"name": "Bob", "tags": []} + ] + )"); + + const auto users = batch.root(); + for (std::size_t i = 0; i < users.size(); ++i) + { + const auto user = users[i]; + std::cout << user["name"].materialize().dump(); + + // operator[] on a missing object key gives a discarded view -- test + // it with a plain "if". The const overload of json::operator[] + // would instead be undefined behavior (guarded by an assertion) for + // a missing key + if (const auto email = user["email"]) + { + std::cout << " <" << email.materialize().dump() << ">"; + } + + // the same holds for an array index past the end: a discarded view, + // not undefined behavior + if (const auto first_tag = user["tags"][0]) + { + std::cout << " #" << first_tag.materialize().dump(); + } + + std::cout << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator[].output b/docs/mkdocs/docs/examples/basic_json_view__operator[].output new file mode 100644 index 000000000..630659e36 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__operator[].output @@ -0,0 +1,2 @@ +"Alice" <"alice@example.com"> #"admin" +"Bob" diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.cpp b/docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.cpp new file mode 100644 index 000000000..15f37555d --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.cpp @@ -0,0 +1,47 @@ +#include +#include + +using json_document = nlohmann::json_document; +using json_pointer = nlohmann::json::json_pointer; + +int main() +{ + // a larger document; operator[] with a JSON pointer reaches straight to + // one deeply nested field, without ever building a tree for the rest + json_document doc = json_document::parse(R"( + { + "region": { + "servers": [ + {"name": "eu-1", "metrics": {"cpu": 0.42}}, + {"name": "eu-2", "metrics": {"cpu": 0.71}} + ] + } + } + )"); + + const auto root = doc.root(); + std::cout << root[json_pointer("/region/servers/1/metrics/cpu")].materialize().dump() << '\n'; + + // a missing key or an out-of-range index along the path gives a + // discarded view, exactly where const json::operator[] would be + // undefined behavior for the same pointer + if (const auto missing = root[json_pointer("/region/servers/5/metrics/cpu")]) + { + std::cout << missing.materialize().dump() << '\n'; + } + else + { + std::cout << "no such server\n"; + } + + // indexing into a primitive still throws, as basic_json::operator[] + // does for the same pointer + try + { + static_cast(root[json_pointer("/region/servers/0/name/x")]); + } + catch (const nlohmann::json::out_of_range& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.output b/docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.output new file mode 100644 index 000000000..9c9a978f5 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.output @@ -0,0 +1,3 @@ +0.71 +no such server +[json.exception.out_of_range.404] unresolved reference token 'x' diff --git a/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp b/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp new file mode 100644 index 000000000..30661c0fd --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp @@ -0,0 +1,30 @@ +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +int main() +{ + // report why some parsed messages were rejected, using only + // type_name() -- no nlohmann::json value is built for the ones that + // are wrong + json_document good = json_document::parse(R"({"id": 1})"); + json_document bad = json_document::parse("[1, 2, 3]"); + json_document failed = json_document::parse("not json", /* allow_exceptions */ false); + + for (const json_view v : + { + good.root(), bad.root(), failed.root() + }) + { + if (v.is_object()) + { + std::cout << "ok\n"; + } + else + { + std::cout << "expected an object, got " << v.type_name() << '\n'; + } + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__type_name.output b/docs/mkdocs/docs/examples/basic_json_view__type_name.output new file mode 100644 index 000000000..e0ab0669e --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__type_name.output @@ -0,0 +1,3 @@ +ok +expected an object, got array +expected an object, got discarded diff --git a/docs/mkdocs/docs/examples/basic_json_view__value.cpp b/docs/mkdocs/docs/examples/basic_json_view__value.cpp new file mode 100644 index 000000000..1829730ef --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__value.cpp @@ -0,0 +1,29 @@ +#include +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; + +int main() +{ + json_document doc = json_document::parse(R"({"server": {"host": "localhost"}})"); + const json_view server = doc.root()["server"]; + + // "port" is missing -- value() returns the default instead of + // throwing, so optional configuration fields never need their own + // try/catch + std::cout << server.value("host", std::string("0.0.0.0")) << '\n'; + std::cout << server.value("port", 8080) << '\n'; + + // a present but wrong-typed default still throws -- value() only + // replaces "not found", not "wrong type", exactly as basic_json::value + try + { + static_cast(server.value("host", 0)); + } + catch (const nlohmann::json::type_error& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__value.output b/docs/mkdocs/docs/examples/basic_json_view__value.output new file mode 100644 index 000000000..ea136a6ef --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__value.output @@ -0,0 +1,3 @@ +localhost +8080 +[json.exception.type_error.302] type must be number, but is string diff --git a/docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.cpp b/docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.cpp new file mode 100644 index 000000000..605f389a7 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.cpp @@ -0,0 +1,21 @@ +#include +#include + +using json_document = nlohmann::json_document; +using json_view = nlohmann::json_view; +using json_pointer = nlohmann::json::json_pointer; + +int main() +{ + json_document doc = json_document::parse(R"({"server": {"host": "localhost", "limits": {"connections": 100}}})"); + const json_view config = doc.root(); + + // a nested, optional setting read with a default -- no exception, even + // though "timeout" is missing several levels down + std::cout << config.value(json_pointer("/server/limits/connections"), 10) << '\n'; + std::cout << config.value(json_pointer("/server/limits/timeout"), 30) << '\n'; + + // an out-of-range array index also falls back to the default + json_document list_doc = json_document::parse(R"({"servers": ["a", "b"]})"); + std::cout << list_doc.root().value(json_pointer("/servers/5"), std::string("none")) << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.output b/docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.output new file mode 100644 index 000000000..07ac12be0 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.output @@ -0,0 +1,3 @@ +100 +30 +none diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 6b66c42ca..7dbea9241 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -22,10 +22,15 @@ actually needed (for a string, only if it contains escape sequences, into one sh [`basic_json_view`](../api/basic_json_view/index.md) is a small, trivially copyable handle (two pointers) into that index. It gives you the read-only, type-inspection part of the `basic_json` interface -- [`type()`](../api/basic_json_view/type.md) and the `is_*()` predicates, -[`size()`](../api/basic_json_view/size.md)/[`empty()`](../api/basic_json_view/empty.md) -- without ever allocating a -`basic_json` value. When you do need an actual `basic_json` value for a subtree, -[`materialize()`](../api/basic_json_view/materialize.md) builds exactly the one -[`parse()`](../api/basic_json/parse.md) would have produced for it. +[`size()`](../api/basic_json_view/size.md)/[`empty()`](../api/basic_json_view/empty.md) -- as well as element access +([`operator[]`](../api/basic_json_view/operator%5B%5D.md), [`at`](../api/basic_json_view/at.md), +[`front`](../api/basic_json_view/front.md)/[`back`](../api/basic_json_view/back.md)), lookup +([`find`](../api/basic_json_view/find.md), [`contains`](../api/basic_json_view/contains.md), +[`count`](../api/basic_json_view/count.md)), and iteration +([`begin`](../api/basic_json_view/begin.md)/[`end`](../api/basic_json_view/end.md), +[`items`](../api/basic_json_view/items.md)) -- without ever allocating a `basic_json` value. When you do need an +actual `basic_json` value for a subtree, [`materialize()`](../api/basic_json_view/materialize.md) builds exactly the +one [`parse()`](../api/basic_json/parse.md) would have produced for it. ## How to use it @@ -116,9 +121,50 @@ whenever any of the other conditions above was not met. [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled, [`materialize()`](../api/basic_json_view/materialize.md) does not set them: there is no lexer run during the replay to record them. -- **Element access, iteration, `get()`, JSON Pointer, `dump()`, and comparison are not (yet) provided** by - `basic_json_view`. For now, [`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you - can do those things with. +- **Objects iterate in document order.** [`begin()`](../api/basic_json_view/begin.md)/ + [`end()`](../api/basic_json_view/end.md) and [`items()`](../api/basic_json_view/items.md) visit an object's + members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which + sorts by key, so iterating a [`materialize()`](../api/basic_json_view/materialize.md)d value can print members in + a different order than iterating the view they came from. +- **Duplicate keys are visible.** If an object in the source text repeats a key, + [`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and + [`items()`](../api/basic_json_view/items.md) visit *every* occurrence (and [`size()`](../api/basic_json_view/size.md) + counts all of them), while [`operator[]`](../api/basic_json_view/operator%5B%5D.md), + [`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md), + [`contains`](../api/basic_json_view/contains.md), and [`count`](../api/basic_json_view/count.md) resolve to the + *first* occurrence, since a lookup can stop as soon as it finds a match. `basic_json::parse()` (and so + [`materialize()`](../api/basic_json_view/materialize.md)) instead keeps only the *last* value for a repeated key. + See the [Notes on duplicate keys](../api/basic_json_view/operator%5B%5D.md#notes) of `operator[]`. +- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own + 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. +- **`dump()` and comparison are not (yet) provided** by `basic_json_view`. For now, + [`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can do those things with. + +## Getting values out without copying + +[`get()`](../api/basic_json_view/get.md) converts many `T` directly from the flat index, without ever building a +`basic_json` value for the conversion: `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`, +`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json`/`ordered_json` (via +[`materialize()`](../api/basic_json_view/materialize.md)), `basic_json_view` itself, `#!cpp std::vector`, and +`#!cpp std::map`/`#!cpp std::unordered_map` with string-like keys. Every other type -- `#!cpp std::list`, +`#!cpp std::pair`, `#!cpp std::array`, enumerations, user types with a `from_json()` -- goes through +[`materialize()`](../api/basic_json_view/materialize.md)`.get()` instead: the subtree is built into a real +`basic_json` value first, exactly as [`parse()`](../api/basic_json/parse.md) would, and converted from there. + +Two conversions never copy at all: + +- [`get_string()`](../api/basic_json_view/get_string.md) (equivalently, `#!cpp get()`) returns a + string as a `string_view_t` pointing into the document's [`source()`](../api/basic_json_document/source.md) text -- + or, for a string that contains escape sequences, into the document's own buffer of decoded strings -- instead of + allocating a new `#!cpp std::string`. +- [`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written in the + source, e.g. `#!cpp "1.50"`, `#!cpp "1E2"`, or an integer with more digits than any number type holds, instead of + rounding it into a `#!cpp double`/`#!cpp int64_t` the way `#!cpp get()` (and + [`basic_json::parse()`](../api/basic_json/parse.md)) would. + +Both results are only valid as long as the view -- and, for a string with no escapes, the borrowed source text -- is. ## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view` diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 6e6a2c4cd..32da88c70 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -250,7 +250,20 @@ nav: - basic_json_view: - 'Overview': api/basic_json_view/index.md - '(Constructor)': api/basic_json_view/basic_json_view.md + - 'at': api/basic_json_view/at.md + - 'back': api/basic_json_view/back.md + - 'begin': api/basic_json_view/begin.md + - 'cbegin': api/basic_json_view/cbegin.md + - 'cend': api/basic_json_view/cend.md + - 'contains': api/basic_json_view/contains.md + - 'count': api/basic_json_view/count.md - 'empty': api/basic_json_view/empty.md + - 'end': api/basic_json_view/end.md + - 'find': api/basic_json_view/find.md + - 'front': api/basic_json_view/front.md + - 'get': api/basic_json_view/get.md + - 'get_string': api/basic_json_view/get_string.md + - 'get_to': api/basic_json_view/get_to.md - 'is_array': api/basic_json_view/is_array.md - 'is_binary': api/basic_json_view/is_binary.md - 'is_boolean': api/basic_json_view/is_boolean.md @@ -264,11 +277,16 @@ nav: - 'is_primitive': api/basic_json_view/is_primitive.md - 'is_string': api/basic_json_view/is_string.md - 'is_structured': api/basic_json_view/is_structured.md + - 'items': api/basic_json_view/items.md - 'materialize': api/basic_json_view/materialize.md + - 'number_token': api/basic_json_view/number_token.md - 'operator bool': api/basic_json_view/operator_bool.md + - 'operator[]': api/basic_json_view/operator[].md - 'size': api/basic_json_view/size.md - 'source_offset': api/basic_json_view/source_offset.md - 'type': api/basic_json_view/type.md + - 'type_name': api/basic_json_view/type_name.md + - 'value': api/basic_json_view/value.md - byte_container_with_subtype: - 'Overview': api/byte_container_with_subtype/index.md - '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 92f52aedc..bf977e80b 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -31,6 +31,11 @@ NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +struct json_pointer_access; +} // namespace detail + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document /// @sa https://json.nlohmann.me/api/json_pointer/ template @@ -43,6 +48,8 @@ class json_pointer template friend class json_pointer; + friend struct detail::json_pointer_access; + template struct string_t_helper { @@ -1172,4 +1179,18 @@ inline bool operator<(const json_pointer& lhs, } #endif +namespace detail +{ +/// the reference tokens of a json_pointer, for code that resolves pointers +/// without a basic_json value (such as the zero-copy view) +struct json_pointer_access +{ + template + static const std::vector::string_t>& reference_tokens(const json_pointer& ptr) noexcept + { + return ptr.reference_tokens; + } +}; +} // namespace detail + NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/value_t.hpp b/include/nlohmann/detail/value_t.hpp index 9fc217bcc..52b947ffe 100644 --- a/include/nlohmann/detail/value_t.hpp +++ b/include/nlohmann/detail/value_t.hpp @@ -67,6 +67,39 @@ enum class value_t : std::uint8_t discarded ///< discarded by the parser callback function }; +/*! +@brief the name of a JSON type, as returned by basic_json::type_name() + +Used in exception messages; also by code that reports types without a +basic_json value at hand (such as the zero-copy view). +*/ +inline const char* value_type_name(const value_t t) noexcept +{ + switch (t) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::binary: + return "binary"; + case value_t::discarded: + return "discarded"; + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + return "number"; + default: + return "invalid"; + } +} + /*! @brief comparison operator for JSON types diff --git a/include/nlohmann/detail/view/errors.hpp b/include/nlohmann/detail/view/errors.hpp index ff5816efd..44d8a6438 100644 --- a/include/nlohmann/detail/view/errors.hpp +++ b/include/nlohmann/detail/view/errors.hpp @@ -40,6 +40,12 @@ namespace view NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); } +/// a parse error without a position (as those of json_pointer) +[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg) +{ + NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr)); +} + /*! @brief throw the exception BasicJsonType::parse would throw for this input diff --git a/include/nlohmann/detail/view/iterator.hpp b/include/nlohmann/detail/view/iterator.hpp new file mode 100644 index 000000000..dfeda4a34 --- /dev/null +++ b/include/nlohmann/detail/view/iterator.hpp @@ -0,0 +1,263 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // ptrdiff_t, size_t +#include // forward_iterator_tag +#include // string, to_string +#include // enable_if + +#include +#include +#include +#include +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// the result of view_iterator::operator->: keeps the view alive for the +/// duration of the member access +template +class arrow_proxy +{ + public: + explicit arrow_proxy(const View& v) noexcept + : m_view(v) + {} + + const View* operator->() const noexcept + { + return &m_view; + } + + private: + View m_view; +}; + +/*! +@brief forward iterator over the elements of a basic_json_view + +Iterates over the elements of an array or the member values of an object, in +document order; key() gives the key of an object member. As for basic_json, a +primitive value iterates as a range of one element (itself), and null as an +empty range. +*/ +template +class view_iterator +{ + public: + using iterator_category = std::forward_iterator_tag; + using value_type = View; + using difference_type = std::ptrdiff_t; + using pointer = arrow_proxy; + using reference = View; + using string_view_t = typename View::string_view_t; + + view_iterator() noexcept = default; + + /// @param[in] pos the element, or the key of the member + /// @param[in] object whether pos is a key (its value is the next node) + view_iterator(const document_data* d, const node* pos, bool object) noexcept + : m_doc(d), m_pos(pos), m_value_offset(object ? 1 : 0) + {} + + NLOHMANN_VIEW_ALWAYS_INLINE View operator*() const noexcept + { + return View(m_doc, m_pos + m_value_offset); + } + + pointer operator->() const noexcept + { + return pointer(**this); + } + + NLOHMANN_VIEW_ALWAYS_INLINE view_iterator& operator++() noexcept + { + m_pos = document_data::after(m_pos + m_value_offset); + return *this; + } + + view_iterator operator++(int) noexcept + { + const view_iterator r = *this; + ++*this; + return r; + } + + friend bool operator==(const view_iterator& a, const view_iterator& b) noexcept + { + return a.m_pos == b.m_pos; + } + + friend bool operator!=(const view_iterator& a, const view_iterator& b) noexcept + { + return a.m_pos != b.m_pos; + } + + /// the key of the current object member; throws invalid_iterator.207 for + /// other iterators, like basic_json's iterators + string_view_t key() const + { + if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) + { + throw_invalid_iterator(207, "cannot use key() for non-object iterators"); + } + return string_view_t(m_doc->str(*m_pos), m_pos->len); + } + + View value() const noexcept + { + return **this; + } + + /// whether the iterator runs over the members of an object + bool is_object_iterator() const noexcept + { + return m_value_offset != 0; + } + + private: + const document_data* m_doc = nullptr; + const node* m_pos = nullptr; + std::size_t m_value_offset = 0; ///< 1 for objects: the value follows its key +}; + +/*! +@brief a (key, value) item of basic_json_view::items() + +The key of an array element is its index, as for basic_json::items(). +Supports structured bindings: for (const auto [key, value] : view.items()) +*/ +template +class view_item +{ + public: + using string_view_t = typename View::string_view_t; + using iterator = view_iterator; + + view_item(const iterator& it, std::size_t index) + : m_it(it) + { + if (!it.is_object_iterator()) + { + m_index = std::to_string(index); + } + } + + /// the member key, or the element index for arrays + string_view_t key() const + { + if (m_it.is_object_iterator()) + { + return m_it.key(); + } + return string_view_t(m_index.data(), m_index.size()); + } + + View value() const noexcept + { + return *m_it; + } + + template::type = 0> + string_view_t get() const + { + return key(); + } + + template::type = 0> + View get() const noexcept + { + return value(); + } + + private: + iterator m_it; + std::string m_index{}; // NOLINT(readability-redundant-member-init) +}; + +/// the range returned by basic_json_view::items() +template +class view_items +{ + public: + using item = view_item; + + class iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = item; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = item; + + explicit iterator(const view_iterator& it) noexcept + : m_it(it) + {} + + item operator*() const + { + return item(m_it, m_index); + } + + iterator& operator++() noexcept + { + ++m_it; + ++m_index; + return *this; + } + + iterator operator++(int) noexcept + { + const iterator r = *this; + ++*this; + return r; + } + + friend bool operator==(const iterator& a, const iterator& b) noexcept + { + return a.m_it == b.m_it; + } + + friend bool operator!=(const iterator& a, const iterator& b) noexcept + { + return a.m_it != b.m_it; + } + + private: + view_iterator m_it; + std::size_t m_index = 0; + }; + + explicit view_items(const View& v) noexcept + : m_view(v) + {} + + iterator begin() const noexcept + { + return iterator(m_view.begin()); + } + + iterator end() const noexcept + { + return iterator(m_view.end()); + } + + private: + View m_view; +}; + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/view/lookup.hpp b/include/nlohmann/detail/view/lookup.hpp new file mode 100644 index 000000000..71338be32 --- /dev/null +++ b/include/nlohmann/detail/view/lookup.hpp @@ -0,0 +1,139 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // size_t +#include // uint16_t, uint32_t, uint64_t +#include // memcmp, memcpy + +#include +#include +#include +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// equality test for strings of one length n <= 16: two overlapping loads per +/// string (the first and the last 8, 4, or 2 bytes) replace a memcmp, and no +/// byte outside [s, s + n) is read +class short_key +{ + public: + short_key(const unsigned char* k, std::size_t n) noexcept + : m_n(n) + { + load(k, m_a, m_b); + } + + NLOHMANN_VIEW_ALWAYS_INLINE bool matches(const unsigned char* s) const noexcept + { + std::uint64_t a = 0; + std::uint64_t b = 0; + load(s, a, b); + return a == m_a && b == m_b; + } + + private: + template + static NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t load_word(const unsigned char* s) noexcept + { + T w = 0; + std::memcpy(&w, s, sizeof(T)); + return w; + } + + NLOHMANN_VIEW_ALWAYS_INLINE void load(const unsigned char* s, std::uint64_t& a, std::uint64_t& b) const noexcept + { + if (m_n >= 8) + { + a = load_word(s); + b = load_word(s + m_n - 8); + } + else if (m_n >= 4) + { + a = load_word(s); + b = load_word(s + m_n - 4); + } + else if (m_n >= 2) + { + a = load_word(s); + b = load_word(s + m_n - 2); + } + else + { + a = m_n == 1 ? s[0] : 0; + b = 0; + } + } + + std::size_t m_n; + std::uint64_t m_a = 0; + std::uint64_t m_b = 0; +}; + +/// the key node of the first member of an object with the given key, or +/// nullptr; most keys are rejected by their length, from the index alone +inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept +{ + const node* const end = document_data::child_end(object); + const auto* const k = reinterpret_cast(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + if (NLOHMANN_VIEW_LIKELY(n <= 16)) + { + const short_key probe(k, n); + for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) + { + if (m->len == n && probe.matches(reinterpret_cast(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + { + return m; + } + } + return nullptr; + } + for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) + { + if (m->len == n && std::memcmp(d.str(*m), key, n) == 0) + { + return m; + } + } + return nullptr; +} + +/// the element of an array at an index below its size +inline const node* element_at(const node* array, std::size_t idx) noexcept +{ + const node* e = document_data::first_child(array); + for (std::size_t i = 0; i < idx; ++i) + { + e = document_data::after(e); + } + return e; +} + +/// the last element of a non-empty array, or the key of the last member of a +/// non-empty object +inline const node* last_child(const node* container) noexcept +{ + const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const node* const end = document_data::child_end(container); + const node* last = document_data::first_child(container); + for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset)) + { + last = c; + } + return last; +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/view/pointer.hpp b/include/nlohmann/detail/view/pointer.hpp new file mode 100644 index 000000000..d2ac497d7 --- /dev/null +++ b/include/nlohmann/detail/view/pointer.hpp @@ -0,0 +1,171 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // size_t +#include // uint64_t +#include // numeric_limits +#include // string, to_string + +#include +#include +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// what resolving a JSON pointer does where it cannot continue +enum class pointer_mode +{ + unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined + checked, ///< as basic_json::at(): out_of_range.401/403 + value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used) + contains, ///< as basic_json::contains(): no exceptions at all +}; + +/// the outcome of reading an array index from a reference token +enum class index_status +{ + ok, + leading_zero, ///< parse_error.106 + not_number, ///< parse_error.109 + unresolved, ///< out_of_range.404 + too_large, ///< out_of_range.410 +}; + +/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4), +/// but reports errors instead of throwing them +template +index_status array_index(const StringType& s, std::size_t& idx) noexcept +{ + if (s.size() > 1 && s[0] == '0') + { + return index_status::leading_zero; + } + if (s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')) + { + return index_status::not_number; + } + if (s.empty()) + { + return index_status::unresolved; + } + std::uint64_t v = 0; + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto d = static_cast(static_cast(s[i])) - '0'; + if (d > 9 || v > ((std::numeric_limits::max)() - d) / 10) + { + return index_status::unresolved; // not a number, or beyond unsigned long long + } + v = (v * 10) + d; + } + if (v >= (std::numeric_limits::max)()) // (std::size_t converts to std::uint64_t implicitly) + { + return index_status::too_large; + } + idx = static_cast(v); + return index_status::ok; +} + +/// throws the exception json_pointer::array_index throws for this status +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s) +{ + switch (status) + { + case index_status::leading_zero: + throw_parse_error(106, concat("array index '", s, "' must not begin with '0'")); + case index_status::not_number: + throw_parse_error(109, concat("array index '", s, "' is not a number")); + case index_status::too_large: + throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE + case index_status::unresolved: + case index_status::ok: + default: + throw_out_of_range(404, concat("unresolved reference token '", s, "'")); + } +} + +/*! +@brief resolve the reference tokens of a JSON pointer, starting at a view + +The exceptions are those basic_json throws for the same pointer; where +basic_json's behavior is undefined (a missing key or an index out of range +with const operator[]), the result is a discarded view. +*/ +template +View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) +{ + using string_view_t = typename View::string_view_t; + const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked; + for (const auto& token : tokens) + { + if (cur.is_object()) + { + const auto it = cur.find(string_view_t(token.data(), token.size())); + if (it == cur.end()) + { + if (mode == pointer_mode::checked) + { + throw_out_of_range(403, concat("key '", token, "' not found")); + } + return View(); + } + cur = *it; + } + else if (cur.is_array()) + { + if (token.size() == 1 && token[0] == '-') + { + if (throwing) + { + throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range")); + } + return View(); + } + std::size_t idx = 0; + const index_status status = array_index(token, idx); + if (status != index_status::ok) + { + const bool parse_error = status == index_status::leading_zero || status == index_status::not_number; + if (throwing || (mode == pointer_mode::value && parse_error)) + { + throw_array_index_error(status, token); + } + return View(); + } + if (idx >= cur.size()) + { + if (mode == pointer_mode::checked) + { + throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range")); + } + return View(); + } + cur = cur[idx]; + } + else + { + if (throwing) + { + throw_out_of_range(404, concat("unresolved reference token '", token, "'")); + } + return View(); + } + } + return cur; +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/view/value.hpp b/include/nlohmann/detail/view/value.hpp new file mode 100644 index 000000000..b43a9c4df --- /dev/null +++ b/include/nlohmann/detail/view/value.hpp @@ -0,0 +1,108 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // int64_t +#include // map +#include // basic_string +#include // enable_if, is_constructible +#include // unordered_map +#include // vector + +#include +#include +#include +#include +#include +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// selects a conversion by its target type +template +struct value_tag {}; + +/*! +@brief the number or boolean of a node converted to an arithmetic type + +As basic_json's get() for arithmetic types: integers and floats are +converted with static_cast, booleans give 0 or 1, and other types throw +type_error.302. +*/ +template +NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n) +{ + switch (static_cast(n.kind)) + { + case value_t::number_unsigned: + return static_cast(static_cast(integer_bits(n))); + case value_t::number_integer: + return static_cast(static_cast(static_cast(integer_bits(n)))); + case value_t::number_float: + return static_cast(float_value(d.str(n), n)); + case value_t::boolean: + return static_cast((n.flags & node_flags::is_true) != 0); + 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: + throw_type_error(302, "type must be number, but is ", value_type_name(static_cast(n.kind))); + } +} + +/// std::vector from an array, element by element (type_error.302 otherwise) +template +std::vector vector_value(const View& v) +{ + if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) + { + throw_type_error(302, "type must be array, but is ", v.type_name()); + } + std::vector r; + r.reserve(v.size()); + for (const View e : v) + { + r.push_back(e.template get()); + } + return r; +} + +/// a map with string keys from an object; with duplicate keys, the last +/// value is kept, as parse() does (type_error.302 for other types) +template +Map map_value(const View& v) +{ + if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) + { + throw_type_error(302, "type must be object, but is ", v.type_name()); + } + Map r; + for (auto it = v.begin(); it != v.end(); ++it) + { + const auto key = it.key(); + r[typename Map::key_type(key.data(), key.size())] = it.value().template get(); + } + return r; +} + +/// whether a map type is read member by member (its keys are made from +/// characters and a length); other maps go through basic_json +template +struct is_string_key : std::is_constructible {}; + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 2f5da27ba..a84f35e5a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -5660,29 +5660,7 @@ public: JSON_HEDLEY_RETURNS_NON_NULL const char* type_name() const noexcept { - switch (m_data.m_type) - { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::binary: - return "binary"; - case value_t::discarded: - return "discarded"; - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - return "number"; - default: - return "invalid"; - } + return detail::value_type_name(m_data.m_type); } JSON_PRIVATE_UNLESS_TESTED: diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 5aec36ef9..b51ff446b 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -27,10 +27,14 @@ #include // size_t #include // memcpy, strlen #include // distance, input_iterator_tag, iterator_traits +#include // map #include // unique_ptr #include // string -#include // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent +#include // tuple_element, tuple_size +#include // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent +#include // unordered_map #include // forward, move +#include // vector #include @@ -43,10 +47,14 @@ #include #include #include +#include +#include #include #include #include +#include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN @@ -75,6 +83,11 @@ class basic_json_view using size_type = std::size_t; /// std::string_view from C++17 on using string_view_t = detail::view::string_ref; + /// forward iterator over elements (arrays) or member values (objects) + using iterator = detail::view::view_iterator; + using const_iterator = iterator; + /// a (key, value) item of items() + using item = detail::view::view_item; /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; @@ -162,6 +175,12 @@ class basic_json_view return m_node != nullptr; } + /// the name of the type, as basic_json::type_name() + const char* type_name() const noexcept + { + return detail::value_type_name(type()); + } + ////////////// // capacity // ////////////// @@ -211,6 +230,323 @@ class basic_json_view } } + //////////////////// + // element access // + //////////////////// + + /// the value of the member with this key (the first one, should the key + /// occur more than once); a discarded view if there is none. Throws + /// type_error.305 if this is not an object. + NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); + } + return lookup(key); + } + + basic_json_view operator[](const char* key) const + { + return operator[](string_view_t(key)); + } + + basic_json_view operator[](const string_t& key) const + { + return operator[](string_view_t(key.data(), key.size())); + } + + /// the element at this index; a discarded view if the index is out of + /// range. Throws type_error.305 if this is not an array. + basic_json_view operator[](size_type idx) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_array())) + { + detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); + } + return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view(); + } + + /// (an int argument would be ambiguous between size_type and const char*) + basic_json_view operator[](int idx) const + { + return operator[](static_cast(idx)); + } + + /// the value a JSON pointer refers to; a discarded view if a key is + /// missing or an index is out of range. Other errors throw what const + /// basic_json::operator[] throws. + basic_json_view operator[](const json_pointer& ptr) const + { + return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked); + } + + /// the value of the member with this key (the first one, should the key + /// occur more than once). Throws type_error.304 if this is not an object, + /// and out_of_range.403 if there is no such member. + basic_json_view at(string_view_t key) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + } + const basic_json_view r = lookup(key); + if (NLOHMANN_VIEW_UNLIKELY(!r)) + { + detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found")); + } + return r; + } + + basic_json_view at(const char* key) const + { + return at(string_view_t(key)); + } + + basic_json_view at(const string_t& key) const + { + return at(string_view_t(key.data(), key.size())); + } + + /// the element at this index. Throws type_error.304 if this is not an + /// array, and out_of_range.401 if the index is out of range. + basic_json_view at(size_type idx) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_array())) + { + detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + } + if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) + { + detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); + } + return basic_json_view(m_doc, detail::view::element_at(m_node, idx)); + } + + basic_json_view at(int idx) const + { + return at(static_cast(idx)); + } + + /// the value a JSON pointer refers to; throws what basic_json::at() + /// throws if it cannot be resolved + basic_json_view at(const json_pointer& ptr) const + { + return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::checked); + } + + /// the member with this key converted to T, or the default value if there + /// is no such member (the first one, should the key occur more than + /// once). Throws type_error.306 if this is not an object. + template < typename T, typename std::enable_if < !std::is_same::type, const char*>::value, int >::type = 0 > + T value(string_view_t key, const T& default_value) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(306, "cannot use value() with ", type_name()); + } + const basic_json_view r = lookup(key); + return r ? r.template get() : default_value; + } + + string_t value(string_view_t key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /// the value a JSON pointer refers to converted to T, or the default + /// value if the pointer cannot be resolved. Throws type_error.306 if this + /// is neither an object nor an array. + template < typename T, typename std::enable_if < !std::is_same::type, const char*>::value, int >::type = 0 > + T value(const json_pointer& ptr, const T& default_value) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) + { + detail::view::throw_type_error(306, "cannot use value() with ", type_name()); + } + const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); + return r ? r.template get() : default_value; + } + + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /// the first element or member value; a primitive value itself. Throws + /// invalid_iterator.214 for null, discarded views, and empty containers. + basic_json_view front() const + { + const iterator it = begin(); + if (NLOHMANN_VIEW_UNLIKELY(it == end())) + { + detail::view::throw_invalid_iterator(214, "cannot get value"); + } + return *it; + } + + /// the last element or member value (linear in the size); a primitive + /// value itself. Throws invalid_iterator.214 for null, discarded views, + /// and empty containers. + basic_json_view back() const + { + if (is_structured() && m_node->len != 0) + { + return basic_json_view(m_doc, detail::view::last_child(m_node) + (is_object() ? 1 : 0)); + } + return front(); + } + + //////////// + // lookup // + //////////// + + /// an iterator to the member with this key (the first one, should the + /// key occur more than once), or end(); end() also for non-objects + iterator find(string_view_t key) const + { + if (!is_object()) + { + return end(); + } + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + return k != nullptr ? iterator(m_doc, k, true) : end(); + } + + iterator find(const char* key) const + { + return find(string_view_t(key)); + } + + iterator find(const string_t& key) const + { + return find(string_view_t(key.data(), key.size())); + } + + /// whether this is an object with a member with this key + bool contains(string_view_t key) const + { + return is_object() && detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr; + } + + bool contains(const char* key) const + { + return contains(string_view_t(key)); + } + + bool contains(const string_t& key) const + { + return contains(string_view_t(key.data(), key.size())); + } + + /// whether a JSON pointer can be resolved (never throws, as + /// basic_json::contains()) + bool contains(const json_pointer& ptr) const + { + return static_cast(detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains)); + } + + /// 1 if this is an object with a member with this key, else 0 (duplicate + /// keys count once) + size_type count(string_view_t key) const + { + return contains(key) ? 1 : 0; + } + + size_type count(const char* key) const + { + return count(string_view_t(key)); + } + + size_type count(const string_t& key) const + { + return count(string_view_t(key.data(), key.size())); + } + + /////////////// + // iteration // + /////////////// + + /// the first element or member value, in document order; a primitive + /// value is a range of one element (itself), null an empty range + NLOHMANN_VIEW_ALWAYS_INLINE iterator begin() const noexcept + { + if (NLOHMANN_VIEW_LIKELY(is_structured())) + { + return iterator(m_doc, document_data::first_child(m_node), is_object()); + } + return iterator(m_doc, m_node, false); + } + + NLOHMANN_VIEW_ALWAYS_INLINE iterator end() const noexcept + { + if (NLOHMANN_VIEW_LIKELY(is_structured())) + { + return iterator(m_doc, document_data::child_end(m_node), is_object()); + } + return iterator(m_doc, (is_null() || is_discarded()) ? m_node : m_node + 1, false); + } + + iterator cbegin() const noexcept + { + return begin(); + } + + iterator cend() const noexcept + { + return end(); + } + + /// (key, value) items; the key of an array element is its index + detail::view::view_items items() const noexcept + { + return detail::view::view_items(*this); + } + + //////////////// + // conversion // + //////////////// + + /// the value converted to T, as BasicJsonType::get(): arithmetic types, + /// strings (string_view_t without a copy), std::nullptr_t, std::vector, + /// maps with string keys, and views are converted directly; other types + /// through materialize().get() + template + NLOHMANN_VIEW_ALWAYS_INLINE T get() const + { + return get_impl(detail::view::value_tag {}, detail::priority_tag<2> {}); + } + + template + T& get_to(T& v) const + { + v = get(); + return v; + } + + /// the string, without a copy; valid as long as the view is. Throws + /// type_error.302 for other types. + string_view_t get_string() const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_string())) + { + detail::view::throw_type_error(302, "type must be string, but is ", type_name()); + } + return {m_doc->str(*m_node), m_node->len}; + } + + /// the text of a number as it appears in the source (e.g. "1.50", "1E2", + /// or an integer with more digits than any number type holds). Throws + /// type_error.302 for other types. + string_view_t number_token() const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_number())) + { + detail::view::throw_type_error(302, "type must be number, but is ", type_name()); + } + return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; + } + ///////////////// // materialize // ///////////////// @@ -237,11 +573,97 @@ class basic_json_view private: template friend class basic_json_document; + friend iterator; basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} + /// the value of the first member with this key, or a discarded view + /// (object required) + NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept + { + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); + } + + // --- get() dispatch --- + + bool get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) + { + detail::view::throw_type_error(302, "type must be boolean, but is ", type_name()); + } + return (m_node->flags & detail::view::node_flags::is_true) != 0; + } + + template < typename T, typename std::enable_if < std::is_arithmetic::value && !std::is_same::value, int >::type = 0 > + NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) + { + detail::view::throw_type_error(302, "type must be number, but is ", type_name()); + } + return detail::view::arithmetic_value(*m_doc, *m_node); + } + + std::nullptr_t get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_null())) + { + detail::view::throw_type_error(302, "type must be null, but is ", type_name()); + } + return nullptr; + } + + string_view_t get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return get_string(); + } + + template + std::basic_string get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + { + const string_view_t s = get_string(); + return std::basic_string(s.data(), s.size()); + } + + BasicJsonType get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return materialize(); + } + + basic_json_view get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const noexcept + { + return *this; + } + + template + std::vector get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return detail::view::vector_value(*this); + } + + template::value, int>::type = 0> + std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return detail::view::map_value>(*this); + } + + template::value, int>::type = 0> + std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return detail::view::map_value>(*this); + } + + /// everything else through the BasicJsonType value (from_json included) + template + T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<0> /*unused*/) const + { + return materialize().template get(); + } + const document_data* m_doc = nullptr; const node* m_node = nullptr; }; @@ -590,6 +1012,31 @@ using ordered_json_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END +// tuple protocol for the items of basic_json_view::items() (structured bindings) +namespace std // NOLINT(cert-dcl58-cpp) +{ + +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template +class tuple_size<::nlohmann::detail::view::view_item> // NOLINT(cert-dcl58-cpp) + : public std::integral_constant {}; + +template +class tuple_element> // NOLINT(cert-dcl58-cpp) +{ + public: + using type = decltype(std::declval<::nlohmann::detail::view::view_item>().template get()); +}; +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + +} // namespace std + #include #endif // INCLUDE_NLOHMANN_JSON_VIEW_HPP_ diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9a457d532..c16627150 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3283,6 +3283,39 @@ enum class value_t : std::uint8_t discarded ///< discarded by the parser callback function }; +/*! +@brief the name of a JSON type, as returned by basic_json::type_name() + +Used in exception messages; also by code that reports types without a +basic_json value at hand (such as the zero-copy view). +*/ +inline const char* value_type_name(const value_t t) noexcept +{ + switch (t) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::binary: + return "binary"; + case value_t::discarded: + return "discarded"; + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + return "number"; + default: + return "invalid"; + } +} + /*! @brief comparison operator for JSON types @@ -20432,6 +20465,11 @@ NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +struct json_pointer_access; +} // namespace detail + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document /// @sa https://json.nlohmann.me/api/json_pointer/ template @@ -20444,6 +20482,8 @@ class json_pointer template friend class json_pointer; + friend struct detail::json_pointer_access; + template struct string_t_helper { @@ -21573,6 +21613,20 @@ inline bool operator<(const json_pointer& lhs, } #endif +namespace detail +{ +/// the reference tokens of a json_pointer, for code that resolves pointers +/// without a basic_json value (such as the zero-copy view) +struct json_pointer_access +{ + template + static const std::vector::string_t>& reference_tokens(const json_pointer& ptr) noexcept + { + return ptr.reference_tokens; + } +}; +} // namespace detail + NLOHMANN_JSON_NAMESPACE_END // #include @@ -34098,29 +34152,7 @@ public: JSON_HEDLEY_RETURNS_NON_NULL const char* type_name() const noexcept { - switch (m_data.m_type) - { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::binary: - return "binary"; - case value_t::discarded: - return "discarded"; - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - return "number"; - default: - return "invalid"; - } + return detail::value_type_name(m_data.m_type); } JSON_PRIVATE_UNLESS_TESTED: diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index b853fcd82..aefe84c6a 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -27,10 +27,14 @@ #include // size_t #include // memcpy, strlen #include // distance, input_iterator_tag, iterator_traits +#include // map #include // unique_ptr #include // string -#include // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent +#include // tuple_element, tuple_size +#include // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent +#include // unordered_map #include // forward, move +#include // vector #include @@ -1618,6 +1622,12 @@ namespace view NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); } +/// a parse error without a position (as those of json_pointer) +[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg) +{ + NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr)); +} + /*! @brief throw the exception BasicJsonType::parse would throw for this input @@ -1753,6 +1763,419 @@ std::string collect_adapter(Adapter ia) } // namespace detail NLOHMANN_JSON_NAMESPACE_END +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // ptrdiff_t, size_t +#include // forward_iterator_tag +#include // string, to_string +#include // enable_if + +// #include +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// the result of view_iterator::operator->: keeps the view alive for the +/// duration of the member access +template +class arrow_proxy +{ + public: + explicit arrow_proxy(const View& v) noexcept + : m_view(v) + {} + + const View* operator->() const noexcept + { + return &m_view; + } + + private: + View m_view; +}; + +/*! +@brief forward iterator over the elements of a basic_json_view + +Iterates over the elements of an array or the member values of an object, in +document order; key() gives the key of an object member. As for basic_json, a +primitive value iterates as a range of one element (itself), and null as an +empty range. +*/ +template +class view_iterator +{ + public: + using iterator_category = std::forward_iterator_tag; + using value_type = View; + using difference_type = std::ptrdiff_t; + using pointer = arrow_proxy; + using reference = View; + using string_view_t = typename View::string_view_t; + + view_iterator() noexcept = default; + + /// @param[in] pos the element, or the key of the member + /// @param[in] object whether pos is a key (its value is the next node) + view_iterator(const document_data* d, const node* pos, bool object) noexcept + : m_doc(d), m_pos(pos), m_value_offset(object ? 1 : 0) + {} + + NLOHMANN_VIEW_ALWAYS_INLINE View operator*() const noexcept + { + return View(m_doc, m_pos + m_value_offset); + } + + pointer operator->() const noexcept + { + return pointer(**this); + } + + NLOHMANN_VIEW_ALWAYS_INLINE view_iterator& operator++() noexcept + { + m_pos = document_data::after(m_pos + m_value_offset); + return *this; + } + + view_iterator operator++(int) noexcept + { + const view_iterator r = *this; + ++*this; + return r; + } + + friend bool operator==(const view_iterator& a, const view_iterator& b) noexcept + { + return a.m_pos == b.m_pos; + } + + friend bool operator!=(const view_iterator& a, const view_iterator& b) noexcept + { + return a.m_pos != b.m_pos; + } + + /// the key of the current object member; throws invalid_iterator.207 for + /// other iterators, like basic_json's iterators + string_view_t key() const + { + if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) + { + throw_invalid_iterator(207, "cannot use key() for non-object iterators"); + } + return string_view_t(m_doc->str(*m_pos), m_pos->len); + } + + View value() const noexcept + { + return **this; + } + + /// whether the iterator runs over the members of an object + bool is_object_iterator() const noexcept + { + return m_value_offset != 0; + } + + private: + const document_data* m_doc = nullptr; + const node* m_pos = nullptr; + std::size_t m_value_offset = 0; ///< 1 for objects: the value follows its key +}; + +/*! +@brief a (key, value) item of basic_json_view::items() + +The key of an array element is its index, as for basic_json::items(). +Supports structured bindings: for (const auto [key, value] : view.items()) +*/ +template +class view_item +{ + public: + using string_view_t = typename View::string_view_t; + using iterator = view_iterator; + + view_item(const iterator& it, std::size_t index) + : m_it(it) + { + if (!it.is_object_iterator()) + { + m_index = std::to_string(index); + } + } + + /// the member key, or the element index for arrays + string_view_t key() const + { + if (m_it.is_object_iterator()) + { + return m_it.key(); + } + return string_view_t(m_index.data(), m_index.size()); + } + + View value() const noexcept + { + return *m_it; + } + + template::type = 0> + string_view_t get() const + { + return key(); + } + + template::type = 0> + View get() const noexcept + { + return value(); + } + + private: + iterator m_it; + std::string m_index{}; // NOLINT(readability-redundant-member-init) +}; + +/// the range returned by basic_json_view::items() +template +class view_items +{ + public: + using item = view_item; + + class iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = item; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = item; + + explicit iterator(const view_iterator& it) noexcept + : m_it(it) + {} + + item operator*() const + { + return item(m_it, m_index); + } + + iterator& operator++() noexcept + { + ++m_it; + ++m_index; + return *this; + } + + iterator operator++(int) noexcept + { + const iterator r = *this; + ++*this; + return r; + } + + friend bool operator==(const iterator& a, const iterator& b) noexcept + { + return a.m_it == b.m_it; + } + + friend bool operator!=(const iterator& a, const iterator& b) noexcept + { + return a.m_it != b.m_it; + } + + private: + view_iterator m_it; + std::size_t m_index = 0; + }; + + explicit view_items(const View& v) noexcept + : m_view(v) + {} + + iterator begin() const noexcept + { + return iterator(m_view.begin()); + } + + iterator end() const noexcept + { + return iterator(m_view.end()); + } + + private: + View m_view; +}; + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // uint16_t, uint32_t, uint64_t +#include // memcmp, memcpy + +// #include +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// equality test for strings of one length n <= 16: two overlapping loads per +/// string (the first and the last 8, 4, or 2 bytes) replace a memcmp, and no +/// byte outside [s, s + n) is read +class short_key +{ + public: + short_key(const unsigned char* k, std::size_t n) noexcept + : m_n(n) + { + load(k, m_a, m_b); + } + + NLOHMANN_VIEW_ALWAYS_INLINE bool matches(const unsigned char* s) const noexcept + { + std::uint64_t a = 0; + std::uint64_t b = 0; + load(s, a, b); + return a == m_a && b == m_b; + } + + private: + template + static NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t load_word(const unsigned char* s) noexcept + { + T w = 0; + std::memcpy(&w, s, sizeof(T)); + return w; + } + + NLOHMANN_VIEW_ALWAYS_INLINE void load(const unsigned char* s, std::uint64_t& a, std::uint64_t& b) const noexcept + { + if (m_n >= 8) + { + a = load_word(s); + b = load_word(s + m_n - 8); + } + else if (m_n >= 4) + { + a = load_word(s); + b = load_word(s + m_n - 4); + } + else if (m_n >= 2) + { + a = load_word(s); + b = load_word(s + m_n - 2); + } + else + { + a = m_n == 1 ? s[0] : 0; + b = 0; + } + } + + std::size_t m_n; + std::uint64_t m_a = 0; + std::uint64_t m_b = 0; +}; + +/// the key node of the first member of an object with the given key, or +/// nullptr; most keys are rejected by their length, from the index alone +inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept +{ + const node* const end = document_data::child_end(object); + const auto* const k = reinterpret_cast(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + if (NLOHMANN_VIEW_LIKELY(n <= 16)) + { + const short_key probe(k, n); + for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) + { + if (m->len == n && probe.matches(reinterpret_cast(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + { + return m; + } + } + return nullptr; + } + for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) + { + if (m->len == n && std::memcmp(d.str(*m), key, n) == 0) + { + return m; + } + } + return nullptr; +} + +/// the element of an array at an index below its size +inline const node* element_at(const node* array, std::size_t idx) noexcept +{ + const node* e = document_data::first_child(array); + for (std::size_t i = 0; i < idx; ++i) + { + e = document_data::after(e); + } + return e; +} + +/// the last element of a non-empty array, or the key of the last member of a +/// non-empty object +inline const node* last_child(const node* container) noexcept +{ + const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const node* const end = document_data::child_end(container); + const node* last = document_data::first_child(container); + for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset)) + { + last = c; + } + return last; +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include // #include @@ -1963,6 +2386,181 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // uint64_t +#include // numeric_limits +#include // string, to_string + +// #include +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// what resolving a JSON pointer does where it cannot continue +enum class pointer_mode +{ + unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined + checked, ///< as basic_json::at(): out_of_range.401/403 + value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used) + contains, ///< as basic_json::contains(): no exceptions at all +}; + +/// the outcome of reading an array index from a reference token +enum class index_status +{ + ok, + leading_zero, ///< parse_error.106 + not_number, ///< parse_error.109 + unresolved, ///< out_of_range.404 + too_large, ///< out_of_range.410 +}; + +/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4), +/// but reports errors instead of throwing them +template +index_status array_index(const StringType& s, std::size_t& idx) noexcept +{ + if (s.size() > 1 && s[0] == '0') + { + return index_status::leading_zero; + } + if (s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')) + { + return index_status::not_number; + } + if (s.empty()) + { + return index_status::unresolved; + } + std::uint64_t v = 0; + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto d = static_cast(static_cast(s[i])) - '0'; + if (d > 9 || v > ((std::numeric_limits::max)() - d) / 10) + { + return index_status::unresolved; // not a number, or beyond unsigned long long + } + v = (v * 10) + d; + } + if (v >= (std::numeric_limits::max)()) // (std::size_t converts to std::uint64_t implicitly) + { + return index_status::too_large; + } + idx = static_cast(v); + return index_status::ok; +} + +/// throws the exception json_pointer::array_index throws for this status +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s) +{ + switch (status) + { + case index_status::leading_zero: + throw_parse_error(106, concat("array index '", s, "' must not begin with '0'")); + case index_status::not_number: + throw_parse_error(109, concat("array index '", s, "' is not a number")); + case index_status::too_large: + throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE + case index_status::unresolved: + case index_status::ok: + default: + throw_out_of_range(404, concat("unresolved reference token '", s, "'")); + } +} + +/*! +@brief resolve the reference tokens of a JSON pointer, starting at a view + +The exceptions are those basic_json throws for the same pointer; where +basic_json's behavior is undefined (a missing key or an index out of range +with const operator[]), the result is a discarded view. +*/ +template +View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) +{ + using string_view_t = typename View::string_view_t; + const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked; + for (const auto& token : tokens) + { + if (cur.is_object()) + { + const auto it = cur.find(string_view_t(token.data(), token.size())); + if (it == cur.end()) + { + if (mode == pointer_mode::checked) + { + throw_out_of_range(403, concat("key '", token, "' not found")); + } + return View(); + } + cur = *it; + } + else if (cur.is_array()) + { + if (token.size() == 1 && token[0] == '-') + { + if (throwing) + { + throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range")); + } + return View(); + } + std::size_t idx = 0; + const index_status status = array_index(token, idx); + if (status != index_status::ok) + { + const bool parse_error = status == index_status::leading_zero || status == index_status::not_number; + if (throwing || (mode == pointer_mode::value && parse_error)) + { + throw_array_index_error(status, token); + } + return View(); + } + if (idx >= cur.size()) + { + if (mode == pointer_mode::checked) + { + throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range")); + } + return View(); + } + cur = cur[idx]; + } + else + { + if (throwing) + { + throw_out_of_range(404, concat("unresolved reference token '", token, "'")); + } + return View(); + } + } + return cur; +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -2079,6 +2677,121 @@ class string_ref } // namespace detail NLOHMANN_JSON_NAMESPACE_END +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // int64_t +#include // map +#include // basic_string +#include // enable_if, is_constructible +#include // unordered_map +#include // vector + +// #include +// #include + +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/// selects a conversion by its target type +template +struct value_tag {}; + +/*! +@brief the number or boolean of a node converted to an arithmetic type + +As basic_json's get() for arithmetic types: integers and floats are +converted with static_cast, booleans give 0 or 1, and other types throw +type_error.302. +*/ +template +NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n) +{ + switch (static_cast(n.kind)) + { + case value_t::number_unsigned: + return static_cast(static_cast(integer_bits(n))); + case value_t::number_integer: + return static_cast(static_cast(static_cast(integer_bits(n)))); + case value_t::number_float: + return static_cast(float_value(d.str(n), n)); + case value_t::boolean: + return static_cast((n.flags & node_flags::is_true) != 0); + 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: + throw_type_error(302, "type must be number, but is ", value_type_name(static_cast(n.kind))); + } +} + +/// std::vector from an array, element by element (type_error.302 otherwise) +template +std::vector vector_value(const View& v) +{ + if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) + { + throw_type_error(302, "type must be array, but is ", v.type_name()); + } + std::vector r; + r.reserve(v.size()); + for (const View e : v) + { + r.push_back(e.template get()); + } + return r; +} + +/// a map with string keys from an object; with duplicate keys, the last +/// value is kept, as parse() does (type_error.302 for other types) +template +Map map_value(const View& v) +{ + if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) + { + throw_type_error(302, "type must be object, but is ", v.type_name()); + } + Map r; + for (auto it = v.begin(); it != v.end(); ++it) + { + const auto key = it.key(); + r[typename Map::key_type(key.data(), key.size())] = it.value().template get(); + } + return r; +} + +/// whether a map type is read member by member (its keys are made from +/// characters and a length); other maps go through basic_json +template +struct is_string_key : std::is_constructible {}; + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + NLOHMANN_JSON_NAMESPACE_BEGIN @@ -2107,6 +2820,11 @@ class basic_json_view using size_type = std::size_t; /// std::string_view from C++17 on using string_view_t = detail::view::string_ref; + /// forward iterator over elements (arrays) or member values (objects) + using iterator = detail::view::view_iterator; + using const_iterator = iterator; + /// a (key, value) item of items() + using item = detail::view::view_item; /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; @@ -2194,6 +2912,12 @@ class basic_json_view return m_node != nullptr; } + /// the name of the type, as basic_json::type_name() + const char* type_name() const noexcept + { + return detail::value_type_name(type()); + } + ////////////// // capacity // ////////////// @@ -2243,6 +2967,323 @@ class basic_json_view } } + //////////////////// + // element access // + //////////////////// + + /// the value of the member with this key (the first one, should the key + /// occur more than once); a discarded view if there is none. Throws + /// type_error.305 if this is not an object. + NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); + } + return lookup(key); + } + + basic_json_view operator[](const char* key) const + { + return operator[](string_view_t(key)); + } + + basic_json_view operator[](const string_t& key) const + { + return operator[](string_view_t(key.data(), key.size())); + } + + /// the element at this index; a discarded view if the index is out of + /// range. Throws type_error.305 if this is not an array. + basic_json_view operator[](size_type idx) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_array())) + { + detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); + } + return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view(); + } + + /// (an int argument would be ambiguous between size_type and const char*) + basic_json_view operator[](int idx) const + { + return operator[](static_cast(idx)); + } + + /// the value a JSON pointer refers to; a discarded view if a key is + /// missing or an index is out of range. Other errors throw what const + /// basic_json::operator[] throws. + basic_json_view operator[](const json_pointer& ptr) const + { + return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked); + } + + /// the value of the member with this key (the first one, should the key + /// occur more than once). Throws type_error.304 if this is not an object, + /// and out_of_range.403 if there is no such member. + basic_json_view at(string_view_t key) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + } + const basic_json_view r = lookup(key); + if (NLOHMANN_VIEW_UNLIKELY(!r)) + { + detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found")); + } + return r; + } + + basic_json_view at(const char* key) const + { + return at(string_view_t(key)); + } + + basic_json_view at(const string_t& key) const + { + return at(string_view_t(key.data(), key.size())); + } + + /// the element at this index. Throws type_error.304 if this is not an + /// array, and out_of_range.401 if the index is out of range. + basic_json_view at(size_type idx) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_array())) + { + detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + } + if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) + { + detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); + } + return basic_json_view(m_doc, detail::view::element_at(m_node, idx)); + } + + basic_json_view at(int idx) const + { + return at(static_cast(idx)); + } + + /// the value a JSON pointer refers to; throws what basic_json::at() + /// throws if it cannot be resolved + basic_json_view at(const json_pointer& ptr) const + { + return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::checked); + } + + /// the member with this key converted to T, or the default value if there + /// is no such member (the first one, should the key occur more than + /// once). Throws type_error.306 if this is not an object. + template < typename T, typename std::enable_if < !std::is_same::type, const char*>::value, int >::type = 0 > + T value(string_view_t key, const T& default_value) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_object())) + { + detail::view::throw_type_error(306, "cannot use value() with ", type_name()); + } + const basic_json_view r = lookup(key); + return r ? r.template get() : default_value; + } + + string_t value(string_view_t key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /// the value a JSON pointer refers to converted to T, or the default + /// value if the pointer cannot be resolved. Throws type_error.306 if this + /// is neither an object nor an array. + template < typename T, typename std::enable_if < !std::is_same::type, const char*>::value, int >::type = 0 > + T value(const json_pointer& ptr, const T& default_value) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) + { + detail::view::throw_type_error(306, "cannot use value() with ", type_name()); + } + const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); + return r ? r.template get() : default_value; + } + + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /// the first element or member value; a primitive value itself. Throws + /// invalid_iterator.214 for null, discarded views, and empty containers. + basic_json_view front() const + { + const iterator it = begin(); + if (NLOHMANN_VIEW_UNLIKELY(it == end())) + { + detail::view::throw_invalid_iterator(214, "cannot get value"); + } + return *it; + } + + /// the last element or member value (linear in the size); a primitive + /// value itself. Throws invalid_iterator.214 for null, discarded views, + /// and empty containers. + basic_json_view back() const + { + if (is_structured() && m_node->len != 0) + { + return basic_json_view(m_doc, detail::view::last_child(m_node) + (is_object() ? 1 : 0)); + } + return front(); + } + + //////////// + // lookup // + //////////// + + /// an iterator to the member with this key (the first one, should the + /// key occur more than once), or end(); end() also for non-objects + iterator find(string_view_t key) const + { + if (!is_object()) + { + return end(); + } + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + return k != nullptr ? iterator(m_doc, k, true) : end(); + } + + iterator find(const char* key) const + { + return find(string_view_t(key)); + } + + iterator find(const string_t& key) const + { + return find(string_view_t(key.data(), key.size())); + } + + /// whether this is an object with a member with this key + bool contains(string_view_t key) const + { + return is_object() && detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr; + } + + bool contains(const char* key) const + { + return contains(string_view_t(key)); + } + + bool contains(const string_t& key) const + { + return contains(string_view_t(key.data(), key.size())); + } + + /// whether a JSON pointer can be resolved (never throws, as + /// basic_json::contains()) + bool contains(const json_pointer& ptr) const + { + return static_cast(detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains)); + } + + /// 1 if this is an object with a member with this key, else 0 (duplicate + /// keys count once) + size_type count(string_view_t key) const + { + return contains(key) ? 1 : 0; + } + + size_type count(const char* key) const + { + return count(string_view_t(key)); + } + + size_type count(const string_t& key) const + { + return count(string_view_t(key.data(), key.size())); + } + + /////////////// + // iteration // + /////////////// + + /// the first element or member value, in document order; a primitive + /// value is a range of one element (itself), null an empty range + NLOHMANN_VIEW_ALWAYS_INLINE iterator begin() const noexcept + { + if (NLOHMANN_VIEW_LIKELY(is_structured())) + { + return iterator(m_doc, document_data::first_child(m_node), is_object()); + } + return iterator(m_doc, m_node, false); + } + + NLOHMANN_VIEW_ALWAYS_INLINE iterator end() const noexcept + { + if (NLOHMANN_VIEW_LIKELY(is_structured())) + { + return iterator(m_doc, document_data::child_end(m_node), is_object()); + } + return iterator(m_doc, (is_null() || is_discarded()) ? m_node : m_node + 1, false); + } + + iterator cbegin() const noexcept + { + return begin(); + } + + iterator cend() const noexcept + { + return end(); + } + + /// (key, value) items; the key of an array element is its index + detail::view::view_items items() const noexcept + { + return detail::view::view_items(*this); + } + + //////////////// + // conversion // + //////////////// + + /// the value converted to T, as BasicJsonType::get(): arithmetic types, + /// strings (string_view_t without a copy), std::nullptr_t, std::vector, + /// maps with string keys, and views are converted directly; other types + /// through materialize().get() + template + NLOHMANN_VIEW_ALWAYS_INLINE T get() const + { + return get_impl(detail::view::value_tag {}, detail::priority_tag<2> {}); + } + + template + T& get_to(T& v) const + { + v = get(); + return v; + } + + /// the string, without a copy; valid as long as the view is. Throws + /// type_error.302 for other types. + string_view_t get_string() const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_string())) + { + detail::view::throw_type_error(302, "type must be string, but is ", type_name()); + } + return {m_doc->str(*m_node), m_node->len}; + } + + /// the text of a number as it appears in the source (e.g. "1.50", "1E2", + /// or an integer with more digits than any number type holds). Throws + /// type_error.302 for other types. + string_view_t number_token() const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_number())) + { + detail::view::throw_type_error(302, "type must be number, but is ", type_name()); + } + return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; + } + ///////////////// // materialize // ///////////////// @@ -2269,11 +3310,97 @@ class basic_json_view private: template friend class basic_json_document; + friend iterator; basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} + /// the value of the first member with this key, or a discarded view + /// (object required) + NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept + { + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); + } + + // --- get() dispatch --- + + bool get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) + { + detail::view::throw_type_error(302, "type must be boolean, but is ", type_name()); + } + return (m_node->flags & detail::view::node_flags::is_true) != 0; + } + + template < typename T, typename std::enable_if < std::is_arithmetic::value && !std::is_same::value, int >::type = 0 > + NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) + { + detail::view::throw_type_error(302, "type must be number, but is ", type_name()); + } + return detail::view::arithmetic_value(*m_doc, *m_node); + } + + std::nullptr_t get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + if (NLOHMANN_VIEW_UNLIKELY(!is_null())) + { + detail::view::throw_type_error(302, "type must be null, but is ", type_name()); + } + return nullptr; + } + + string_view_t get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return get_string(); + } + + template + std::basic_string get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + { + const string_view_t s = get_string(); + return std::basic_string(s.data(), s.size()); + } + + BasicJsonType get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return materialize(); + } + + basic_json_view get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<2> /*unused*/) const noexcept + { + return *this; + } + + template + std::vector get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return detail::view::vector_value(*this); + } + + template::value, int>::type = 0> + std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return detail::view::map_value>(*this); + } + + template::value, int>::type = 0> + std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + { + return detail::view::map_value>(*this); + } + + /// everything else through the BasicJsonType value (from_json included) + template + T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<0> /*unused*/) const + { + return materialize().template get(); + } + const document_data* m_doc = nullptr; const node* m_node = nullptr; }; @@ -2622,6 +3749,31 @@ using ordered_json_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END +// tuple protocol for the items of basic_json_view::items() (structured bindings) +namespace std // NOLINT(cert-dcl58-cpp) +{ + +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template +class tuple_size<::nlohmann::detail::view::view_item> // NOLINT(cert-dcl58-cpp) + : public std::integral_constant {}; + +template +class tuple_element> // NOLINT(cert-dcl58-cpp) +{ + public: + using type = decltype(std::declval<::nlohmann::detail::view::view_item>().template get()); +}; +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + +} // namespace std + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index d953d9903..e208c2abb 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -14,13 +14,21 @@ using nlohmann::ordered_json; using nlohmann::json_document; using nlohmann::json_view; using nlohmann::ordered_json_document; +using nlohmann::ordered_json_view; +#include +#include +#include #include +#include +#include +#include #include #include #include #include #include +#include #include #include @@ -407,7 +415,669 @@ TEST_CASE("json_view") const std::string text = R"( {"key": "value", "escaped": "a\nb", "n": 42})"; const json_document d = json_document::parse(text); CHECK(d.root().source_offset() == 2); - // (element access comes with a later change; the offsets of the - // string nodes are checked through materialize() above) + CHECK(d.root()["key"].source_offset() == text.find("value")); + CHECK(d.root()["escaped"].source_offset() == static_cast(-1)); + CHECK(d.root()["n"].source_offset() == text.find("42")); } } + +namespace +{ +#if !defined(JSON_NOEXCEPTION) +// the exception a call throws, or "" if it throws none +template +std::string exception_of(F f) +{ + try + { + f(); + } + catch (const json::exception& e) + { + return e.what(); + } + return ""; +} +#endif + +// compares a view with the ordered_json value materialize() gives for it: +// types, sizes, elements and members (by index, key, and iteration), in +// document order; duplicate keys are found as their first occurrence +void check_access(const ordered_json_view& v, const ordered_json& j) +{ + REQUIRE(v.type() == j.type()); + CHECK(std::string(v.type_name()) == j.type_name()); + if (v.is_array()) + { + REQUIRE(v.size() == j.size()); + std::size_t i = 0; + for (const ordered_json_view e : v) + { + CHECK(v[i].materialize() == e.materialize()); + CHECK(v.at(i).materialize() == e.materialize()); + check_access(e, j[i]); + ++i; + } + CHECK(i == v.size()); + CHECK(!v[v.size()]); + std::size_t index = 0; + for (const auto& item : v.items()) + { + CHECK(item.key() == std::to_string(index)); + CHECK(item.value().materialize() == j[index]); + ++index; + } + if (!v.empty()) + { + CHECK(v.front().materialize() == j.front()); + CHECK(v.back().materialize() == j.back()); + } + } + else if (v.is_object()) + { + std::vector keys; // first occurrences, in order + std::size_t members = 0; + for (auto it = v.begin(); it != v.end(); ++it) + { + ++members; + const std::string key(it.key().data(), it.key().size()); + CHECK(v.contains(key)); + CHECK(v.count(key) == 1); + if (std::find(keys.begin(), keys.end(), key) != keys.end()) + { + continue; // a duplicate: lookups find the first one + } + keys.push_back(key); + CHECK(v.find(key) == it); + CHECK(v[key].materialize() == it->materialize()); + CHECK(v.at(key).materialize() == it.value().materialize()); + CHECK(v[key.c_str()].materialize() == (*it).materialize()); + } + CHECK(members == v.size()); + REQUIRE(keys.size() == j.size()); + std::size_t k = 0; + for (const auto& member : j.items()) + { + CHECK(keys[k++] == member.key()); + } + if (keys.size() == members) + { + // no duplicates: the values are those of the object + for (const auto& key : keys) + { + check_access(v[key], j[key]); + } + if (!v.empty()) + { + CHECK(v.front().materialize() == j.front()); + CHECK(v.back().materialize() == j.back()); + } + } + CHECK(!v["not a key in the generated documents"]); + CHECK(v.find("not a key in the generated documents") == v.end()); + } + else + { + // a primitive is a range of one element; null is empty + CHECK(static_cast(std::distance(v.begin(), v.end())) == (v.is_null() ? 0u : 1u)); + if (!v.is_null()) + { + CHECK((*v.begin()).materialize() == j); + CHECK(v.front().materialize() == j); + CHECK(v.back().materialize() == j); + } + } +} +} // namespace + +TEST_CASE("json_view element access and iteration") +{ + SECTION("generated documents") + { + generator g; + for (int i = 0; i < 2000; ++i) + { + std::string text; + g.value(text, 0); + CAPTURE(text) + const ordered_json_document d = ordered_json_document::parse(text); + check_access(d.root(), ordered_json::parse(text)); + } + } + + SECTION("keys") + { + // keys of every length around the 2/4/8/16-byte loads, with escapes + std::string text = "{"; + std::vector keys = {"", "x"}; + for (std::size_t n = 1; n <= 40; ++n) + { + keys.emplace_back(n, 'k'); + keys.push_back(std::string(n, 'k') + "x"); + keys.push_back("x" + std::string(n, 'k')); + } + for (std::size_t i = 0; i < keys.size(); ++i) + { + text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i); + } + text += ",\"esc\\u0061ped\":\"escaped key\"}"; // NOLINT(modernize-raw-string-literal) + const json_document d = json_document::parse(text); + const json_view root = d.root(); + for (std::size_t i = 0; i < keys.size(); ++i) + { + CAPTURE(keys[i]) + CHECK(root[keys[i]].materialize() == i); + CHECK(root.at(keys[i]).materialize() == i); + CHECK(root.find(keys[i]).key() == keys[i]); + CHECK(!root.contains(keys[i] + "y")); + } + CHECK(root["escaped"].materialize() == "escaped key"); + CHECK(!root.contains("esc\\u0061ped")); +#ifdef JSON_HAS_CPP_17 + CHECK(root[std::string_view("kkk")].materialize() == root["kkk"].materialize()); +#endif + } + + SECTION("duplicate keys: lookups find the first member, iteration all") + { + const json_document d = json_document::parse(R"({"a":1,"b":2,"a":3})"); + const json_view v = d.root(); + CHECK(v.size() == 3); + CHECK(v["a"].materialize() == 1); + CHECK(v.at("a").materialize() == 1); + CHECK(v.find("a") == v.begin()); + CHECK(v.count("a") == 1); + std::string order; + for (auto it = v.begin(); it != v.end(); ++it) + { + order += std::string(it.key().data(), it.key().size()) + it->materialize().dump(); + } + CHECK(order == "a1b2a3"); + CHECK(v.back().materialize() == 3); + CHECK(v.materialize() == json::parse(R"({"a":1,"b":2,"a":3})")); // the last value, as parse() + } + + SECTION("errors are those of const basic_json") + { + for (const char* text : + {"null", "true", "42", "-1", "1.5", "\"s\"", "[]", "[1,2]", "{}", "{\"a\":1}" + }) + { + CAPTURE(text) + const json_document d = json_document::parse(text); + const json_view v = d.root(); + const json j = v.materialize(); +#if !defined(JSON_NOEXCEPTION) + if (!j.is_object()) + { + CHECK(exception_of([&] { static_cast(v["a"]); }) == exception_of([&] { static_cast(j["a"]); })); + } + if (!j.is_array()) + { + CHECK(exception_of([&] { static_cast(v[0]); }) == exception_of([&] { static_cast(j[0]); })); + } + CHECK(exception_of([&] { static_cast(v.at("a")); }) == exception_of([&] { static_cast(j.at("a")); })); + CHECK(exception_of([&] { static_cast(v.at("missing")); }) == exception_of([&] { static_cast(j.at("missing")); })); + CHECK(exception_of([&] { static_cast(v.at(0)); }) == exception_of([&] { static_cast(j.at(0)); })); + CHECK(exception_of([&] { static_cast(v.at(5)); }) == exception_of([&] { static_cast(j.at(5)); })); + if (!(j.is_object() && j.empty())) // (key() of an end iterator) + { + CHECK(exception_of([&] { static_cast(v.begin().key()); }) == exception_of([&] { static_cast(j.begin().key()); })); + } + if (!j.empty() || j.is_null()) + { + CHECK(exception_of([&] { static_cast(v.front()); }) == exception_of([&] { static_cast(j.front()); })); + CHECK(exception_of([&] { static_cast(v.back()); }) == exception_of([&] { static_cast(j.back()); })); + } +#endif + CHECK(v.contains("a") == j.contains("a")); + CHECK(v.count("a") == j.count("a")); + CHECK((v.find("a") == v.end()) == (j.find("a") == j.end())); // NOLINT(readability-container-contains): find() is what is tested + } + + // where basic_json has undefined behavior, the view answers safely + const json_document d = json_document::parse(R"({"a":[]})"); + CHECK(!d.root()["b"]); + CHECK(!d.root()["a"][0]); + CHECK_THROWS_WITH_AS(d.root()["a"].front(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&); + CHECK_THROWS_WITH_AS(d.root()["a"].back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&); + const json_view invalid{}; + CHECK(invalid.begin() == invalid.end()); + CHECK(std::string(invalid.type_name()) == "discarded"); + CHECK_THROWS_WITH_AS(invalid["a"], "[json.exception.type_error.305] cannot use operator[] with a string argument with discarded", json::type_error&); + } + + SECTION("iterators") + { + const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})"); + const json_view v = d.root(); + json_view::iterator it = v.begin(); + CHECK(it.is_object_iterator()); + CHECK(it->is_array()); + CHECK(it->size() == 2); + const json_view::iterator previous = it++; + CHECK(previous.key() == "x"); + CHECK(it.key() == "z"); + CHECK(it.value().is_null()); + CHECK(++it == v.end()); + CHECK(v.cbegin() == v.begin()); + CHECK(v.cend() == v.end()); + CHECK(!v["x"].begin().is_object_iterator()); + CHECK(json_view::iterator() == json_view::iterator()); + // standard algorithms + CHECK(std::count_if(v["x"].begin(), v["x"].end(), [](const json_view & e) + { + return e.is_object(); + }) == 1); + } + + SECTION("items") + { + const json_document d = json_document::parse(R"({"a":1,"b":[true,false]})"); + std::string keys; + for (const auto& item : d.root().items()) + { + keys += std::string(item.key().data(), item.key().size()); + CHECK(item.value().materialize() == d.root()[item.key()].materialize()); + } + CHECK(keys == "ab"); + auto items = d.root()["b"].items(); + auto first = items.begin(); + CHECK((*first++).key() == "0"); + CHECK((*first).key() == "1"); + CHECK(++first == items.end()); +#ifdef JSON_HAS_CPP_17 + std::string pairs; + for (const auto [key, value] : d.root().items()) + { + pairs += std::string(key) + "=" + value.materialize().dump() + ";"; + } + CHECK(pairs == "a=1;b=[true,false];"); + static_assert(std::tuple_size::value == 2, ""); + static_assert(std::is_same::type, json_view>::value, ""); +#endif + } +} + +namespace +{ +#if !defined(JSON_NOEXCEPTION) +// an exception message without the context that basic_json adds with +// JSON_DIAGNOSTICS ("(/path) ") and JSON_DIAGNOSTIC_POSITIONS ("(bytes 1-2) "); +// the view's exceptions have no such context +std::string without_path(std::string msg) +{ + for (const char* prefix : + {"] (/", "] (bytes " + }) + { + const std::size_t open = msg.find(prefix); + if (open != std::string::npos) + { + msg.erase(open + 2, msg.find(") ", open) + 2 - (open + 2)); + } + } + return msg; +} +#endif + +// the bits of a float, to compare values bit for bit +std::uint64_t bits(double x) +{ + std::uint64_t r = 0; + std::memcpy(&r, &x, sizeof(r)); + return r; +} + +std::uint32_t bits(float x) +{ + std::uint32_t r = 0; + std::memcpy(&r, &x, sizeof(r)); + return r; +} + +bool has_duplicate_keys(const ordered_json_view& v) +{ + if (v.is_object() && v.size() != v.materialize().size()) + { + return true; + } + return std::any_of(v.begin(), v.end(), [](const ordered_json_view e) + { + return e.is_structured() && has_duplicate_keys(e); + }); +} + +// compares the conversions of a view with those of ordered_json +void check_values(const ordered_json_view& v, const ordered_json& j, const std::string& text) +{ + CHECK(v.get() == j); + switch (j.type()) + { + case json::value_t::number_integer: + case json::value_t::number_unsigned: + case json::value_t::number_float: + { + // (converting a float out of range of the target type is undefined) + if (j.is_number_unsigned()) + { + CHECK(v.get() == j.get()); + } + else if (j.is_number_integer()) + { + CHECK(v.get() == j.get()); + } + CHECK(bits(v.get()) == bits(j.get())); + if (std::abs(j.get()) < 1e9) + { + CHECK(v.get() == j.get()); + } + const auto token = v.number_token(); + CHECK(text.compare(v.source_offset(), token.size(), token.data(), token.size()) == 0); + break; + } + case json::value_t::string: + CHECK(v.get() == j.get()); + CHECK(std::string(v.get_string().data(), v.get_string().size()) == j.get()); + break; + case json::value_t::boolean: + CHECK(v.get() == j.get()); + CHECK(v.get() == j.get()); + break; + case json::value_t::null: + CHECK(v.get() == nullptr); + break; + case json::value_t::array: + CHECK(v.get>() == j.get>()); + break; + case json::value_t::object: + CHECK((v.get>() == j.get>())); + break; + case json::value_t::binary: + case json::value_t::discarded: + default: + break; + } + +#if !defined(JSON_NOEXCEPTION) + // conversions to the wrong type throw what basic_json throws + if (!j.is_number()) + { + CHECK(exception_of([&] { static_cast(v.get()); }) == without_path(exception_of([&] { static_cast(j.get()); }))); + } + CHECK(exception_of([&] { static_cast(v.get()); }) == without_path(exception_of([&] { static_cast(j.get()); }))); + CHECK(exception_of([&] { static_cast(v.get()); }) == without_path(exception_of([&] { static_cast(j.get()); }))); + CHECK(exception_of([&] { static_cast(v.get()); }) == without_path(exception_of([&] { static_cast(j.get()); }))); + if (!j.is_array()) + { + CHECK(exception_of([&] { static_cast(v.get>()); }) == without_path(exception_of([&] { static_cast(j.get>()); }))); + } + if (!j.is_object()) + { + CHECK(exception_of([&] { static_cast(v.get>()); }) == without_path(exception_of([&] { static_cast(j.get>()); }))); + } +#endif + + if (v.is_array()) + { + std::size_t i = 0; + for (const ordered_json_view e : v) + { + check_values(e, j[i++], text); + } + } + else if (v.is_object()) + { + for (auto it = v.begin(); it != v.end(); ++it) + { + const std::string key(it.key().data(), it.key().size()); + if (v.size() == j.size()) // (no duplicate keys) + { + check_values(it.value(), j[key], text); + } + } + } +} + +struct record +{ + std::string name{}; // NOLINT(readability-redundant-member-init) + int count = 0; +}; + +void from_json(const json& j, record& r) +{ + j.at("name").get_to(r.name); + j.at("count").get_to(r.count); +} +} // namespace + +TEST_CASE("json_view values") +{ + SECTION("generated documents") + { + generator g; + for (int i = 0; i < 2000; ++i) + { + std::string text; + g.value(text, 0); + CAPTURE(text) + const ordered_json_document d = ordered_json_document::parse(text); + check_values(d.root(), ordered_json::parse(text), text); + } + } + + SECTION("floats are converted as parse() converts them") + { + std::mt19937_64 rng(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::vector tokens = {"0.1", "-0.0", "1e308", "1.7976931348623157e308", "2.2250738585072011e-308", "4.9e-324", "5e-324", + "0.1000000000000000055511151231257827021181583404541015625", "123456789012345678901234567890", + "9007199254740993", "1.00000000000000011102230246251565404236316680908203125", "7.2057594037927933e16" + }; + for (int i = 0; i < 20000; ++i) + { + const std::uint64_t bits = rng(); + double d = 0; + std::memcpy(&d, &bits, sizeof(d)); + if (!std::isfinite(d)) + { + continue; + } + std::array buf{}; + switch (i % 5) // NOLINT(hicpp-multiway-paths-covered) + { + case 0: + std::snprintf(buf.data(), buf.size(), "%.17g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + break; + case 1: + std::snprintf(buf.data(), buf.size(), "%.15g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + break; + case 2: + std::snprintf(buf.data(), buf.size(), "%.3e", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + break; + case 3: + std::snprintf(buf.data(), buf.size(), "%.25g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + break; + default: + std::snprintf(buf.data(), buf.size(), "%.0f", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + break; + } + tokens.emplace_back(buf.data()); + } + using json_float = nlohmann::basic_json; + for (const auto& token : tokens) + { + CAPTURE(token) + const std::string text = "[" + token + "]"; + const double b = json::parse(text)[0].get(); + CHECK(bits(json_document::parse(text).root()[0].get()) == bits(b)); + if (std::abs(b) < 1e38) + { + CHECK(bits(nlohmann::basic_json_document::parse(text).root()[0].get()) == bits(json_float::parse(text)[0].get())); + } + } + } + + SECTION("number tokens") + { + const json_document d = json_document::parse(R"([1.50, 1E2, -0, 123456789012345678901234567890, -12, 7, "x"])"); + const json_view v = d.root(); + CHECK(v[0].number_token() == "1.50"); + CHECK(v[1].number_token() == "1E2"); + CHECK(v[2].number_token() == "-0"); + CHECK(v[3].number_token() == "123456789012345678901234567890"); + CHECK(v[4].number_token() == "-12"); + CHECK(v[5].number_token() == "7"); + CHECK_THROWS_WITH_AS(v[6].number_token(), "[json.exception.type_error.302] type must be number, but is string", json::type_error&); + CHECK_THROWS_WITH_AS(v.get_string(), "[json.exception.type_error.302] type must be string, but is array", json::type_error&); + } + + SECTION("conversions") + { + const std::string text = R"({"name": "widget", "count": 3, "tags": ["a", "b\n"], "sizes": {"s": 1, "m": 2}, "pair": [1, "x"]})"; + const json_document d = json_document::parse(text); + const json_view v = d.root(); + const json j = json::parse(text); + + // user types with from_json, and other types, through basic_json + const record r = v.get(); + CHECK(r.name == "widget"); + CHECK(r.count == 3); + CHECK((v["pair"].get>() == j["pair"].get>())); + CHECK(v["tags"].get>() == j["tags"].get>()); + CHECK((v["sizes"].get>() == j["sizes"].get>())); + CHECK(v["tags"].get>() == std::vector {"a", "b\n"}); + + // views of the elements + const auto views = v["tags"].get>(); + CHECK(views.size() == 2); + CHECK(views[1].get_string() == "b\n"); + const auto members = v.get>(); + CHECK(members.at("count").get() == 3); + CHECK(v.get()["name"].get_string() == "widget"); + + // strings without a copy point into the source text + CHECK(v["name"].get_string().data() == text.data() + text.find("widget")); +#ifdef JSON_HAS_CPP_17 + CHECK(v["name"].get() == "widget"); +#endif + + std::string name; + int count = 0; + CHECK(&v["name"].get_to(name) == &name); + v["count"].get_to(count); + CHECK(name == "widget"); + CHECK(count == 3); + + // a duplicate key: the last value, as parse() + CHECK((json_document::parse(R"({"a":1,"a":2})").root().get>() == std::map {{"a", 2}})); + + const json_view invalid{}; + CHECK_THROWS_WITH_AS(invalid.get(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&); + CHECK(invalid.get().is_discarded()); + } + + SECTION("value") + { + const json_document d = json_document::parse(R"({"n": 1, "s": "text", "o": {"x": [10, 20]}})"); + const json_view v = d.root(); + const json j = v.materialize(); + CHECK(v.value("n", 0) == j.value("n", 0)); + CHECK(v.value("missing", 42) == j.value("missing", 42)); + CHECK(v.value("s", "default") == j.value("s", "default")); + CHECK(v.value("missing", "default") == j.value("missing", "default")); + CHECK(v.value(std::string("n"), 2.5) == j.value(std::string("n"), 2.5)); + CHECK(v.value(json::json_pointer("/o/x/1"), 0) == j.value(json::json_pointer("/o/x/1"), 0)); + CHECK(v.value(json::json_pointer("/o/x/5"), 0) == j.value(json::json_pointer("/o/x/5"), 0)); + CHECK(v.value(json::json_pointer("/o/y"), "none") == j.value(json::json_pointer("/o/y"), "none")); + // with a JSON pointer, arrays can be asked as well + CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0)); + CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3)); +#if !defined(JSON_NOEXCEPTION) + CHECK(exception_of([&] { static_cast(v["o"]["x"].value("k", 0)); }) == without_path(exception_of([&] { static_cast(j["o"]["x"].value("k", 0)); }))); + CHECK(exception_of([&] { static_cast(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast(j.value("s", 0)); }))); + CHECK(exception_of([&] { static_cast(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast(j["n"].value("x", 0)); }))); + CHECK(exception_of([&] { static_cast(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast(j["n"].value(json::json_pointer("/x"), 0)); }))); +#endif + } +} + +TEST_CASE("json_view JSON pointers") +{ + SECTION("every value of generated documents") + { + generator g; + for (int i = 0; i < 1000; ++i) + { + std::string text; + g.value(text, 0); + const ordered_json_document d = ordered_json_document::parse(text); + if (has_duplicate_keys(d.root())) + { + continue; + } + CAPTURE(text) + const ordered_json j = ordered_json::parse(text); + const ordered_json flat = j.flatten(); + for (const auto& leaf : flat.items()) + { + // the leaf and each of its parents + for (ordered_json::json_pointer p(leaf.key());; p = p.parent_pointer()) + { + CAPTURE(p.to_string()) + CHECK(d.root()[p].materialize() == j[p]); + CHECK(d.root().at(p).materialize() == j.at(p)); + CHECK(d.root().contains(p)); + if (p.empty()) + { + break; + } + } + } + } + } + +#if !defined(JSON_NOEXCEPTION) + SECTION("errors are those of basic_json") + { + const std::string text = R"({"a": [1, {"b": null}], "c": "s", "": {"": 0}, "a~b": 1, "c/d": 2})"; + const json_document d = json_document::parse(text); + const json_view v = d.root(); + const json j = v.materialize(); + for (const char* pointer : + {"", "/", "//", "/a", "/a/0", "/a/1/b", "/a/-", "/a/01", "/a/00", "/a/1a", "/a/a", "/a/", "/a/2", "/a/99", "/a/99999999999999999999", + "/a/18446744073709551615", "/a/-1", "/a/+1", "/a/ 1", "/x", "/c/x", "/a/0/x", "/a/1/b/c", "/a~0b", "/c~1d", "/c~1d/x", "/a/1/-" + }) + { + CAPTURE(pointer) + const json::json_pointer p(pointer); + const std::string at_error = without_path(exception_of([&] { static_cast(j.at(p)); })); + CHECK(exception_of([&] { static_cast(v.at(p)); }) == at_error); + if (at_error.empty()) + { + CHECK(v.at(p).materialize() == j.at(p)); + CHECK(v[p].materialize() == j[p]); + } + else if (at_error.find("out_of_range.401") != std::string::npos || at_error.find("out_of_range.403") != std::string::npos) // NOLINT(abseil-string-find-str-contains) + { + // undefined behavior for const basic_json::operator[] + CHECK(!v[p]); + } + else + { + CHECK(exception_of([&] { static_cast(v[p]); }) == without_path(exception_of([&] { static_cast(j[p]); }))); + } + // (basic_json::contains() throws out_of_range.404 for an empty + // array index token, although it is not meant to throw; the view + // answers false) + const std::string contains_error = exception_of([&] + { + const bool found = j.contains(p); + static_cast(found); + }); + CHECK(v.contains(p) == (contains_error.empty() && j.contains(p))); + CHECK(exception_of([&] { static_cast(v.value(p, 5)); }) == without_path(exception_of([&] { static_cast(j.value(p, 5)); }))); + } + } +#endif +}