diff --git a/BUILD.bazel b/BUILD.bazel index e5fecac22..9e3fa0003 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 9db3d6a8b..fa55fa337 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -148,7 +148,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'); @@ -162,11 +175,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 8324a303b..c5453e53d 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -251,7 +251,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 @@ -265,11 +278,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 94bfe74cb..099182c7a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -5793,29 +5793,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 002f7e7e8..20e0e7e04 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 @@ -20395,6 +20428,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 @@ -20407,6 +20445,8 @@ class json_pointer template friend class json_pointer; + friend struct detail::json_pointer_access; + template struct string_t_helper { @@ -21536,6 +21576,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 @@ -34208,29 +34262,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 +}