diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 7a8184eae..6e5f7001b 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -143,7 +143,17 @@ 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::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'); @@ -157,11 +167,14 @@ 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::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 ('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 2d1042cc8..798113898 100644 --- a/docs/mkdocs/docs/api/basic_json/at.md +++ b/docs/mkdocs/docs/api/basic_json/at.md @@ -219,6 +219,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 ef623a5ff..288fc7c12 100644 --- a/docs/mkdocs/docs/api/basic_json/begin.md +++ b/docs/mkdocs/docs/api/basic_json/begin.md @@ -37,6 +37,12 @@ Constant. --8<-- "examples/begin.output" ``` +## See also + +- [end](end.md) - returns an iterator to one past the last element +- [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 - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/cbegin.md b/docs/mkdocs/docs/api/basic_json/cbegin.md index 06504fee6..876c8a360 100644 --- a/docs/mkdocs/docs/api/basic_json/cbegin.md +++ b/docs/mkdocs/docs/api/basic_json/cbegin.md @@ -36,6 +36,11 @@ Constant. --8<-- "examples/cbegin.output" ``` +## See also + +- [cend](cend.md) - returns a const iterator to one past the last element +- [basic_json_view::cbegin](../basic_json_view/cbegin.md) - the same iteration on a zero-copy view + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/cend.md b/docs/mkdocs/docs/api/basic_json/cend.md index 3f3aa949d..f808173a6 100644 --- a/docs/mkdocs/docs/api/basic_json/cend.md +++ b/docs/mkdocs/docs/api/basic_json/cend.md @@ -36,6 +36,11 @@ Constant. --8<-- "examples/cend.output" ``` +## See also + +- [cbegin](cbegin.md) - returns a const iterator to the first element +- [basic_json_view::cend](../basic_json_view/cend.md) - the same iteration on a zero-copy view + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/contains.md b/docs/mkdocs/docs/api/basic_json/contains.md index 7f865f83c..3bde577d6 100644 --- a/docs/mkdocs/docs/api/basic_json/contains.md +++ b/docs/mkdocs/docs/api/basic_json/contains.md @@ -111,6 +111,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 ce51addbd..6c7f4a897 100644 --- a/docs/mkdocs/docs/api/basic_json/count.md +++ b/docs/mkdocs/docs/api/basic_json/count.md @@ -76,6 +76,7 @@ This method always returns `0` when executed on a JSON type that is not an objec - [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 179ce9e67..2c2a59ed1 100644 --- a/docs/mkdocs/docs/api/basic_json/end.md +++ b/docs/mkdocs/docs/api/basic_json/end.md @@ -37,6 +37,11 @@ Constant. --8<-- "examples/end.output" ``` +## See also + +- [begin](begin.md) - returns an iterator to the first element +- [basic_json_view::end](../basic_json_view/end.md) - the same iteration on a zero-copy view + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/find.md b/docs/mkdocs/docs/api/basic_json/find.md index 35ff9dcb2..98c3c51ae 100644 --- a/docs/mkdocs/docs/api/basic_json/find.md +++ b/docs/mkdocs/docs/api/basic_json/find.md @@ -80,6 +80,7 @@ This method always returns `end()` when executed on a JSON type that is not an o - [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/items.md b/docs/mkdocs/docs/api/basic_json/items.md index 3f3d1f2ca..4a772a22a 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 3195870e4..8b289a276 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -254,6 +254,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 2022b8897..385e5b08a 100644 --- a/docs/mkdocs/docs/api/basic_json/type_name.md +++ b/docs/mkdocs/docs/api/basic_json/type_name.md @@ -52,6 +52,11 @@ Constant. --8<-- "examples/type_name.output" ``` +## See also + +- [type](type.md) - return the type of the JSON value +- [basic_json_view::type_name](../basic_json_view/type_name.md) - the same function on a zero-copy view + ## Version history - Added in version 1.0.0. 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..2c563232f --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/at.md @@ -0,0 +1,90 @@ +# 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; +``` + +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`. + +## Parameters + +`key` (in) +: object key of the element to access + +`idx` (in) +: index of the element to access + +## Return value + +1. the value of the first member with key `key` +2. the element at index `idx` + +## 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()`. + +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). + +## 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. + +## Examples + +??? example + + 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" + ``` + +## 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` + +## 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..e5abc4ffa --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/contains.md @@ -0,0 +1,64 @@ +# nlohmann::basic_json_view::contains + +```cpp +bool contains(string_view_t key) const; +bool contains(const char* key) const; +bool contains(const string_t& key) const; +``` + +Checks whether the value is an object with a member with key `key`. + +## Parameters + +`key` (in) +: key value to check its existence + +## Return value + +`#!cpp true` if the value is an object and has a member with key `key`, `#!cpp false` 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 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). + +## Examples + +??? example + + 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" + ``` + +## See also + +- [find](find.md) - find a value in an object +- [count](count.md) - returns the number of occurrences of a key +- [`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/index.md b/docs/mkdocs/docs/api/basic_json_view/index.md index 564406667..54e76bd1e 100644 --- a/docs/mkdocs/docs/api/basic_json_view/index.md +++ b/docs/mkdocs/docs/api/basic_json_view/index.md @@ -19,9 +19,9 @@ 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 [`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. ## Template parameters @@ -41,6 +41,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 +52,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 +68,27 @@ 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 +- [**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 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/operator[].md b/docs/mkdocs/docs/api/basic_json_view/operator[].md new file mode 100644 index 000000000..0959b9972 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json_view/operator[].md @@ -0,0 +1,105 @@ +# 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; +``` + +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.) + +## Parameters + +`key` (in) +: object key of the element to access + +`idx` (in) +: index of 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()` + +## 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. + +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 +`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). + +## 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). + +## Examples + +??? example + + 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" + ``` + +## 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` + +## 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/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__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..811db6267 --- /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..2999bd684 --- /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__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__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__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__type_name.cpp b/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp new file mode 100644 index 000000000..9d59792ca --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp @@ -0,0 +1,27 @@ +#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/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 6b66c42ca..cb8b8ed5d 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,26 @@ 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. +- **`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. ## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view` diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 0058b0e58..4c7bb7fd8 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -246,7 +246,17 @@ 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 - '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 @@ -260,11 +270,14 @@ 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 - '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 - byte_container_with_subtype: - 'Overview': api/byte_container_with_subtype/index.md - '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md