diff --git a/BUILD.bazel b/BUILD.bazel index b6b5152a2..2b1c9f146 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -90,6 +90,7 @@ cc_library( "include/nlohmann/detail/view/simd.hpp", "include/nlohmann/detail/view/string_ref.hpp", "include/nlohmann/detail/view/value.hpp", + "include/nlohmann/detail/view/view_mode.hpp", "include/nlohmann/json.hpp", "include/nlohmann/json_fwd.hpp", "include/nlohmann/json_literals.hpp", diff --git a/docs/mkdocs/docs/api/basic_json_document/erase.md b/docs/mkdocs/docs/api/basic_json_document/erase.md index 4cbc50d19..7d453addd 100644 --- a/docs/mkdocs/docs/api/basic_json_document/erase.md +++ b/docs/mkdocs/docs/api/basic_json_document/erase.md @@ -12,7 +12,7 @@ void erase(view_type array, I idx); std::size_t erase(const json_pointer& ptr); ``` -Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) +Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md)) has `erase`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). 1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many diff --git a/docs/mkdocs/docs/api/basic_json_document/index.md b/docs/mkdocs/docs/api/basic_json_document/index.md index ea5f449db..b0743e4ec 100644 --- a/docs/mkdocs/docs/api/basic_json_document/index.md +++ b/docs/mkdocs/docs/api/basic_json_document/index.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -template +template class basic_json_document; ``` @@ -20,9 +20,9 @@ is move-only: a copy would duplicate the whole index, the decoded-string buffer, enough in every use we know of. The index, the decoded strings, and the owned text live in one heap block that the document holds through a `#!cpp std::unique_ptr`; this is also why views survive a move of the document. -With `#!cpp Editable == true`, the document also offers [`set`](set.md), [`push_back`](push_back.md), +With `#!cpp Mode == view_mode::editable`, the document also offers [`set`](set.md), [`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md) to change values in place, see [Edits](#edits) below. The source text -itself is never written; a read-only document (`#!cpp Editable == false`, the default) does not carry any of the +itself is never written; a read-only document (`#!cpp view_mode::read_only`, the default) does not carry any of the bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp static_assert`). ## Template parameters @@ -32,9 +32,10 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp [`ordered_json`](../ordered_json.md). Only 64-bit `number_integer_t`/`number_unsigned_t` types are supported; this is checked with a `static_assert`. -`Editable` -: whether the document supports [`set`](set.md), [`push_back`](push_back.md), [`insert`](insert.md), and - [`erase`](erase.md) (optional, `#!cpp false` by default). See [Edits](#edits) below. +`Mode` +: a [`view_mode`](../view_mode.md): `#!cpp view_mode::editable` if the document supports [`set`](set.md), + [`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md) (optional, `#!cpp view_mode::read_only` + by default). See [Edits](#edits) below. ## Specializations @@ -47,7 +48,7 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp ## Member types -- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view`) +- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view`) - **value_t** - the JSON type enumeration, see [`basic_json::value_t`](../basic_json/value_t.md) - **image_check** - how [`load()`](load.md) validates an image (`full`, `bounds`, `none`), see [`image_check`](load.md#image_check) @@ -80,15 +81,15 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp ### Edits - [**set**](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers - to (`#!cpp Editable` documents only) -- [**push_back**](push_back.md) - append to an array (`#!cpp Editable` documents only) -- [**insert**](insert.md) - insert an element into an array before a given position (`#!cpp Editable` documents only) + to (editable documents only) +- [**push_back**](push_back.md) - append to an array (editable documents only) +- [**insert**](insert.md) - insert an element into an array before a given position (editable documents only) - [**erase**](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to - (`#!cpp Editable` documents only) + (editable documents only) ## Edits -An editable document (`#!cpp Editable == true`) can be changed after parsing, with [`set`](set.md), +An editable document (`#!cpp view_mode::editable`) can be changed after parsing, with [`set`](set.md), [`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md); [`json_editable_document`](../json_editable_document.md) and [`ordered_json_editable_document`](../ordered_json_editable_document.md) are the corresponding specializations. A few diff --git a/docs/mkdocs/docs/api/basic_json_document/insert.md b/docs/mkdocs/docs/api/basic_json_document/insert.md index b513dfc0d..883acabda 100644 --- a/docs/mkdocs/docs/api/basic_json_document/insert.md +++ b/docs/mkdocs/docs/api/basic_json_document/insert.md @@ -5,7 +5,7 @@ template view_type insert(view_type array, I idx, V&& value); ``` -Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) +Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md)) has `insert`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). Inserts `value` into `array` as a new element before position `idx`, which must not be past the end diff --git a/docs/mkdocs/docs/api/basic_json_document/push_back.md b/docs/mkdocs/docs/api/basic_json_document/push_back.md index 37ce10bc5..96e3f113a 100644 --- a/docs/mkdocs/docs/api/basic_json_document/push_back.md +++ b/docs/mkdocs/docs/api/basic_json_document/push_back.md @@ -13,7 +13,7 @@ editable, and it does not have to be `array`'s own document -- which is copied s source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...). -Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) +Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md)) has `push_back`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). ## Template parameters diff --git a/docs/mkdocs/docs/api/basic_json_document/save.md b/docs/mkdocs/docs/api/basic_json_document/save.md index f69d5c9f8..c47295048 100644 --- a/docs/mkdocs/docs/api/basic_json_document/save.md +++ b/docs/mkdocs/docs/api/basic_json_document/save.md @@ -12,7 +12,7 @@ An edited document is written in its *current* state, with its values in documen parser would have produced them for that JSON text: a member [`set`](set.md) added goes at the end, an [`erase`](erase.md)d member leaves no trace, and a float that is not finite (NaN or positive/negative infinity) becomes null, the same substitution [`dump()`](../basic_json_view/dump.md) makes. The same document always saves to -the same bytes -- also across `BasicJsonType` and `#!cpp Editable`, since the image reflects document order and +the same bytes -- also across `BasicJsonType` and the [`view_mode`](../view_mode.md), since the image reflects document order and values only, not which specialization produced them. ## Return value diff --git a/docs/mkdocs/docs/api/basic_json_document/set.md b/docs/mkdocs/docs/api/basic_json_document/set.md index ae26ed516..dd5d99611 100644 --- a/docs/mkdocs/docs/api/basic_json_document/set.md +++ b/docs/mkdocs/docs/api/basic_json_document/set.md @@ -18,7 +18,7 @@ template view_type set(const json_pointer& ptr, V&& value); ``` -Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) +Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md)) has `set`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). 1. Replaces the value `target` refers to with `value`. 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 df1e939fa..7ae259287 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 @@ -1,20 +1,32 @@ # nlohmann::basic_json_view::basic_json_view ```cpp +// (1) basic_json_view() noexcept = default; + +// (2) only for Mode == view_mode::editable +basic_json_view(const basic_json_view& other) noexcept; ``` -Creates an invalid (discarded) view: [`type()`](type.md) is `#!cpp value_t::discarded` and -[`is_discarded()`](is_discarded.md) is `#!cpp true`. +1. Creates an invalid (discarded) view: [`type()`](type.md) is `#!cpp value_t::discarded` and + [`is_discarded()`](is_discarded.md) is `#!cpp true`. +2. Converts a view of a read-only document into a view of an editable document (implicitly), so that a function + taking a [`json_editable_view`](../json_editable_view.md) accepts both. The result is a view of the same value of + the same read-only document. See [`view_mode`](../view_mode.md) for why there is no conversion the other way. -This is the only constructor a caller can use directly. Every other view is obtained from a +## Parameters + +`other` (in) +: a view of a read-only document with the same `BasicJsonType` + +Apart from these, a view is obtained from a [`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 -No-throw guarantee: this constructor never throws exceptions. +No-throw guarantee: these constructors never throw exceptions. ## Complexity @@ -45,6 +57,7 @@ placeholder for "no value yet" and later be assigned a real view. - [is_discarded](is_discarded.md) - return whether the view is invalid - [root](../basic_json_document/root.md) - the view of a document's root value +- [view_mode](../view_mode.md) - whether a document and its views can be edited ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/index.md b/docs/mkdocs/docs/api/basic_json_view/index.md index 5f0d90694..5049e2242 100644 --- a/docs/mkdocs/docs/api/basic_json_view/index.md +++ b/docs/mkdocs/docs/api/basic_json_view/index.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -template +template class basic_json_view; ``` @@ -37,7 +37,7 @@ Moving the document itself does not invalidate its views: the index is heap-allo `#!cpp operator ValueType()`), and no `#!cpp get_ptr()` or `#!cpp get_ref()`; see [`get`](get.md). `basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an -**editable** document (`#!cpp Editable == true`) sees every edit made through +**editable** document (`#!cpp view_mode::editable`) sees every edit made through [`basic_json_document::set`](../basic_json_document/set.md) and [`basic_json_document::push_back`](../basic_json_document/push_back.md): once a value is changed, every view that still refers to it -- including ones taken before the change -- reads the new value. See @@ -49,9 +49,10 @@ still refers to it -- including ones taken before the change -- reads the new va : a specialization of [`basic_json`](../basic_json/index.md), matching the [`basic_json_document`](../basic_json_document/index.md) the view was taken from. -`Editable` -: whether the view is of an editable document, matching the [`basic_json_document`](../basic_json_document/index.md) - it was taken from (optional, `#!cpp false` by default). See [Edits](../basic_json_document/index.md#edits). +`Mode` +: a [`view_mode`](../view_mode.md): whether the view is of an editable document, matching the + [`basic_json_document`](../basic_json_document/index.md) it was taken from (optional, `#!cpp view_mode::read_only` by + default). See [Edits](../basic_json_document/index.md#edits). ## Specializations diff --git a/docs/mkdocs/docs/api/json_document.md b/docs/mkdocs/docs/api/json_document.md index cfa22e80b..2da6fe548 100644 --- a/docs/mkdocs/docs/api/json_document.md +++ b/docs/mkdocs/docs/api/json_document.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using json_document = basic_json_document; +using json_document = basic_json_document; ``` This type is a [`basic_json_document`](basic_json_document/index.md) of the default [`json`](json.md) diff --git a/docs/mkdocs/docs/api/json_editable_document.md b/docs/mkdocs/docs/api/json_editable_document.md index ec5226b78..2acea534c 100644 --- a/docs/mkdocs/docs/api/json_editable_document.md +++ b/docs/mkdocs/docs/api/json_editable_document.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using json_editable_document = basic_json_document; +using json_editable_document = basic_json_document; ``` This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the default diff --git a/docs/mkdocs/docs/api/json_editable_view.md b/docs/mkdocs/docs/api/json_editable_view.md index ddee4e372..37301d3d6 100644 --- a/docs/mkdocs/docs/api/json_editable_view.md +++ b/docs/mkdocs/docs/api/json_editable_view.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using json_editable_view = basic_json_view; +using json_editable_view = basic_json_view; ``` This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a diff --git a/docs/mkdocs/docs/api/json_view.md b/docs/mkdocs/docs/api/json_view.md index 89229ed6f..0998549d9 100644 --- a/docs/mkdocs/docs/api/json_view.md +++ b/docs/mkdocs/docs/api/json_view.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using json_view = basic_json_view; +using json_view = basic_json_view; ``` This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a [`json_document`](json_document.md). diff --git a/docs/mkdocs/docs/api/ordered_json_document.md b/docs/mkdocs/docs/api/ordered_json_document.md index b737c2e4d..40039774d 100644 --- a/docs/mkdocs/docs/api/ordered_json_document.md +++ b/docs/mkdocs/docs/api/ordered_json_document.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using ordered_json_document = basic_json_document; +using ordered_json_document = basic_json_document; ``` This type is a [`basic_json_document`](basic_json_document/index.md) of the [`ordered_json`](ordered_json.md) diff --git a/docs/mkdocs/docs/api/ordered_json_editable_document.md b/docs/mkdocs/docs/api/ordered_json_editable_document.md index cf86307e5..b47dfc3c2 100644 --- a/docs/mkdocs/docs/api/ordered_json_editable_document.md +++ b/docs/mkdocs/docs/api/ordered_json_editable_document.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using ordered_json_editable_document = basic_json_document; +using ordered_json_editable_document = basic_json_document; ``` This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the diff --git a/docs/mkdocs/docs/api/ordered_json_editable_view.md b/docs/mkdocs/docs/api/ordered_json_editable_view.md index 1c9953d57..a42ca42f5 100644 --- a/docs/mkdocs/docs/api/ordered_json_editable_view.md +++ b/docs/mkdocs/docs/api/ordered_json_editable_view.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using ordered_json_editable_view = basic_json_view; +using ordered_json_editable_view = basic_json_view; ``` This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an diff --git a/docs/mkdocs/docs/api/ordered_json_view.md b/docs/mkdocs/docs/api/ordered_json_view.md index 9df72040f..017f2e9a8 100644 --- a/docs/mkdocs/docs/api/ordered_json_view.md +++ b/docs/mkdocs/docs/api/ordered_json_view.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using ordered_json_view = basic_json_view; +using ordered_json_view = basic_json_view; ``` This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an diff --git a/docs/mkdocs/docs/api/view_mode.md b/docs/mkdocs/docs/api/view_mode.md new file mode 100644 index 000000000..c0339b5fb --- /dev/null +++ b/docs/mkdocs/docs/api/view_mode.md @@ -0,0 +1,88 @@ +# nlohmann::view_mode + +Defined in header `` + +```cpp +enum class view_mode : std::uint8_t +{ + read_only, + editable +}; +``` + +The second template parameter of [`basic_json_document`](basic_json_document/index.md) and +[`basic_json_view`](basic_json_view/index.md): whether a document can be changed after parsing, and whether a view is +of such a document. + +`read_only` +: the default: the document is parsed once and then only read. It carries none of the bookkeeping edits need, and + calling [`set`](basic_json_document/set.md), [`push_back`](basic_json_document/push_back.md), + [`insert`](basic_json_document/insert.md), or [`erase`](basic_json_document/erase.md) on it fails to compile. + +`editable` +: the document also offers [`set`](basic_json_document/set.md), [`push_back`](basic_json_document/push_back.md), + [`insert`](basic_json_document/insert.md), and [`erase`](basic_json_document/erase.md); its views see every edit, + see [Edits](basic_json_document/index.md#edits). + +The shortcuts name both modes: [`json_document`](json_document.md) and [`json_view`](json_view.md) are read-only, +[`json_editable_document`](json_editable_document.md) and [`json_editable_view`](json_editable_view.md) are editable +(and likewise for `ordered_json`). + +## Notes + +The mode is a compile-time property of the document's and the view's type, not a setting of a call. A read-only view +reads a container in place, while a view of an editable document follows the links that edits leave behind. A view of +a read-only document converts implicitly to a view of an editable document of the same `BasicJsonType`: a read-only +document has no edits, so the converted view reads it like an editable document before its first edit (at the cost of +one check per access). There is no conversion the other way, because a read-only view of an editable document would +read wrong values after the next edit. + +A function that takes a [`json_editable_view`](json_editable_view.md) therefore accepts the views of both modes. A +function template deduces the mode from its argument instead, without conversions, so it names the modes it accepts in +its signature: + +```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); +``` + +A function that takes a [`json_view`](json_view.md) accepts neither an [`ordered_json_view`](ordered_json_view.md) +nor a [`json_editable_view`](json_editable_view.md). Views of the two modes compare with +[`operator==`](basic_json_view/operator_eq.md). + +## Examples + +??? example + + The example below shows a function template that accepts the views of both modes, one that accepts only views of + editable documents, and a function taking a `json_editable_view` that also accepts a view of a read-only document. + + ```cpp + --8<-- "examples/view_mode.cpp" + ``` + + Output: + + ``` + --8<-- "examples/view_mode.output" + ``` + +## See also + +- [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` +- [(constructor)](basic_json_view/basic_json_view.md) - the conversion from a read-only to an editable view + +## Version history + +Added in version 3.13.0. diff --git a/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp b/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp index 9d7821066..7089466e4 100644 --- a/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp +++ b/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp @@ -5,7 +5,7 @@ using nlohmann::ordered_json_editable_document; int main() { - // ordered_json_editable_document is basic_json_document + // ordered_json_editable_document is basic_json_document ordered_json_editable_document doc = ordered_json_editable_document::parse(R"({"z": 1, "a": 2, "m": 3})"); doc.set(doc.root(), "b", 4); // set() always appends a new member at the end diff --git a/docs/mkdocs/docs/examples/view_mode.cpp b/docs/mkdocs/docs/examples/view_mode.cpp new file mode 100644 index 000000000..5cdd7718b --- /dev/null +++ b/docs/mkdocs/docs/examples/view_mode.cpp @@ -0,0 +1,36 @@ +#include +#include + +using nlohmann::basic_json_view; +using nlohmann::view_mode; + +// accepts the views of read-only and of editable documents, of json and of ordered_json +template +std::size_t count_members(basic_json_view v) +{ + return v.is_object() ? v.size() : 0; +} + +// accepts only the views of editable documents +template +bool is_edited(basic_json_view v) +{ + return v.is_string() && v.get_string() == "edited"; +} + +// a function (no template) taking a json_editable_view also accepts the view of a read-only document +std::size_t size_of(nlohmann::json_editable_view v) +{ + return v.size(); +} + +int main() +{ + const nlohmann::json_document doc = nlohmann::json_document::parse(R"({"a": 1, "b": 2})"); + nlohmann::json_editable_document edoc = nlohmann::json_editable_document::parse(R"({"a": 1})"); + edoc.set(edoc.root()["a"], "edited"); + + std::cout << count_members(doc.root()) << ' ' << count_members(edoc.root()) << '\n'; + std::cout << std::boolalpha << is_edited(edoc.root()["a"]) << '\n'; + std::cout << size_of(doc.root()) << ' ' << size_of(edoc.root()) << '\n'; +} diff --git a/docs/mkdocs/docs/examples/view_mode.output b/docs/mkdocs/docs/examples/view_mode.output new file mode 100644 index 000000000..0e56385a0 --- /dev/null +++ b/docs/mkdocs/docs/examples/view_mode.output @@ -0,0 +1,3 @@ +2 1 +true +2 1 diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 4f0938273..4b5498af5 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -252,14 +252,14 @@ the stream's `width`/`fill` for indentation. ## Editing a document Everything above is read-only: a `json_document`/`json_view` lets you look at a parsed text without copying it, but -not change it. [`basic_json_document`](../api/basic_json_document/index.md) -- more conveniently +not change it. [`basic_json_document`](../api/basic_json_document/index.md) -- more conveniently spelled [`json_editable_document`](../api/json_editable_document.md) or [`ordered_json_editable_document`](../api/ordered_json_editable_document.md) -- also lets you [`set`](../api/basic_json_document/set.md) a value, [`push_back`](../api/basic_json_document/push_back.md) onto or [`insert`](../api/basic_json_document/insert.md) into an array, and [`erase`](../api/basic_json_document/erase.md) an object member or an array element, still without ever building a `basic_json` tree for parts you do not touch. -`#!cpp Editable` defaults to `#!cpp false`, so `json_document`/`ordered_json_document` are unaffected -- they carry +The [mode](../api/view_mode.md) defaults to `#!cpp view_mode::read_only`, so `json_document`/`ordered_json_document` are unaffected -- they carry none of the bookkeeping edits need, and calling `set`/`push_back`/`insert`/`erase` on one is a compile error, not a runtime one. diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index f2b99cf3a..66bac7e80 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -411,6 +411,7 @@ nav: - 'ordered_json_editable_view': api/ordered_json_editable_view.md - 'ordered_json_view': api/ordered_json_view.md - 'ordered_map': api/ordered_map.md + - 'view_mode': api/view_mode.md - macros: - 'Overview': api/macros/index.md - 'JSON_ASSERT': api/macros/json_assert.md diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 3e4ea268a..f7b560f50 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -639,11 +639,11 @@ run_found: switch (cur()) { case '{': - open(value_t::object); + open(node_kind::object); ++p; goto obj_first; case '[': - open(value_t::array); + open(node_kind::array); ++p; goto arr_first; default: @@ -665,11 +665,11 @@ run_found: if (NLOHMANN_VIEW_UNLIKELY(!string())) { return false; } \ break; \ case '{': \ - open(value_t::object); \ + open(node_kind::object); \ ++p; \ goto obj_first; \ case '[': \ - open(value_t::array); \ + open(node_kind::array); \ ++p; \ goto arr_first; \ case '-': \ @@ -886,17 +886,17 @@ root_done: NLOHMANN_VIEW_ALWAYS_INLINE bool literal_true() { - return literal("true", value_t::boolean, node_flags::is_true); + return literal("true", node_kind::boolean, node_flags::is_true); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_false() { - return literal("false", value_t::boolean, 0); + return literal("false", node_kind::boolean, 0); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_null() { - return literal("null", value_t::null, 0); + return literal("null", node_kind::null, 0); } /// skip whitespace (and comments); false on a malformed comment @@ -962,7 +962,7 @@ indent_done: /// append a node: (kind, flags, extra, off) and the second word (len, or /// an integer's value); two stores on little-endian targets - NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) + NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) { if (NLOHMANN_VIEW_UNLIKELY(out == cap)) { @@ -978,7 +978,7 @@ indent_done: std::memcpy(reinterpret_cast(n), &first, 8); std::memcpy(reinterpret_cast(n) + 8, &second, 8); #else - n->kind = static_cast(k); + n->kind = k; n->flags = flags; n->extra = extra; n->off = static_cast(off); @@ -990,7 +990,7 @@ indent_done: return n; } - NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k) + NLOHMANN_VIEW_ALWAYS_INLINE void open(node_kind k) { // (base is read after push_node(), which moves the node array when it // grows; in one expression the order of the two is unspecified) @@ -1017,7 +1017,7 @@ indent_done: max_depth = (std::max)(max_depth, depth); cur_idx = idx; cur_count = 0; - cur_is_object = k == value_t::object; + cur_is_object = k == node_kind::object; } NLOHMANN_VIEW_ALWAYS_INLINE void close() @@ -1047,7 +1047,7 @@ indent_done: /// the literal `text` at p (N counts the terminating NUL) template - NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], value_t k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal + NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], node_kind k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal { const std::size_t n = N - 1; if (NLOHMANN_VIEW_UNLIKELY(e - p < static_cast(n) || std::memcmp(p, text, n) != 0)) @@ -1126,23 +1126,23 @@ indent_done: is_float = true; } - value_t kind = value_t::number_float; + node_kind kind = node_kind::number_float; if (!is_float) { // integers that do not fit become floats, as in parse() if (negative) { - kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? value_t::number_float : value_t::number_integer; + kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? node_kind::number_float : node_kind::number_integer; } else { - kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? value_t::number_float : value_t::number_unsigned; + kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? node_kind::number_float : node_kind::number_unsigned; } } // parse() rejects floats that overflow; only numbers whose magnitude // could reach the largest FloatType (1e308 for double, 1e38 for // float) need the conversion - if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == value_t::number_float)) + if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == node_kind::number_float)) { if (builder::float_overflows(s, p)) { @@ -1152,7 +1152,7 @@ indent_done: } const auto layout = static_cast((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8)); auto second = static_cast(p - s); - if (kind != value_t::number_float) + if (kind != node_kind::number_float) { // integers are converted now, while their digits are in cache const std::uint64_t m = int_digits <= 19 ? parse_upto19(int_start, static_cast(int_digits), e) @@ -1175,7 +1175,7 @@ indent_done: p = scan_string_run(p, e); if (NLOHMANN_VIEW_LIKELY(cur() == '"')) { - push_node(value_t::string, 0, 0, static_cast(s - b), static_cast(p - s)); + push_node(node_kind::string, 0, 0, static_cast(s - b), static_cast(p - s)); ++p; return true; } @@ -1185,7 +1185,7 @@ indent_done: return false; } p = r.p; - push_node(value_t::string, node_flags::escaped, 0, r.start, r.len); + push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len); return true; } }; diff --git a/include/nlohmann/detail/view/document_data.hpp b/include/nlohmann/detail/view/document_data.hpp index 55f9d8c76..afef5a7a2 100644 --- a/include/nlohmann/detail/view/document_data.hpp +++ b/include/nlohmann/detail/view/document_data.hpp @@ -232,7 +232,7 @@ struct document_data /// following loads. static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept { - return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n); + return NLOHMANN_VIEW_LIKELY(n->kind != node_kind::link) ? n : follow_link(n); } static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept diff --git a/include/nlohmann/detail/view/edit.hpp b/include/nlohmann/detail/view/edit.hpp index bad8d9da3..f47bf5985 100644 --- a/include/nlohmann/detail/view/edit.hpp +++ b/include/nlohmann/detail/view/edit.hpp @@ -26,12 +26,10 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN -template -class basic_json_view; - namespace detail { namespace view @@ -120,15 +118,15 @@ class editor View set(const View& object, string_view_t key, V&& value) { node* const o = own(object); - if (o->kind != static_cast(value_t::object) && o->kind != static_cast(value_t::null)) + if (o->kind != node_kind::object && o->kind != node_kind::null) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); } check_utf8(key.data(), key.size()); const encoded e = encode(std::forward(value)); - if (o->kind == static_cast(value_t::null)) + if (o->kind == node_kind::null) { - become_empty(o, value_t::object); + become_empty(o, node_kind::object); } // an existing member: assign it (and drop later duplicates, so that // lookups, iteration, and materialize() agree) @@ -171,7 +169,7 @@ class editor View set(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); } @@ -188,14 +186,14 @@ class editor View push_back(const View& array, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array) && a->kind != static_cast(value_t::null)) + if (a->kind != node_kind::array && a->kind != node_kind::null) { throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); } const encoded e = encode(std::forward(value)); - if (a->kind == static_cast(value_t::null)) + if (a->kind == node_kind::null) { - become_empty(a, value_t::array); + become_empty(a, node_kind::array); } node* const slot = new_slot(e); node* const h = block_of(m_doc, a, 1); @@ -212,7 +210,7 @@ class editor View insert(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); } @@ -232,7 +230,7 @@ class editor std::size_t erase(const View& object, string_view_t key) { node* const o = own(object); - if (o->kind != static_cast(value_t::object)) + if (o->kind != node_kind::object) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); } @@ -250,7 +248,7 @@ class editor void erase(const View& array, std::size_t idx) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); } @@ -324,11 +322,11 @@ class editor } /// turn a null into an empty array/object in place - static void become_empty(node* n, value_t k) noexcept + static void become_empty(node* n, node_kind k) noexcept { const std::uint8_t linked = n->flags & node_flags::linked; *n = node{}; - n->kind = static_cast(k); + n->kind = k; n->flags = static_cast(node_flags::is_new | linked); n->next = 1; } @@ -405,8 +403,8 @@ class editor template struct is_view : std::false_type {}; - template - struct is_view> : std::true_type {}; + template + struct is_view> : std::true_type {}; template encoded encode(V&& v) @@ -421,8 +419,8 @@ class editor } /// a view of any document (copied; nothing is shared with it) - template - encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) + template + encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) { if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) { @@ -434,8 +432,8 @@ class editor r.scalar = copy_scalar(*v.m_doc, *v.m_node); return r; } - r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); - fill_nodes(*v.m_doc, v.m_node, r.region); + r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); + fill_nodes(*v.m_doc, v.m_node, r.region); edit_state_of(m_doc).regions.emplace(r.region, nullptr); return r; } @@ -457,14 +455,14 @@ class editor encoded encode_impl(std::nullptr_t /*unused*/, encode_tag<2> /*null*/) { encoded r; - r.scalar = plain_node(value_t::null); + r.scalar = plain_node(node_kind::null); return r; } encoded encode_impl(bool b, encode_tag<3> /*boolean*/) { encoded r; - r.scalar = plain_node(value_t::boolean); + r.scalar = plain_node(node_kind::boolean); r.scalar.flags = static_cast(r.scalar.flags | (b ? node_flags::is_true : 0)); return r; } @@ -493,10 +491,10 @@ class editor return encode_impl(BasicJsonType(std::forward(x)), encode_tag<1> {}); } - static node plain_node(value_t k) noexcept + static node plain_node(node_kind k) noexcept { node n{}; - n.kind = static_cast(k); + n.kind = k; n.flags = node_flags::is_new; return n; } @@ -510,19 +508,19 @@ class editor template node number_node(T x, std::integral_constant /*signed*/) { - return integer_node(static_cast(static_cast(x)), value_t::number_integer); + return integer_node(static_cast(static_cast(x)), node_kind::number_integer); } template node number_node(T x, std::integral_constant /*unsigned*/) { - return integer_node(static_cast(x), value_t::number_unsigned); + return integer_node(static_cast(x), node_kind::number_unsigned); } /// an integer with its canonical token in the edit arena - node integer_node(std::uint64_t bits, value_t k) + node integer_node(std::uint64_t bits, node_kind k) { - const bool negative = k == value_t::number_integer && static_cast(bits) < 0; + const bool negative = k == node_kind::number_integer && static_cast(bits) < 0; std::uint64_t magnitude = negative ? 0 - bits : bits; std::array buf{}; char* p = buf.data() + buf.size(); // NOLINT(misc-const-correctness): digits are written through p @@ -541,7 +539,7 @@ class editor n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, p, len); // number_length() adds one for the sign of number_integer nodes - n.extra = static_cast(k == value_t::number_integer ? len - 1 : len); + n.extra = static_cast(k == node_kind::number_integer ? len - 1 : len); set_integer_bits(n, bits); return n; } @@ -563,7 +561,7 @@ class editor { text = BasicJsonType(x).dump(); } - node n = plain_node(value_t::number_float); + node n = plain_node(node_kind::number_float); n.flags = static_cast(n.flags | node_flags::edited); n.extra = 0xFFFFu; // (the digit layout is not recorded) n.off = append_text(m_doc, text.data(), text.size()); @@ -578,7 +576,7 @@ class editor { throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE } - node n = plain_node(value_t::string); + node n = plain_node(node_kind::string); n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, s, len); n.len = static_cast(len); @@ -600,10 +598,10 @@ class editor return string_node(from.str(n), n.len); case value_t::number_integer: case value_t::number_unsigned: - return integer_node(integer_bits(n), static_cast(n.kind)); + return integer_node(integer_bits(n), n.kind); case value_t::number_float: { - node r = plain_node(value_t::number_float); + node r = plain_node(node_kind::number_float); r.flags = static_cast(r.flags | node_flags::edited); r.off = append_text(m_doc, from.str(n), n.len); r.len = n.len; @@ -612,7 +610,7 @@ class editor } case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (n.flags & node_flags::is_true)); return r; } @@ -622,7 +620,7 @@ class editor case value_t::binary: case value_t::discarded: default: - return plain_node(value_t::null); + return plain_node(node_kind::null); } } @@ -631,17 +629,17 @@ class editor switch (j.type()) { case value_t::null: - return plain_node(value_t::null); + return plain_node(node_kind::null); case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (j.template get() ? node_flags::is_true : 0)); return r; } case value_t::number_integer: - return integer_node(static_cast(static_cast(j.template get())), value_t::number_integer); + return integer_node(static_cast(static_cast(j.template get())), node_kind::number_integer); case value_t::number_unsigned: - return integer_node(static_cast(j.template get()), value_t::number_unsigned); + return integer_node(static_cast(j.template get()), node_kind::number_unsigned); case value_t::number_float: return float_node(j.template get()); case value_t::string: @@ -681,7 +679,7 @@ class editor ++r; if (is_container(*n)) { - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object)}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object}); } // the next value: close finished containers, then step over the key for (;;) @@ -728,9 +726,9 @@ class editor if (is_container(*n)) { node* const self = out++; - *self = plain_node(static_cast(n->kind)); + *self = plain_node(n->kind); self->len = n->len; - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object), self}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object, self}); } else { @@ -819,7 +817,7 @@ class editor if (n->is_structured()) { node* const self = out++; - *self = plain_node(n->type()); + *self = plain_node(static_cast(n->type())); self->len = static_cast(n->size()); open.push_back(frame{n->cbegin(), n->cend(), n->is_object(), self}); } diff --git a/include/nlohmann/detail/view/edit_storage.hpp b/include/nlohmann/detail/view/edit_storage.hpp index 9a45bfebd..e5b7ddb90 100644 --- a/include/nlohmann/detail/view/edit_storage.hpp +++ b/include/nlohmann/detail/view/edit_storage.hpp @@ -27,7 +27,7 @@ // live in chunks that never move; strings and number tokens written by edits // live in the edit arena. An array/object whose elements change gets // node_flags::moved: its elements then live in a separate sequence (a header -// node, then the entries), whose entries link to the values (kind_link). +// node, then the entries), whose entries link to the values (node_kind::link). NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -185,7 +185,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra) return nh; } reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before) - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; const std::size_t used = 1 + (static_cast(n->len) * (object ? 2 : 1)); const std::size_t cap = used + extra; node* const h = alloc_nodes(d, cap); @@ -251,7 +251,7 @@ inline node* find_parent(const document_data& d, const node* target) { return nullptr; // LCOV_EXCL_LINE (the value is inside c) } - const bool object = c->kind == static_cast(value_t::object); + const bool object = c->kind == node_kind::object; const node* down = nullptr; for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;) { diff --git a/include/nlohmann/detail/view/image.hpp b/include/nlohmann/detail/view/image.hpp index af9994712..c13d269e1 100644 --- a/include/nlohmann/detail/view/image.hpp +++ b/include/nlohmann/detail/view/image.hpp @@ -117,7 +117,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.len = 0; // counted below r.next = 0; // set when the container is complete - stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast(value_t::object)}); + stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == node_kind::object}); break; case value_t::string: r = string_node(*v); @@ -138,7 +138,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i')) { r = node{}; // nan and infinity: null, as dump() writes them - r.kind = static_cast(value_t::null); + r.kind = node_kind::null; break; } r.off = static_cast(d.size + text_tail.size()); @@ -221,7 +221,7 @@ inline std::vector save_image(const document_data& d) // extra of an object, and the same document saves to the same bytes for (std::size_t i = 0; i < count; ++i) { - if (nodes[i].kind == static_cast(value_t::object) && nodes[i].extra != 0) + if (nodes[i].kind == node_kind::object && nodes[i].extra != 0) { std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra)); } @@ -337,7 +337,7 @@ inline bool float_token_finite(const unsigned char* s, std::size_t len, const nu if (static_cast(t.int_digits) + t.exponent > 300) { node n{}; - n.kind = static_cast(value_t::number_float); + n.kind = node_kind::number_float; n.len = static_cast(len); const auto v = float_value(reinterpret_cast(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) return v <= (std::numeric_limits::max)() && v >= -(std::numeric_limits::max)(); @@ -358,7 +358,7 @@ inline bool check_integer(const node& n, const unsigned char* text) } // the token's value is the stored one; number_integer nodes of edits can // be non-negative (as basic_json keeps the type of a value) - const bool integer = n.kind == static_cast(value_t::number_integer); + const bool integer = n.kind == node_kind::number_integer; if (t.is_float || t.int_digits > 20 || (t.negative && !integer)) { return false; @@ -540,7 +540,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - if (n.kind != static_cast(value_t::number_float)) + if (n.kind != node_kind::number_float) { return (n.extra >> 8u) == 0; } @@ -581,7 +581,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha const node& n = nodes[i]; if (!stack.empty() && stack.back().object && stack.back().expect_key) { - if (n.kind != static_cast(value_t::string) || !check_string(n)) + if (n.kind != node_kind::string || !check_string(n)) { return false; } @@ -622,7 +622,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { // (the contents of float tokens are checked later; the token of an // integer has at most 256 characters) - if (n.kind == static_cast(value_t::number_float)) + if (n.kind == node_kind::number_float) { floats.push_back(float_range{n.off, n.len, n.extra}); } @@ -640,7 +640,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast(value_t::object), true}); + stack.push_back(frame{i + n.next, n.len, 0, n.kind == node_kind::object, true}); complete = false; break; } @@ -729,7 +729,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t for (std::size_t i = 0; i < count; ++i) { const node& n = d.nodes[i]; - if (n.kind == static_cast(value_t::object) && n.len >= document_data::index_min_members) + if (n.kind == node_kind::object && n.len >= document_data::index_min_members) { d.large_objects.push_back(static_cast(i)); } diff --git a/include/nlohmann/detail/view/lookup.hpp b/include/nlohmann/detail/view/lookup.hpp index e2038a956..f288927bb 100644 --- a/include/nlohmann/detail/view/lookup.hpp +++ b/include/nlohmann/detail/view/lookup.hpp @@ -182,7 +182,7 @@ const node* element_at(const document_data& d, const node* array, std::size_t id template const node* last_child(const document_data& d, const node* container) noexcept { - const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const std::size_t value_offset = container->kind == node_kind::object ? 1 : 0; const node* const end = navigation::end(d, container); const node* last = navigation::first(d, container); if (value_offset == 0 && container->next == container->len + 1u && (!navigation::editable() || (container->flags & node_flags::moved) == 0)) diff --git a/include/nlohmann/detail/view/materialize.hpp b/include/nlohmann/detail/view/materialize.hpp index 62adb30a9..1bab0e896 100644 --- a/include/nlohmann/detail/view/materialize.hpp +++ b/include/nlohmann/detail/view/materialize.hpp @@ -101,7 +101,7 @@ BasicJsonType materialize(const document_data& d, const node* n) case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; if (object) { sax.start_object(n->len); diff --git a/include/nlohmann/detail/view/node.hpp b/include/nlohmann/detail/view/node.hpp index 7a05c8e0a..8987a8860 100644 --- a/include/nlohmann/detail/view/node.hpp +++ b/include/nlohmann/detail/view/node.hpp @@ -41,9 +41,22 @@ struct node_flags static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters }; -/// kind of an entry of an edited sequence that stands for a value stored -/// elsewhere (the address of the value's node is kept in the len/next bytes) -constexpr std::uint8_t kind_link = 10; +/// the kind of a node: the value_t of its value, or link +enum class node_kind : std::uint8_t +{ + null = static_cast(value_t::null), + object = static_cast(value_t::object), + array = static_cast(value_t::array), + string = static_cast(value_t::string), // NOLINT(build/include_what_you_use): an enumerator, not std::string + boolean = static_cast(value_t::boolean), + number_integer = static_cast(value_t::number_integer), + number_unsigned = static_cast(value_t::number_unsigned), + number_float = static_cast(value_t::number_float), + /// an entry of an edited sequence that stands for a value stored elsewhere + /// (the address of the value's node is kept in the len/next bytes); placed + /// after value_t::discarded, which no node ever has + link = static_cast(value_t::discarded) + 1, +}; /// One entry of the flat index, in document order. An object's members are /// stored as key node followed by the value's subtree. Integers keep their @@ -51,7 +64,7 @@ constexpr std::uint8_t kind_link = 10; /// always the next one, and the token length follows from `extra`). struct node { - std::uint8_t kind; ///< value_t, or kind_link + node_kind kind; ///< the kind of the value, or link std::uint8_t flags; ///< node_flags std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved @@ -60,13 +73,13 @@ struct node }; static_assert(sizeof(node) == 16, "node must stay 16 bytes"); -// the node kinds are value_t values; the test below (object and array are 1 -// and 2) and those of the number kinds depend on this numbering -static_assert(static_cast(value_t::null) == 0 && static_cast(value_t::object) == 1 - && static_cast(value_t::array) == 2 && static_cast(value_t::string) == 3 - && static_cast(value_t::boolean) == 4 && static_cast(value_t::number_integer) == 5 - && static_cast(value_t::number_unsigned) == 6 && static_cast(value_t::number_float) == 7, - "the node format depends on the numbering of value_t"); +// images store the node kinds as bytes, and the test below (object and array +// are 1 and 2) and those of the number kinds depend on this numbering +static_assert(static_cast(node_kind::null) == 0 && static_cast(node_kind::object) == 1 + && static_cast(node_kind::array) == 2 && static_cast(node_kind::string) == 3 + && static_cast(node_kind::boolean) == 4 && static_cast(node_kind::number_integer) == 5 + && static_cast(node_kind::number_unsigned) == 6 && static_cast(node_kind::number_float) == 7, + "the node format depends on the numbering of node_kind"); NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept { @@ -86,7 +99,7 @@ inline void make_link(node& n, node* target) noexcept { target->flags = static_cast(target->flags | node_flags::linked); n = node{}; - n.kind = kind_link; + n.kind = node_kind::link; std::memcpy(reinterpret_cast(&n) + 8, static_cast(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } @@ -116,8 +129,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noex /// token length of a number node NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept { - return n.kind == static_cast(value_t::number_float) ? n.len - : (n.extra & 0xFFu) + (n.kind == static_cast(value_t::number_integer) ? 1u : 0u); + return n.kind == node_kind::number_float ? n.len + : (n.extra & 0xFFu) + (n.kind == node_kind::number_integer ? 1u : 0u); } /// estimated number of nodes for an input of `size` bytes (one node per ~12 diff --git a/include/nlohmann/detail/view/serializer.hpp b/include/nlohmann/detail/view/serializer.hpp index 76df8f89b..b41ac683f 100644 --- a/include/nlohmann/detail/view/serializer.hpp +++ b/include/nlohmann/detail/view/serializer.hpp @@ -874,7 +874,7 @@ class view_serializer case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; room(8); if (n->len == 0) { diff --git a/include/nlohmann/detail/view/view_mode.hpp b/include/nlohmann/detail/view/view_mode.hpp new file mode 100644 index 000000000..3aeccd0b9 --- /dev/null +++ b/include/nlohmann/detail/view/view_mode.hpp @@ -0,0 +1,52 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // uint8_t +#include // false_type, true_type + +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/// whether a basic_json_document and its views can be edited +enum class view_mode : std::uint8_t +{ + read_only, ///< parsed once, then only read (the default) + editable, ///< also has set(), push_back(), insert(), and erase() +}; + +template +class basic_json_view; + +namespace detail +{ +namespace view +{ + +/// whether two view modes differ +template +struct is_other_mode : std::true_type {}; + +template +struct is_other_mode : std::false_type {}; + +/// whether a view of mode From converts implicitly to one of mode To: a +/// read-only document has no edits, so a view of it is read correctly as a +/// view of an editable document that has not been edited yet +template +struct is_widening_mode : std::false_type {}; + +template<> +struct is_widening_mode : std::true_type {}; + +} // namespace view +} // namespace detail + +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 85026008a..42a8cb99e 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -84,6 +84,7 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN @@ -93,13 +94,16 @@ NLOHMANN_JSON_NAMESPACE_BEGIN Trivially copyable (two pointers). Valid as long as the document is alive and has not been re-parsed, and as long as a borrowed source text is alive. */ -template +template class basic_json_view { + /// whether this is a view of an editable document + static constexpr bool is_editable = Mode == view_mode::editable; + using node = detail::view::node; using document_data = detail::view::document_data; /// how the index is walked (with edits only for editable documents) - using navigation = detail::view::navigation; + using navigation = detail::view::navigation; public: using value_t = detail::value_t; @@ -120,6 +124,15 @@ class basic_json_view /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; + /// a view of a read-only document as a view of an editable one, so that a + /// function taking an editable view accepts both (a read-only document has + /// no edits; the view reads it as an editable document before its first + /// edit, at the cost of one check per access) + template::value, int> = 0> + basic_json_view(const basic_json_view& other) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : m_doc(other.m_doc), m_node(other.m_node) + {} + ////////// // type // ////////// @@ -446,7 +459,7 @@ class basic_json_view if (is_structured() && m_node->len != 0) { return unedited() ? basic_json_view(m_doc, detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0)) - : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); + : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); } return front(); } @@ -504,7 +517,7 @@ class basic_json_view /// more than once); 0 if this is not an object size_type count(string_view_t key) const { - return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; + return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; } size_type count(const char* key) const @@ -646,7 +659,7 @@ class basic_json_view } else { - detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); + detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); } return out; } @@ -682,14 +695,14 @@ class basic_json_view } /// a view of an editable document compares with one of a read-only document - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator==(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator==(const basic_json_view& a, const basic_json_view& b) { - return detail::view::equal(side(a), detail::view::view_side>(b)); + return detail::view::equal(side(a), detail::view::view_side>(b)); } - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator!=(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator!=(const basic_json_view& a, const basic_json_view& b) { return !(a == b); } @@ -727,7 +740,7 @@ class basic_json_view { return BasicJsonType(value_t::discarded); } - return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); + return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); } /// byte offset of this value in the source text (for strings: of the @@ -740,8 +753,8 @@ class basic_json_view } private: - template friend class basic_json_document; - template friend class basic_json_view; + template friend class basic_json_document; + template friend class basic_json_view; template friend class detail::view::editor; friend iterator; @@ -757,11 +770,11 @@ class basic_json_view } /// the template argument as a runtime condition: testing it directly in - /// `Editable && ...` makes the condition constant, which MSVC reports as + /// `is_editable && ...` makes the condition constant, which MSVC reports as /// C4127 static bool editable() noexcept { - return Editable; + return is_editable; } /// the number of source bytes of this value (estimated for values with @@ -810,7 +823,7 @@ class basic_json_view const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); } - const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); } @@ -824,7 +837,7 @@ class basic_json_view /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const { - return find_key(key, std::integral_constant {}); + return find_key(key, std::integral_constant {}); } NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const @@ -846,7 +859,7 @@ class basic_json_view { return basic_json_view(m_doc, detail::view::element_at(*m_doc, m_node, idx)); } - return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); + return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); } // --- get() dispatch --- @@ -952,16 +965,19 @@ Borrowed parses keep a pointer to the caller's text, which must outlive the document. Owned parses (parse_copy, rvalue std::string, streams, and inputs that are not contiguous byte ranges) keep their own copy. */ -template +template class basic_json_document { + /// whether the document can be edited + static constexpr bool is_editable = Mode == view_mode::editable; + using document_data = detail::view::document_data; static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8, "json_view supports 64-bit integer types only"); public: - using view_type = basic_json_view; + using view_type = basic_json_view; using value_t = detail::value_t; /// an empty (discarded) document @@ -1357,7 +1373,7 @@ class basic_json_document detail::view::editor editor() { - static_assert(Editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); + static_assert(is_editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) { detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); @@ -1634,21 +1650,21 @@ class basic_json_document }; /// a parsed JSON text for json -using json_document = basic_json_document; +using json_document = basic_json_document; /// a value of a json_document -using json_view = basic_json_view; +using json_view = basic_json_view; /// a parsed JSON text for ordered_json -using ordered_json_document = basic_json_document; +using ordered_json_document = basic_json_document; /// a value of an ordered_json_document -using ordered_json_view = basic_json_view; +using ordered_json_view = basic_json_view; /// an editable parsed JSON text for json -using json_editable_document = basic_json_document; +using json_editable_document = basic_json_document; /// a value of a json_editable_document -using json_editable_view = basic_json_view; +using json_editable_view = basic_json_view; /// an editable parsed JSON text for ordered_json -using ordered_json_editable_document = basic_json_document; +using ordered_json_editable_document = basic_json_document; /// a value of an ordered_json_editable_document -using ordered_json_editable_view = basic_json_view; +using ordered_json_editable_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 338b55492..1fee76e63 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -239,9 +239,22 @@ struct node_flags static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters }; -/// kind of an entry of an edited sequence that stands for a value stored -/// elsewhere (the address of the value's node is kept in the len/next bytes) -constexpr std::uint8_t kind_link = 10; +/// the kind of a node: the value_t of its value, or link +enum class node_kind : std::uint8_t +{ + null = static_cast(value_t::null), + object = static_cast(value_t::object), + array = static_cast(value_t::array), + string = static_cast(value_t::string), // NOLINT(build/include_what_you_use): an enumerator, not std::string + boolean = static_cast(value_t::boolean), + number_integer = static_cast(value_t::number_integer), + number_unsigned = static_cast(value_t::number_unsigned), + number_float = static_cast(value_t::number_float), + /// an entry of an edited sequence that stands for a value stored elsewhere + /// (the address of the value's node is kept in the len/next bytes); placed + /// after value_t::discarded, which no node ever has + link = static_cast(value_t::discarded) + 1, +}; /// One entry of the flat index, in document order. An object's members are /// stored as key node followed by the value's subtree. Integers keep their @@ -249,7 +262,7 @@ constexpr std::uint8_t kind_link = 10; /// always the next one, and the token length follows from `extra`). struct node { - std::uint8_t kind; ///< value_t, or kind_link + node_kind kind; ///< the kind of the value, or link std::uint8_t flags; ///< node_flags std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved @@ -258,13 +271,13 @@ struct node }; static_assert(sizeof(node) == 16, "node must stay 16 bytes"); -// the node kinds are value_t values; the test below (object and array are 1 -// and 2) and those of the number kinds depend on this numbering -static_assert(static_cast(value_t::null) == 0 && static_cast(value_t::object) == 1 - && static_cast(value_t::array) == 2 && static_cast(value_t::string) == 3 - && static_cast(value_t::boolean) == 4 && static_cast(value_t::number_integer) == 5 - && static_cast(value_t::number_unsigned) == 6 && static_cast(value_t::number_float) == 7, - "the node format depends on the numbering of value_t"); +// images store the node kinds as bytes, and the test below (object and array +// are 1 and 2) and those of the number kinds depend on this numbering +static_assert(static_cast(node_kind::null) == 0 && static_cast(node_kind::object) == 1 + && static_cast(node_kind::array) == 2 && static_cast(node_kind::string) == 3 + && static_cast(node_kind::boolean) == 4 && static_cast(node_kind::number_integer) == 5 + && static_cast(node_kind::number_unsigned) == 6 && static_cast(node_kind::number_float) == 7, + "the node format depends on the numbering of node_kind"); NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept { @@ -284,7 +297,7 @@ inline void make_link(node& n, node* target) noexcept { target->flags = static_cast(target->flags | node_flags::linked); n = node{}; - n.kind = kind_link; + n.kind = node_kind::link; std::memcpy(reinterpret_cast(&n) + 8, static_cast(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } @@ -314,8 +327,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noex /// token length of a number node NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept { - return n.kind == static_cast(value_t::number_float) ? n.len - : (n.extra & 0xFFu) + (n.kind == static_cast(value_t::number_integer) ? 1u : 0u); + return n.kind == node_kind::number_float ? n.len + : (n.extra & 0xFFu) + (n.kind == node_kind::number_integer ? 1u : 0u); } /// estimated number of nodes for an input of `size` bytes (one node per ~12 @@ -547,7 +560,7 @@ struct document_data /// following loads. static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept { - return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n); + return NLOHMANN_VIEW_LIKELY(n->kind != node_kind::link) ? n : follow_link(n); } static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept @@ -1830,11 +1843,11 @@ run_found: switch (cur()) { case '{': - open(value_t::object); + open(node_kind::object); ++p; goto obj_first; case '[': - open(value_t::array); + open(node_kind::array); ++p; goto arr_first; default: @@ -1856,11 +1869,11 @@ run_found: if (NLOHMANN_VIEW_UNLIKELY(!string())) { return false; } \ break; \ case '{': \ - open(value_t::object); \ + open(node_kind::object); \ ++p; \ goto obj_first; \ case '[': \ - open(value_t::array); \ + open(node_kind::array); \ ++p; \ goto arr_first; \ case '-': \ @@ -2077,17 +2090,17 @@ root_done: NLOHMANN_VIEW_ALWAYS_INLINE bool literal_true() { - return literal("true", value_t::boolean, node_flags::is_true); + return literal("true", node_kind::boolean, node_flags::is_true); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_false() { - return literal("false", value_t::boolean, 0); + return literal("false", node_kind::boolean, 0); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_null() { - return literal("null", value_t::null, 0); + return literal("null", node_kind::null, 0); } /// skip whitespace (and comments); false on a malformed comment @@ -2153,7 +2166,7 @@ indent_done: /// append a node: (kind, flags, extra, off) and the second word (len, or /// an integer's value); two stores on little-endian targets - NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) + NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) { if (NLOHMANN_VIEW_UNLIKELY(out == cap)) { @@ -2169,7 +2182,7 @@ indent_done: std::memcpy(reinterpret_cast(n), &first, 8); std::memcpy(reinterpret_cast(n) + 8, &second, 8); #else - n->kind = static_cast(k); + n->kind = k; n->flags = flags; n->extra = extra; n->off = static_cast(off); @@ -2181,7 +2194,7 @@ indent_done: return n; } - NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k) + NLOHMANN_VIEW_ALWAYS_INLINE void open(node_kind k) { // (base is read after push_node(), which moves the node array when it // grows; in one expression the order of the two is unspecified) @@ -2208,7 +2221,7 @@ indent_done: max_depth = (std::max)(max_depth, depth); cur_idx = idx; cur_count = 0; - cur_is_object = k == value_t::object; + cur_is_object = k == node_kind::object; } NLOHMANN_VIEW_ALWAYS_INLINE void close() @@ -2238,7 +2251,7 @@ indent_done: /// the literal `text` at p (N counts the terminating NUL) template - NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], value_t k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal + NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], node_kind k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal { const std::size_t n = N - 1; if (NLOHMANN_VIEW_UNLIKELY(e - p < static_cast(n) || std::memcmp(p, text, n) != 0)) @@ -2317,23 +2330,23 @@ indent_done: is_float = true; } - value_t kind = value_t::number_float; + node_kind kind = node_kind::number_float; if (!is_float) { // integers that do not fit become floats, as in parse() if (negative) { - kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? value_t::number_float : value_t::number_integer; + kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? node_kind::number_float : node_kind::number_integer; } else { - kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? value_t::number_float : value_t::number_unsigned; + kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? node_kind::number_float : node_kind::number_unsigned; } } // parse() rejects floats that overflow; only numbers whose magnitude // could reach the largest FloatType (1e308 for double, 1e38 for // float) need the conversion - if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == value_t::number_float)) + if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == node_kind::number_float)) { if (builder::float_overflows(s, p)) { @@ -2343,7 +2356,7 @@ indent_done: } const auto layout = static_cast((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8)); auto second = static_cast(p - s); - if (kind != value_t::number_float) + if (kind != node_kind::number_float) { // integers are converted now, while their digits are in cache const std::uint64_t m = int_digits <= 19 ? parse_upto19(int_start, static_cast(int_digits), e) @@ -2366,7 +2379,7 @@ indent_done: p = scan_string_run(p, e); if (NLOHMANN_VIEW_LIKELY(cur() == '"')) { - push_node(value_t::string, 0, 0, static_cast(s - b), static_cast(p - s)); + push_node(node_kind::string, 0, 0, static_cast(s - b), static_cast(p - s)); ++p; return true; } @@ -2376,7 +2389,7 @@ indent_done: return false; } p = r.p; - push_node(value_t::string, node_flags::escaped, 0, r.start, r.len); + push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len); return true; } }; @@ -3001,7 +3014,7 @@ NLOHMANN_JSON_NAMESPACE_END // live in chunks that never move; strings and number tokens written by edits // live in the edit arena. An array/object whose elements change gets // node_flags::moved: its elements then live in a separate sequence (a header -// node, then the entries), whose entries link to the values (kind_link). +// node, then the entries), whose entries link to the values (node_kind::link). NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -3159,7 +3172,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra) return nh; } reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before) - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; const std::size_t used = 1 + (static_cast(n->len) * (object ? 2 : 1)); const std::size_t cap = used + extra; node* const h = alloc_nodes(d, cap); @@ -3225,7 +3238,7 @@ inline node* find_parent(const document_data& d, const node* target) { return nullptr; // LCOV_EXCL_LINE (the value is inside c) } - const bool object = c->kind == static_cast(value_t::object); + const bool object = c->kind == node_kind::object; const node* down = nullptr; for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;) { @@ -3692,7 +3705,7 @@ const node* element_at(const document_data& d, const node* array, std::size_t id template const node* last_child(const document_data& d, const node* container) noexcept { - const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const std::size_t value_offset = container->kind == node_kind::object ? 1 : 0; const node* const end = navigation::end(d, container); const node* last = navigation::first(d, container); if (value_offset == 0 && container->next == container->len + 1u && (!navigation::editable() || (container->flags & node_flags::moved) == 0)) @@ -3714,10 +3727,32 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // uint8_t +#include // false_type, true_type + +// #include NLOHMANN_JSON_NAMESPACE_BEGIN -template +/// whether a basic_json_document and its views can be edited +enum class view_mode : std::uint8_t +{ + read_only, ///< parsed once, then only read (the default) + editable, ///< also has set(), push_back(), insert(), and erase() +}; + +template class basic_json_view; namespace detail @@ -3725,6 +3760,35 @@ namespace detail namespace view { +/// whether two view modes differ +template +struct is_other_mode : std::true_type {}; + +template +struct is_other_mode : std::false_type {}; + +/// whether a view of mode From converts implicitly to one of mode To: a +/// read-only document has no edits, so a view of it is read correctly as a +/// view of an editable document that has not been edited yet +template +struct is_widening_mode : std::false_type {}; + +template<> +struct is_widening_mode : std::true_type {}; + +} // namespace view +} // namespace detail + +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +namespace detail +{ +namespace view +{ + /// the index of the first true condition (the number of conditions if none is) template struct first_true : std::integral_constant {}; @@ -3808,15 +3872,15 @@ class editor View set(const View& object, string_view_t key, V&& value) { node* const o = own(object); - if (o->kind != static_cast(value_t::object) && o->kind != static_cast(value_t::null)) + if (o->kind != node_kind::object && o->kind != node_kind::null) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); } check_utf8(key.data(), key.size()); const encoded e = encode(std::forward(value)); - if (o->kind == static_cast(value_t::null)) + if (o->kind == node_kind::null) { - become_empty(o, value_t::object); + become_empty(o, node_kind::object); } // an existing member: assign it (and drop later duplicates, so that // lookups, iteration, and materialize() agree) @@ -3859,7 +3923,7 @@ class editor View set(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); } @@ -3876,14 +3940,14 @@ class editor View push_back(const View& array, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array) && a->kind != static_cast(value_t::null)) + if (a->kind != node_kind::array && a->kind != node_kind::null) { throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); } const encoded e = encode(std::forward(value)); - if (a->kind == static_cast(value_t::null)) + if (a->kind == node_kind::null) { - become_empty(a, value_t::array); + become_empty(a, node_kind::array); } node* const slot = new_slot(e); node* const h = block_of(m_doc, a, 1); @@ -3900,7 +3964,7 @@ class editor View insert(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); } @@ -3920,7 +3984,7 @@ class editor std::size_t erase(const View& object, string_view_t key) { node* const o = own(object); - if (o->kind != static_cast(value_t::object)) + if (o->kind != node_kind::object) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); } @@ -3938,7 +4002,7 @@ class editor void erase(const View& array, std::size_t idx) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); } @@ -4012,11 +4076,11 @@ class editor } /// turn a null into an empty array/object in place - static void become_empty(node* n, value_t k) noexcept + static void become_empty(node* n, node_kind k) noexcept { const std::uint8_t linked = n->flags & node_flags::linked; *n = node{}; - n->kind = static_cast(k); + n->kind = k; n->flags = static_cast(node_flags::is_new | linked); n->next = 1; } @@ -4093,8 +4157,8 @@ class editor template struct is_view : std::false_type {}; - template - struct is_view> : std::true_type {}; + template + struct is_view> : std::true_type {}; template encoded encode(V&& v) @@ -4109,8 +4173,8 @@ class editor } /// a view of any document (copied; nothing is shared with it) - template - encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) + template + encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) { if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) { @@ -4122,8 +4186,8 @@ class editor r.scalar = copy_scalar(*v.m_doc, *v.m_node); return r; } - r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); - fill_nodes(*v.m_doc, v.m_node, r.region); + r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); + fill_nodes(*v.m_doc, v.m_node, r.region); edit_state_of(m_doc).regions.emplace(r.region, nullptr); return r; } @@ -4145,14 +4209,14 @@ class editor encoded encode_impl(std::nullptr_t /*unused*/, encode_tag<2> /*null*/) { encoded r; - r.scalar = plain_node(value_t::null); + r.scalar = plain_node(node_kind::null); return r; } encoded encode_impl(bool b, encode_tag<3> /*boolean*/) { encoded r; - r.scalar = plain_node(value_t::boolean); + r.scalar = plain_node(node_kind::boolean); r.scalar.flags = static_cast(r.scalar.flags | (b ? node_flags::is_true : 0)); return r; } @@ -4181,10 +4245,10 @@ class editor return encode_impl(BasicJsonType(std::forward(x)), encode_tag<1> {}); } - static node plain_node(value_t k) noexcept + static node plain_node(node_kind k) noexcept { node n{}; - n.kind = static_cast(k); + n.kind = k; n.flags = node_flags::is_new; return n; } @@ -4198,19 +4262,19 @@ class editor template node number_node(T x, std::integral_constant /*signed*/) { - return integer_node(static_cast(static_cast(x)), value_t::number_integer); + return integer_node(static_cast(static_cast(x)), node_kind::number_integer); } template node number_node(T x, std::integral_constant /*unsigned*/) { - return integer_node(static_cast(x), value_t::number_unsigned); + return integer_node(static_cast(x), node_kind::number_unsigned); } /// an integer with its canonical token in the edit arena - node integer_node(std::uint64_t bits, value_t k) + node integer_node(std::uint64_t bits, node_kind k) { - const bool negative = k == value_t::number_integer && static_cast(bits) < 0; + const bool negative = k == node_kind::number_integer && static_cast(bits) < 0; std::uint64_t magnitude = negative ? 0 - bits : bits; std::array buf{}; char* p = buf.data() + buf.size(); // NOLINT(misc-const-correctness): digits are written through p @@ -4229,7 +4293,7 @@ class editor n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, p, len); // number_length() adds one for the sign of number_integer nodes - n.extra = static_cast(k == value_t::number_integer ? len - 1 : len); + n.extra = static_cast(k == node_kind::number_integer ? len - 1 : len); set_integer_bits(n, bits); return n; } @@ -4251,7 +4315,7 @@ class editor { text = BasicJsonType(x).dump(); } - node n = plain_node(value_t::number_float); + node n = plain_node(node_kind::number_float); n.flags = static_cast(n.flags | node_flags::edited); n.extra = 0xFFFFu; // (the digit layout is not recorded) n.off = append_text(m_doc, text.data(), text.size()); @@ -4266,7 +4330,7 @@ class editor { throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE } - node n = plain_node(value_t::string); + node n = plain_node(node_kind::string); n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, s, len); n.len = static_cast(len); @@ -4288,10 +4352,10 @@ class editor return string_node(from.str(n), n.len); case value_t::number_integer: case value_t::number_unsigned: - return integer_node(integer_bits(n), static_cast(n.kind)); + return integer_node(integer_bits(n), n.kind); case value_t::number_float: { - node r = plain_node(value_t::number_float); + node r = plain_node(node_kind::number_float); r.flags = static_cast(r.flags | node_flags::edited); r.off = append_text(m_doc, from.str(n), n.len); r.len = n.len; @@ -4300,7 +4364,7 @@ class editor } case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (n.flags & node_flags::is_true)); return r; } @@ -4310,7 +4374,7 @@ class editor case value_t::binary: case value_t::discarded: default: - return plain_node(value_t::null); + return plain_node(node_kind::null); } } @@ -4319,17 +4383,17 @@ class editor switch (j.type()) { case value_t::null: - return plain_node(value_t::null); + return plain_node(node_kind::null); case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (j.template get() ? node_flags::is_true : 0)); return r; } case value_t::number_integer: - return integer_node(static_cast(static_cast(j.template get())), value_t::number_integer); + return integer_node(static_cast(static_cast(j.template get())), node_kind::number_integer); case value_t::number_unsigned: - return integer_node(static_cast(j.template get()), value_t::number_unsigned); + return integer_node(static_cast(j.template get()), node_kind::number_unsigned); case value_t::number_float: return float_node(j.template get()); case value_t::string: @@ -4369,7 +4433,7 @@ class editor ++r; if (is_container(*n)) { - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object)}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object}); } // the next value: close finished containers, then step over the key for (;;) @@ -4416,9 +4480,9 @@ class editor if (is_container(*n)) { node* const self = out++; - *self = plain_node(static_cast(n->kind)); + *self = plain_node(n->kind); self->len = n->len; - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object), self}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object, self}); } else { @@ -4507,7 +4571,7 @@ class editor if (n->is_structured()) { node* const self = out++; - *self = plain_node(n->type()); + *self = plain_node(static_cast(n->type())); self->len = static_cast(n->size()); open.push_back(frame{n->cbegin(), n->cend(), n->is_object(), self}); } @@ -4903,7 +4967,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.len = 0; // counted below r.next = 0; // set when the container is complete - stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast(value_t::object)}); + stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == node_kind::object}); break; case value_t::string: r = string_node(*v); @@ -4924,7 +4988,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i')) { r = node{}; // nan and infinity: null, as dump() writes them - r.kind = static_cast(value_t::null); + r.kind = node_kind::null; break; } r.off = static_cast(d.size + text_tail.size()); @@ -5007,7 +5071,7 @@ inline std::vector save_image(const document_data& d) // extra of an object, and the same document saves to the same bytes for (std::size_t i = 0; i < count; ++i) { - if (nodes[i].kind == static_cast(value_t::object) && nodes[i].extra != 0) + if (nodes[i].kind == node_kind::object && nodes[i].extra != 0) { std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra)); } @@ -5123,7 +5187,7 @@ inline bool float_token_finite(const unsigned char* s, std::size_t len, const nu if (static_cast(t.int_digits) + t.exponent > 300) { node n{}; - n.kind = static_cast(value_t::number_float); + n.kind = node_kind::number_float; n.len = static_cast(len); const auto v = float_value(reinterpret_cast(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) return v <= (std::numeric_limits::max)() && v >= -(std::numeric_limits::max)(); @@ -5144,7 +5208,7 @@ inline bool check_integer(const node& n, const unsigned char* text) } // the token's value is the stored one; number_integer nodes of edits can // be non-negative (as basic_json keeps the type of a value) - const bool integer = n.kind == static_cast(value_t::number_integer); + const bool integer = n.kind == node_kind::number_integer; if (t.is_float || t.int_digits > 20 || (t.negative && !integer)) { return false; @@ -5326,7 +5390,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - if (n.kind != static_cast(value_t::number_float)) + if (n.kind != node_kind::number_float) { return (n.extra >> 8u) == 0; } @@ -5367,7 +5431,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha const node& n = nodes[i]; if (!stack.empty() && stack.back().object && stack.back().expect_key) { - if (n.kind != static_cast(value_t::string) || !check_string(n)) + if (n.kind != node_kind::string || !check_string(n)) { return false; } @@ -5408,7 +5472,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { // (the contents of float tokens are checked later; the token of an // integer has at most 256 characters) - if (n.kind == static_cast(value_t::number_float)) + if (n.kind == node_kind::number_float) { floats.push_back(float_range{n.off, n.len, n.extra}); } @@ -5426,7 +5490,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast(value_t::object), true}); + stack.push_back(frame{i + n.next, n.len, 0, n.kind == node_kind::object, true}); complete = false; break; } @@ -5515,7 +5579,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t for (std::size_t i = 0; i < count; ++i) { const node& n = d.nodes[i]; - if (n.kind == static_cast(value_t::object) && n.len >= document_data::index_min_members) + if (n.kind == node_kind::object && n.len >= document_data::index_min_members) { d.large_objects.push_back(static_cast(i)); } @@ -6108,7 +6172,7 @@ BasicJsonType materialize(const document_data& d, const node* n) case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; if (object) { sax.start_object(n->len); @@ -7222,7 +7286,7 @@ class view_serializer case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; room(8); if (n->len == 0) { @@ -7773,6 +7837,8 @@ struct is_direct_value, BasicJsonType, View> : } // namespace detail NLOHMANN_JSON_NAMESPACE_END +// #include + NLOHMANN_JSON_NAMESPACE_BEGIN @@ -7782,13 +7848,16 @@ NLOHMANN_JSON_NAMESPACE_BEGIN Trivially copyable (two pointers). Valid as long as the document is alive and has not been re-parsed, and as long as a borrowed source text is alive. */ -template +template class basic_json_view { + /// whether this is a view of an editable document + static constexpr bool is_editable = Mode == view_mode::editable; + using node = detail::view::node; using document_data = detail::view::document_data; /// how the index is walked (with edits only for editable documents) - using navigation = detail::view::navigation; + using navigation = detail::view::navigation; public: using value_t = detail::value_t; @@ -7809,6 +7878,15 @@ class basic_json_view /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; + /// a view of a read-only document as a view of an editable one, so that a + /// function taking an editable view accepts both (a read-only document has + /// no edits; the view reads it as an editable document before its first + /// edit, at the cost of one check per access) + template::value, int> = 0> + basic_json_view(const basic_json_view& other) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : m_doc(other.m_doc), m_node(other.m_node) + {} + ////////// // type // ////////// @@ -8135,7 +8213,7 @@ class basic_json_view if (is_structured() && m_node->len != 0) { return unedited() ? basic_json_view(m_doc, detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0)) - : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); + : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); } return front(); } @@ -8193,7 +8271,7 @@ class basic_json_view /// more than once); 0 if this is not an object size_type count(string_view_t key) const { - return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; + return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; } size_type count(const char* key) const @@ -8335,7 +8413,7 @@ class basic_json_view } else { - detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); + detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); } return out; } @@ -8371,14 +8449,14 @@ class basic_json_view } /// a view of an editable document compares with one of a read-only document - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator==(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator==(const basic_json_view& a, const basic_json_view& b) { - return detail::view::equal(side(a), detail::view::view_side>(b)); + return detail::view::equal(side(a), detail::view::view_side>(b)); } - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator!=(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator!=(const basic_json_view& a, const basic_json_view& b) { return !(a == b); } @@ -8416,7 +8494,7 @@ class basic_json_view { return BasicJsonType(value_t::discarded); } - return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); + return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); } /// byte offset of this value in the source text (for strings: of the @@ -8429,8 +8507,8 @@ class basic_json_view } private: - template friend class basic_json_document; - template friend class basic_json_view; + template friend class basic_json_document; + template friend class basic_json_view; template friend class detail::view::editor; friend iterator; @@ -8446,11 +8524,11 @@ class basic_json_view } /// the template argument as a runtime condition: testing it directly in - /// `Editable && ...` makes the condition constant, which MSVC reports as + /// `is_editable && ...` makes the condition constant, which MSVC reports as /// C4127 static bool editable() noexcept { - return Editable; + return is_editable; } /// the number of source bytes of this value (estimated for values with @@ -8499,7 +8577,7 @@ class basic_json_view const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); } - const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); } @@ -8513,7 +8591,7 @@ class basic_json_view /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const { - return find_key(key, std::integral_constant {}); + return find_key(key, std::integral_constant {}); } NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const @@ -8535,7 +8613,7 @@ class basic_json_view { return basic_json_view(m_doc, detail::view::element_at(*m_doc, m_node, idx)); } - return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); + return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); } // --- get() dispatch --- @@ -8641,16 +8719,19 @@ Borrowed parses keep a pointer to the caller's text, which must outlive the document. Owned parses (parse_copy, rvalue std::string, streams, and inputs that are not contiguous byte ranges) keep their own copy. */ -template +template class basic_json_document { + /// whether the document can be edited + static constexpr bool is_editable = Mode == view_mode::editable; + using document_data = detail::view::document_data; static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8, "json_view supports 64-bit integer types only"); public: - using view_type = basic_json_view; + using view_type = basic_json_view; using value_t = detail::value_t; /// an empty (discarded) document @@ -9046,7 +9127,7 @@ class basic_json_document detail::view::editor editor() { - static_assert(Editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); + static_assert(is_editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) { detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); @@ -9323,21 +9404,21 @@ class basic_json_document }; /// a parsed JSON text for json -using json_document = basic_json_document; +using json_document = basic_json_document; /// a value of a json_document -using json_view = basic_json_view; +using json_view = basic_json_view; /// a parsed JSON text for ordered_json -using ordered_json_document = basic_json_document; +using ordered_json_document = basic_json_document; /// a value of an ordered_json_document -using ordered_json_view = basic_json_view; +using ordered_json_view = basic_json_view; /// an editable parsed JSON text for json -using json_editable_document = basic_json_document; +using json_editable_document = basic_json_document; /// a value of a json_editable_document -using json_editable_view = basic_json_view; +using json_editable_view = basic_json_view; /// an editable parsed JSON text for ordered_json -using ordered_json_editable_document = basic_json_document; +using ordered_json_editable_document = basic_json_document; /// a value of an ordered_json_editable_document -using ordered_json_editable_view = basic_json_view; +using ordered_json_editable_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 6935981fb..1074f7018 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -592,7 +592,7 @@ TEST_CASE("json_view node integer bits") }) { CAPTURE(v) - n.kind = 0x5A; + n.kind = static_cast(0x5A); n.flags = 0xA5; n.extra = 0x1234; n.off = 0x89ABCDEFu; @@ -601,7 +601,7 @@ TEST_CASE("json_view node integer bits") CHECK(n.len == static_cast(v)); CHECK(n.next == static_cast(v >> 32)); // the other fields are untouched - CHECK(n.kind == 0x5A); + CHECK(static_cast(n.kind) == 0x5A); CHECK(n.flags == 0xA5); CHECK(n.extra == 0x1234); CHECK(n.off == 0x89ABCDEFu); diff --git a/tests/src/unit-json_view_edit_values.cpp b/tests/src/unit-json_view_edit_values.cpp index 59aee3a9d..39db8d0ba 100644 --- a/tests/src/unit-json_view_edit_values.cpp +++ b/tests/src/unit-json_view_edit_values.cpp @@ -26,12 +26,119 @@ using nlohmann::json_editable_view; #include #include #include +#include #include #include // These tests were split off unit-json_view_edit.cpp, whose object file got too // large for the MinGW linker (see json_view_test_helpers.hpp). +namespace +{ +// the three ways a function can accept views: editable ones, read-only ones, or both +template +int accepts_editable(nlohmann::basic_json_view /*unused*/) +{ + return 1; +} + +template +int accepts_read_only(nlohmann::basic_json_view /*unused*/) +{ + return 2; +} + +template +int accepts_any(nlohmann::basic_json_view /*unused*/) +{ + return Mode == nlohmann::view_mode::editable ? 3 : 4; +} + +template +auto overload(View v) -> decltype(accepts_editable(v)) +{ + return accepts_editable(v); +} + +template +auto overload(View v) -> decltype(accepts_read_only(v)) +{ + return accepts_read_only(v); +} + +// a function (no template) that takes an editable view also accepts a read-only one +std::size_t sum_of(json_editable_view v) +{ + std::size_t sum = 0; + for (const json_editable_view x : v.at("values")) + { + sum += x.get(); + } + return sum; +} +} // namespace + +TEST_CASE("json_view modes") +{ + using nlohmann::basic_json_document; + using nlohmann::basic_json_view; + using nlohmann::ordered_json; + using nlohmann::view_mode; + + SECTION("read-only is the default, and the shortcuts name the modes") + { + static_assert(std::is_same, basic_json_view>::value, "default mode"); + static_assert(std::is_same, basic_json_document>::value, "default mode"); + static_assert(std::is_same>::value, "json_view"); + static_assert(std::is_same>::value, "json_editable_view"); + static_assert(std::is_same>::value, "json_editable_document"); + static_assert(std::is_same>::value, "ordered_json_editable_view"); + static_assert(std::is_same::value, "view_type"); + static_assert(std::is_same::value, "view_type"); + } + + SECTION("a function template selects views by their mode") + { + const json_document d = json_document::parse("[1]"); + const json_editable_document e = json_editable_document::parse("[1]"); + const nlohmann::ordered_json_editable_document o = nlohmann::ordered_json_editable_document::parse("[1]"); + CHECK(overload(e.root()) == 1); + CHECK(overload(o.root()) == 1); + CHECK(overload(d.root()) == 2); + CHECK(accepts_any(e.root()) == 3); + CHECK(accepts_any(d.root()) == 4); + } + + SECTION("a view of a read-only document converts to an editable view, not the other way") + { + static_assert(std::is_convertible::value, "read-only to editable"); + static_assert(!std::is_convertible::value, "not editable to read-only"); + static_assert(!std::is_convertible::value, "same BasicJsonType only"); + + const json_document d = json_document::parse(R"({"values": [1, 2, 3], "name": "a"})"); + json_editable_document e = json_editable_document::parse(R"({"values": [1, 2, 3], "name": "a"})"); + CHECK(sum_of(d.root()) == 6); + CHECK(sum_of(e.root()) == 6); + + const json_editable_view v = d.root(); + CHECK(v.size() == 2); + CHECK(v.at("name").get() == "a"); + CHECK(v.source_offset() == d.root().source_offset()); + CHECK((v == e.root())); + CHECK((v == d.root())); + + e.push_back(e.root().at("values"), 4); + CHECK(sum_of(e.root()) == 10); + CHECK((v != e.root())); + +#if !defined(JSON_NOEXCEPTION) + // the converted view still belongs to the read-only document + CHECK_THROWS_WITH_AS(e.push_back(v.at("values"), 5), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&); +#endif + CHECK(sum_of(d.root()) == 6); + } +} + TEST_CASE("json_view edits: views and values") { SECTION("a value that is no longer part of the document") @@ -100,7 +207,7 @@ TEST_CASE("json_view edits: views and values") // doubles have their own path to the output; other float types are // written as basic_json writes them, non-finite values as null using json_float = nlohmann::basic_json; - using document_float = nlohmann::basic_json_document; + using document_float = nlohmann::basic_json_document; document_float d = document_float::parse("[1.5]"); d.push_back(d.root(), std::numeric_limits::quiet_NaN()); d.push_back(d.root(), -std::numeric_limits::infinity()); diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index eaf27eb9f..e2a738a4a 100644 --- a/tests/src/unit-json_view_image.cpp +++ b/tests/src/unit-json_view_image.cpp @@ -480,7 +480,7 @@ TEST_CASE("json_view images: edited documents") { CAPTURE(i) CHECK((node_at(image, i).flags & nlohmann::detail::view::node_flags::linked) == 0); - CHECK(node_at(image, i).kind != nlohmann::detail::view::kind_link); + CHECK((node_at(image, i).kind != nlohmann::detail::view::node_kind::link)); } for (const image_check check : { @@ -810,13 +810,13 @@ TEST_CASE("json_view images: check") { rejected(corrupted(image, 12, [&](node & n) { - n.kind = kind; + n.kind = static_cast(kind); }), true); } // a key that is not a string rejected(corrupted(image, 1, [](node & n) { - n.kind = 0; + n.kind = nlohmann::detail::view::node_kind::null; n.len = 0; n.off = 0; }), true); @@ -960,11 +960,11 @@ TEST_CASE("json_view images: check") }), true); rejected(corrupted(image, 0, [](node & n) { - n.kind = 2; // an array: the "keys" are values, and the counts do not match + n.kind = nlohmann::detail::view::node_kind::array; // an array: the "keys" are values, and the counts do not match }), true); const std::vector as_array = corrupted(image, 16, [](node & n) { - n.kind = 2; // {} as []: fine + n.kind = nlohmann::detail::view::node_kind::array; // {} as []: fine }); CHECK(load_result(as_array, image_check::full).empty()); const std::string expected_dump = R"({"s":"x\"y","i":-12,"u":7,"f":1.5e+300,"b":true,"n":null,"a":["t",[]]})"; @@ -1013,13 +1013,13 @@ TEST_CASE("json_view images: check") rejected(at_token(u6, 0, '8'), false); // 8 != 7 rejected(corrupted(image, 4, [](node & n) { - n.kind = 6; // "-12" as unsigned: a sign + n.kind = nlohmann::detail::view::node_kind::number_unsigned; // "-12" as unsigned: a sign n.extra = 3; }), false); // a non-negative number_integer (as edits write it): fine const std::vector positive = corrupted(image, 6, [](node & n) { - n.kind = 5; + n.kind = nlohmann::detail::view::node_kind::number_integer; n.extra = 0; }); CHECK(load_result(positive, image_check::full).empty()); @@ -1027,7 +1027,7 @@ TEST_CASE("json_view images: check") CHECK(pos.root()["u"].is_number_integer()); rejected(corrupted(image, 8, [](node & n) { - n.kind = 6; // a float token as integer + n.kind = nlohmann::detail::view::node_kind::number_unsigned; // a float token as integer n.extra = 7; }), false); } @@ -1197,7 +1197,7 @@ TEST_CASE("json_view images: check") { std::vector b = img; node n = node_at(b, i); - n.kind = kind; + n.kind = static_cast(kind); n.extra = extra; set_node(b, i, n); return load_result(b, image_check::full);