diff --git a/docs/mkdocs/docs/api/basic_json_view/get.md b/docs/mkdocs/docs/api/basic_json_view/get.md index cd0a98bff..191fabdf5 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get.md +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -38,7 +38,7 @@ as [`BasicJsonType::parse()`](../basic_json/parse.md) does), as well as the type accept views, e.g. with [`is_json_view`](../is_json_view.md). A `from_json()` that also accepts types that are no JSON values at all, such as the unconstrained `#!cpp template void from_json(const BasicJsonType&, T&)` of [arbitrary type conversions](../../features/arbitrary_types.md), is not called with a view: it may use -`BasicJsonType` functions a view does not have. See [json_view](../../features/json_view.md). +`BasicJsonType` functions a view does not have. See [Writing generic code](../../features/json_view.md#writing-generic-code). !!! warning "Duplicate keys" diff --git a/docs/mkdocs/docs/api/is_json_view.md b/docs/mkdocs/docs/api/is_json_view.md index 53bd8ec3a..6153adf30 100644 --- a/docs/mkdocs/docs/api/is_json_view.md +++ b/docs/mkdocs/docs/api/is_json_view.md @@ -49,6 +49,7 @@ view class. - [basic_json_view](basic_json_view/index.md) - a handle to one value of a document - [get](basic_json_view/get.md) - which conversions read the view directly - [view_mode](view_mode.md) - read-only or editable +- [Writing generic code](../features/json_view.md#writing-generic-code) - functions and `from_json()` for views ## Version history diff --git a/docs/mkdocs/docs/api/view_mode.md b/docs/mkdocs/docs/api/view_mode.md index bdbdfb5d8..8559b6662 100644 --- a/docs/mkdocs/docs/api/view_mode.md +++ b/docs/mkdocs/docs/api/view_mode.md @@ -74,6 +74,7 @@ nor a [`json_editable_view`](json_editable_view.md). Views of the two modes stil - [basic_json_document](basic_json_document/index.md) - a parsed JSON text - [basic_json_view](basic_json_view/index.md) - a handle to one value of a document - [json_editable_document](json_editable_document.md) - an editable document of `json` +- [Writing generic code](../features/json_view.md#writing-generic-code) - functions that accept views ## Version history diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 7de4a07cd..2225be41e 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -214,14 +214,15 @@ number of distinct keys, or reject duplicates when iterating. ## Getting values out without copying -[`get()`](../api/basic_json_view/get.md) converts many `T` directly from the flat index, without ever building a +[`get()`](../api/basic_json_view/get.md) converts most `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 +`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json_view` itself, the standard containers +and other types the library converts (`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::list`, `#!cpp std::array`, +`#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, ...), and the types of the `NLOHMANN_DEFINE_TYPE_*` and +`NLOHMANN_JSON_SERIALIZE_ENUM` macros. Types whose `from_json()` is written for `basic_json` only go 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. +`basic_json` value first, exactly as [`parse()`](../api/basic_json/parse.md) would, and converted from there. See +[Writing generic code](#writing-generic-code) for how to write a `from_json()` that reads views. Two conversions never copy at all: @@ -236,6 +237,89 @@ Two conversions never copy at all: Both results are only valid as long as the view -- and, for a string with no escapes, the borrowed source text -- is. +## Writing generic code + +A view's type names its `BasicJsonType` and its [`view_mode`](../api/view_mode.md): +[`json_view`](../api/json_view.md) is `#!cpp basic_json_view`, and +[`json_editable_view`](../api/json_editable_view.md) is `#!cpp basic_json_view`. These are +different types, and there is no conversion between them, so a function written for one shortcut accepts no other +view: + +```cpp +void print_names(json_view v); // accepts neither an ordered_json_view nor a json_editable_view +``` + +This is the same pitfall as a `to_json`/`from_json` written for `json` instead of any `BasicJsonType`, which then does +not work for `ordered_json`. The shortcuts are for declaring documents and views; a function that takes a view names +the views it accepts with a template: + +```cpp +// views of editable documents only +template +void f(basic_json_view v); + +// views of read-only documents only +template +void f(basic_json_view v); + +// either +template +void f(basic_json_view v); +``` + +Views are passed by value: they are two pointers, cheap to copy, like a `#!cpp std::string_view`. A `const&` works as +well, but a non-`const` reference does not bind to the views that [`root()`](../api/basic_json_document/root.md), +[`operator[]`](../api/basic_json_view/operator%5B%5D.md), and [`at()`](../api/basic_json_view/at.md) return, so +`#!cpp f(doc.root())` would not compile. + +There is no conversion from an editable view to a read-only one, by design: the mode decides how a view walks the +index. A read-only view reads a container in place, while a view of an editable document follows the links that edits +leave behind, so a read-only view of an edited document would read stale values. Views of both modes compare with +[`operator==`](../api/basic_json_view/operator_eq.md). + +[`is_json_view`](../api/is_json_view.md) tells views from other types, for constraints that cannot be written as a +`basic_json_view<...>` parameter. + +### A `from_json()` that reads views + +[`get()`](../api/basic_json_view/get.md) on a view calls `T`'s `from_json()` with the view itself if that +`from_json()` accepts views, and with [`materialize()`](../api/basic_json_view/materialize.md) otherwise. The +`from_json()` functions of the [`NLOHMANN_DEFINE_TYPE_*`](../api/macros/nlohmann_define_type_intrusive.md), +[`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../api/macros/nlohmann_define_derived_type.md), and +[`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md) macros, and those of the library +for the standard types, accept views, so these types need nothing else. For decoding the statuses of +twitter.json into structs, reading the view is about 5 times faster than `#!cpp materialize().get()`. + +A hand-written `from_json()` reads views if it is constrained to `json` values and views: + +```cpp +template < typename J, typename std::enable_if < + nlohmann::is_json_view::value || std::is_same::value, int >::type = 0 > +void from_json(const J& j, person& p) +{ + p.name = j.at("name").template get(); + p.age = j.at("age").template get(); +} +``` + +Its body may only use what views and `basic_json` have in common: the type checks, `at()`, `operator[]`, `contains()`, +`value()`, `size()`, iteration and `items()`, and `get()`/`get_to()`. An unconstrained +`#!cpp template void from_json(const BasicJsonType& j, person& p)`, as in +[arbitrary type conversions](arbitrary_types.md), is never called with a view, as it might use `basic_json` functions +a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is +converted through `materialize()` as well. + +Two things differ when a `from_json()` reads the view: + +- **Duplicate keys.** Members looked up by name (`at()`, `value()`, ... and thus the macros) find the **first** member + with a repeated key, see [Duplicate keys](#duplicate-keys); `#!cpp materialize().get()` would see the last one. + Containers built from an object, such as `#!cpp std::map`, keep the **last** value either way, as + [`parse()`](../api/basic_json/parse.md) does. +- **[`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md).** Exceptions thrown by the view carry no path to the + value. + +`to_json()` is unaffected: it writes `basic_json` values, never views. + ## Writing a view back [`dump()`](../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a