From d3d2ad3e4ad982298ea5a00468da01dee7770e01 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 28 Sep 2026 23:10:06 +0200 Subject: [PATCH] Document values and JSON pointers of json_view - API pages for get, get_to, get_string, number_token, and value of basic_json_view; JSON pointer overloads of operator[], at, and contains; links both ways with the basic_json pages - the feature page describes which conversions copy nothing - the examples show when the view helps: strings without copies, numbers exactly as written, and paths into a large text Signed-off-by: Niels Lohmann --- docs/docset/docSet.sql | 5 + docs/mkdocs/docs/api/basic_json/get.md | 2 + docs/mkdocs/docs/api/basic_json/get_ref.md | 2 + docs/mkdocs/docs/api/basic_json/get_to.md | 1 + docs/mkdocs/docs/api/basic_json/value.md | 1 + docs/mkdocs/docs/api/basic_json_view/at.md | 46 +++++- .../docs/api/basic_json_view/contains.md | 53 +++++-- docs/mkdocs/docs/api/basic_json_view/get.md | 130 +++++++++++++++++ .../docs/api/basic_json_view/get_string.md | 71 ++++++++++ .../mkdocs/docs/api/basic_json_view/get_to.md | 65 +++++++++ docs/mkdocs/docs/api/basic_json_view/index.md | 12 +- .../docs/api/basic_json_view/number_token.md | 74 ++++++++++ .../docs/api/basic_json_view/operator[].md | 57 +++++++- docs/mkdocs/docs/api/basic_json_view/value.md | 131 ++++++++++++++++++ .../basic_json_view__at_json_pointer.cpp | 34 +++++ .../basic_json_view__at_json_pointer.output | 3 + ...basic_json_view__contains_json_pointer.cpp | 39 ++++++ ...ic_json_view__contains_json_pointer.output | 4 + .../docs/examples/basic_json_view__get.cpp | 55 ++++++++ .../docs/examples/basic_json_view__get.output | 3 + .../examples/basic_json_view__get_string.cpp | 31 +++++ .../basic_json_view__get_string.output | 3 + .../docs/examples/basic_json_view__get_to.cpp | 28 ++++ .../examples/basic_json_view__get_to.output | 2 + .../basic_json_view__number_token.cpp | 29 ++++ .../basic_json_view__number_token.output | 5 + ...sic_json_view__operator[]_json_pointer.cpp | 47 +++++++ ..._json_view__operator[]_json_pointer.output | 3 + .../docs/examples/basic_json_view__value.cpp | 29 ++++ .../examples/basic_json_view__value.output | 3 + .../basic_json_view__value_json_pointer.cpp | 21 +++ ...basic_json_view__value_json_pointer.output | 3 + docs/mkdocs/docs/features/json_view.md | 26 +++- docs/mkdocs/mkdocs.yml | 5 + 34 files changed, 1007 insertions(+), 16 deletions(-) create mode 100644 docs/mkdocs/docs/api/basic_json_view/get.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/get_string.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/get_to.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/number_token.md create mode 100644 docs/mkdocs/docs/api/basic_json_view/value.md create mode 100644 docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__at_json_pointer.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__contains_json_pointer.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get_string.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get_string.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get_to.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__get_to.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__number_token.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__number_token.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__operator[]_json_pointer.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__value.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__value.output create mode 100644 docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json_view__value_json_pointer.output diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 6e5f7001b..a663c8726 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -154,6 +154,9 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::empty', 'Met 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'); @@ -169,12 +172,14 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_string', 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/get.md b/docs/mkdocs/docs/api/basic_json/get.md index 8329de919..70a35256c 100644 --- a/docs/mkdocs/docs/api/basic_json/get.md +++ b/docs/mkdocs/docs/api/basic_json/get.md @@ -163,6 +163,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 c50c077b0..3332590b4 100644 --- a/docs/mkdocs/docs/api/basic_json/get_to.md +++ b/docs/mkdocs/docs/api/basic_json/get_to.md @@ -67,6 +67,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/value.md b/docs/mkdocs/docs/api/basic_json/value.md index 2e9b85a2b..49c3b631e 100644 --- a/docs/mkdocs/docs/api/basic_json/value.md +++ b/docs/mkdocs/docs/api/basic_json/value.md @@ -181,6 +181,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 index 2c563232f..d1b1732bb 100644 --- a/docs/mkdocs/docs/api/basic_json_view/at.md +++ b/docs/mkdocs/docs/api/basic_json_view/at.md @@ -9,11 +9,15 @@ 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 @@ -23,10 +27,14 @@ basic_json_view at(int idx) const; `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 @@ -42,6 +50,20 @@ Strong exception safety: if an exception is thrown, there are no changes to the [`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 @@ -54,16 +76,20 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics 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. +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 "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 @@ -79,11 +105,27 @@ existing error handling written against `BasicJsonType::at` keeps working unchan --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 diff --git a/docs/mkdocs/docs/api/basic_json_view/contains.md b/docs/mkdocs/docs/api/basic_json_view/contains.md index e5abc4ffa..bbd791887 100644 --- a/docs/mkdocs/docs/api/basic_json_view/contains.md +++ b/docs/mkdocs/docs/api/basic_json_view/contains.md @@ -1,21 +1,30 @@ # 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; ``` -Checks whether the value is an object with a member with key `key`. +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 -`#!cpp true` if the value is an object and has a member with key `key`, `#!cpp false` otherwise. +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 @@ -23,22 +32,34 @@ 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. +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 -This method always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md) +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(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 "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. @@ -53,10 +74,26 @@ view. --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 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 54e76bd1e..50bcaa24b 100644 --- a/docs/mkdocs/docs/api/basic_json_view/index.md +++ b/docs/mkdocs/docs/api/basic_json_view/index.md @@ -20,8 +20,11 @@ Moving the document itself does not invalidate its views: the index is heap-allo `basic_json_document` object. `basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element -access, lookup, iteration, and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on -demand. It does not (yet) provide `get()`, JSON Pointer support, `dump()`, or comparison. +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 @@ -72,6 +75,7 @@ demand. It does not (yet) provide `get()`, JSON Pointer support, `dump()`, or - [**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 @@ -96,6 +100,10 @@ demand. It does not (yet) provide `get()`, JSON Pointer support, `dump()`, or ### 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/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 index 0959b9972..2361bf77c 100644 --- a/docs/mkdocs/docs/api/basic_json_view/operator[].md +++ b/docs/mkdocs/docs/api/basic_json_view/operator[].md @@ -9,12 +9,18 @@ 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 @@ -24,11 +30,18 @@ basic_json_view operator[](int idx) const; `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 @@ -42,9 +55,19 @@ Strong exception safety: if an exception is thrown, there are no changes to the 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. -Neither exception carries 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 +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 @@ -55,6 +78,8 @@ value to point at, so the exception is created without one, even if `BasicJsonTy 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 @@ -74,9 +99,18 @@ document. 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 "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 @@ -93,12 +127,29 @@ document. --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 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_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__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__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__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[]_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__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 cb8b8ed5d..7dbea9241 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -139,9 +139,33 @@ whenever any of the other conditions above was not met. element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the view has no `basic_json` value to point at, so the exception is created without one, regardless of how `BasicJsonType` was built. -- **`get()`, JSON Pointer, `dump()`, and comparison are not (yet) provided** by `basic_json_view`. For now, +- **`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` | | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) | diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 4c7bb7fd8..0ff0f9fc3 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -257,6 +257,9 @@ nav: - '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 @@ -272,12 +275,14 @@ nav: - '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