diff --git a/docs/mkdocs/docs/api/basic_json_document/accept.md b/docs/mkdocs/docs/api/basic_json_document/accept.md index 322350130..d2eb3862a 100644 --- a/docs/mkdocs/docs/api/basic_json_document/accept.md +++ b/docs/mkdocs/docs/api/basic_json_document/accept.md @@ -12,6 +12,11 @@ Checks whether the input is valid JSON, accepting and rejecting exactly what function never throws an exception for invalid input, and the returned `#!cpp bool` is the only result -- no document is returned. +The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This +function runs the document's index builder (and discards the index), while `BasicJsonType::accept()` runs the SAX +parser with a handler that ignores every event. Use this one if you go on to parse the same input with +[`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain yes/no answer. + ## Template parameters `InputType` diff --git a/docs/mkdocs/docs/api/basic_json_document/basic_json_document.md b/docs/mkdocs/docs/api/basic_json_document/basic_json_document.md index b877e8895..c4bf1c3b6 100644 --- a/docs/mkdocs/docs/api/basic_json_document/basic_json_document.md +++ b/docs/mkdocs/docs/api/basic_json_document/basic_json_document.md @@ -16,8 +16,8 @@ basic_json_document(const basic_json_document&) = delete; 2. Move constructor. Takes over `other`'s index and, if owned, its text; `other` is left as an empty document. Views taken from `other` before the move remain valid, because the index is heap-allocated independently of the `basic_json_document` object. -3. `basic_json_document` is move-only. Copying is disabled because it would either duplicate a potentially large index - and text, or leave two documents claiming to borrow the same buffer. +3. `basic_json_document` is move-only. Copying is disabled because of its cost: a copy would duplicate the whole + index, the decoded-string buffer, and the owned text, while a move is enough in every use we know of. ## Parameters @@ -30,7 +30,7 @@ No-throw guarantee: the default and move constructors never throw exceptions. ## Complexity -Constant, for the default and move constructors. +Constant. ## Examples diff --git a/docs/mkdocs/docs/api/basic_json_document/index.md b/docs/mkdocs/docs/api/basic_json_document/index.md index 89564369a..b64b96e15 100644 --- a/docs/mkdocs/docs/api/basic_json_document/index.md +++ b/docs/mkdocs/docs/api/basic_json_document/index.md @@ -11,13 +11,14 @@ A parsed JSON text, held as a flat index of its values ([16 bytes per value](../../home/architecture.md#node-index-of-json-views)) instead of a tree of `BasicJsonType` values. Strings and numbers stay in the source text; only strings that contain escapes are decoded, into one buffer owned by the document. [`basic_json_view`](../basic_json_view/index.md) is a read-only handle to one value of a -`basic_json_document`; [`materialize()`](../basic_json_view/materialize.md) turns a subtree back into the +`basic_json_document`; [`materialize()`](../basic_json_view/materialize.md) turns a subtree into the `BasicJsonType` value that [`BasicJsonType::parse()`](../basic_json/parse.md) would have produced for it. A document may **borrow** the text it was parsed from (the caller's buffer must then outlive the document) or **own** it (a copy, or an rvalue `#!cpp std::string` that was moved in); see [`owns_source`](owns_source.md). `basic_json_document` -is move-only: copying a document would either duplicate a potentially large index and text, or leave two documents -claiming to borrow the same buffer, so it is disabled. +is move-only: a copy would duplicate the whole index, the decoded-string buffer, and the owned text, and a move is +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. ## Template parameters diff --git a/docs/mkdocs/docs/api/basic_json_document/owns_source.md b/docs/mkdocs/docs/api/basic_json_document/owns_source.md index ab1fdb45e..ede04e369 100644 --- a/docs/mkdocs/docs/api/basic_json_document/owns_source.md +++ b/docs/mkdocs/docs/api/basic_json_document/owns_source.md @@ -4,7 +4,7 @@ bool owns_source() const noexcept; ``` -Returns whether the document holds its own copy of the parsed text, as opposed to borrowing the caller's buffer. +Returns whether the document holds its own copy of the parsed text or is borrowing the caller's buffer. ## Return value diff --git a/docs/mkdocs/docs/api/basic_json_document/parse.md b/docs/mkdocs/docs/api/basic_json_document/parse.md index 972c63752..3e2506264 100644 --- a/docs/mkdocs/docs/api/basic_json_document/parse.md +++ b/docs/mkdocs/docs/api/basic_json_document/parse.md @@ -21,13 +21,15 @@ static basic_json_document parse(IteratorType first, IteratorType last, Both overloads accept the same JSON text as [`BasicJsonType::parse()`](../basic_json/parse.md), with the same `ignore_comments`/`ignore_trailing_commas` options, but build a [`basic_json_document`](index.md) (a flat index into -the input) instead of a tree of `BasicJsonType` values. The input must be byte-oriented (see the template parameters -below): not every input type of `BasicJsonType::parse()` is supported. +the input) instead of a tree of `BasicJsonType` values. They take the same inputs as `BasicJsonType::parse()`. Byte +inputs that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous +iterators) are read into a copy that the document owns -- see the Notes. Use [`parse_copy()`](parse_copy.md) to get an +owned copy of a borrowable input as well. ## Template parameters `InputType` -: A byte-oriented input, one of: +: A compatible input, for instance: - a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of single-byte characters - a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`, @@ -35,12 +37,10 @@ below): not every input type of `BasicJsonType::parse()` is supported. - a container for which `#!cpp obj.data()` and `#!cpp obj.size()` give contiguous single-byte access, e.g. `#!cpp std::vector` or `#!cpp std::vector` - an `#!cpp std::istream` object - - a wide string object (`#!cpp std::wstring`, `#!cpp std::u16string`, `#!cpp std::u32string`), which is converted - to UTF-8 - - Other inputs are not supported: a `#!cpp FILE*`, and pointers to or arrays of wide characters (`#!cpp wchar_t`, - `#!cpp char16_t`, `#!cpp char32_t`) are rejected at compile time by a `#!cpp static_assert`. (Use - [`BasicJsonType::parse()`](../basic_json/parse.md) for these.) + - a `#!cpp FILE*` pointer + - wide characters (`#!cpp wchar_t`, `#!cpp char16_t`, `#!cpp char32_t`), which are converted to UTF-8: a wide + string object (`#!cpp std::wstring`, `#!cpp std::u16string`, `#!cpp std::u32string`), an array (e.g. a wide + string literal), or a pointer to a null-terminated wide string `IteratorType` : an input iterator type, for instance a pair of pointers such as `ptr` and `ptr + len`, or a pair of @@ -78,7 +78,9 @@ discarded; see [`is_discarded`](is_discarded.md). Throws the same exception [`BasicJsonType::parse()`](../basic_json/parse.md) throws for the same input and options -- the same exception id, message, and position -- because on a failing input the library's own parser is run on the -same bytes to produce the diagnostic. Additionally throws +same bytes to produce the diagnostic. This replay runs only for a failing input, and costs about a lexing pass up to +the error (its SAX handler builds no values); with `allow_exceptions` set to `#!cpp false`, it is skipped and a +discarded document is returned instead. Additionally throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the input is 4294967280 bytes (4 GiB minus 16 bytes) or larger, a size [`BasicJsonType::parse()`](../basic_json/parse.md) does not reject. @@ -95,7 +97,7 @@ Linear in the length of the input. | lvalue byte container (`std::string`, `std::vector`, ...), `std::string_view`, C string, character array | **borrowed** -- `input` must outlive the document | | non-const rvalue `#!cpp std::string` | **owned**, moved in without a copy | | other rvalue byte container (including a `#!cpp const` rvalue `#!cpp std::string`) | **owned**, copied | -| stream, wide string, or anything else read through the general input adapter | **owned**, read into a buffer (a stream is read to its end) | +| stream, `#!cpp FILE*`, wide characters, or anything else read through the general input adapter | **owned**, read into a buffer (a stream is read to its end) | For overload (2), a pair of pointers to single-byte integers (e.g. `#!cpp const char*`, `#!cpp std::uint8_t*`) is borrowed. From C++20 on, so is any other contiguous iterator over single bytes, such as diff --git a/docs/mkdocs/docs/api/basic_json_document/read.md b/docs/mkdocs/docs/api/basic_json_document/read.md index ff37014bd..74141a881 100644 --- a/docs/mkdocs/docs/api/basic_json_document/read.md +++ b/docs/mkdocs/docs/api/basic_json_document/read.md @@ -47,7 +47,9 @@ Every view taken from `#!cpp *this` before the call -- including the previous [` whether or not the new parse succeeds; take fresh views from [`root()`](root.md) afterward. `input` is borrowed or owned by the same rules as [`parse()`](parse.md#notes); a document can borrow on one call and -own on the next, since ownership is decided freshly each time. +own on the next, since ownership is decided freshly each time. An input that is copied (see the table in +[`parse()`](parse.md#notes)) is written into the document's previous owned copy, reusing its memory; a moved-in +`#!cpp std::string` replaces it. An integer argument that is not a `#!cpp bool` where the flags are expected, such as `#!cpp read(ptr, len)`, does not compile; see [`parse`](parse.md#notes). diff --git a/docs/mkdocs/docs/api/basic_json_document/root.md b/docs/mkdocs/docs/api/basic_json_document/root.md index 3ccedeb4a..a8f0c0ded 100644 --- a/docs/mkdocs/docs/api/basic_json_document/root.md +++ b/docs/mkdocs/docs/api/basic_json_document/root.md @@ -39,7 +39,8 @@ auto v = doc.root(); `root()` is a cheap handle into the document's index, not a copy of anything; call it as often as needed. The returned view is valid under the same conditions as any other view of the document -- see [Object inspection](../basic_json_view/index.md) -- in particular, it is invalidated by the next -[`read()`](read.md) or [`shrink_to_fit()`](shrink_to_fit.md) on this document. +[`read()`](read.md) or [`shrink_to_fit()`](shrink_to_fit.md) on this document. A view can dangle, like a +`#!cpp std::string_view`; see [Ownership and lifetime](../../features/json_view.md#ownership-and-lifetime). ## Examples diff --git a/docs/mkdocs/docs/api/basic_json_view/is_array.md b/docs/mkdocs/docs/api/basic_json_view/is_array.md index a33176d2b..1149c80e5 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_array.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_array.md @@ -38,7 +38,8 @@ Constant. ## See also - [is_structured](is_structured.md) - return whether the type is structured -- [size](size.md), [empty](empty.md) - the number of elements, and whether there are none +- [size](size.md) - the number of elements +- [empty](empty.md) - whether there are no elements - [`BasicJsonType::is_array`](../basic_json/is_array.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_binary.md b/docs/mkdocs/docs/api/basic_json_view/is_binary.md index 829a1fbb3..3122051f3 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_binary.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_binary.md @@ -4,8 +4,9 @@ bool is_binary() const noexcept; ``` -This function always returns `#!cpp false`: a JSON text has no binary values, so a view can never refer to one. The -function exists for interface parity with [`BasicJsonType::is_binary`](../basic_json/is_binary.md). +This function always returns `#!cpp false`: a document is only ever parsed from JSON text, never from CBOR or the other +binary formats, and a JSON text has no binary values, so a view can never refer to one. The function exists for +interface parity with [`BasicJsonType::is_binary`](../basic_json/is_binary.md). ## Return value diff --git a/docs/mkdocs/docs/api/basic_json_view/is_discarded.md b/docs/mkdocs/docs/api/basic_json_view/is_discarded.md index 22210a5ad..5e99d34db 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_discarded.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_discarded.md @@ -11,7 +11,7 @@ that is itself [discarded](../basic_json_document/is_discarded.md) -- in particu ## Return value -`#!cpp true` if the view is discarded, `#!cpp false` otherwise. +`#!cpp true` if the view or its document is discarded, `#!cpp false` otherwise. ## Exception safety diff --git a/docs/mkdocs/docs/api/basic_json_view/is_object.md b/docs/mkdocs/docs/api/basic_json_view/is_object.md index 5421aeeb1..0aea2b55d 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_object.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_object.md @@ -38,7 +38,8 @@ Constant. ## See also - [is_structured](is_structured.md) - return whether the type is structured -- [size](size.md), [empty](empty.md) - the number of elements, and whether there are none +- [size](size.md) - the number of elements +- [empty](empty.md) - whether there are no elements - [`BasicJsonType::is_object`](../basic_json/is_object.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_string.md b/docs/mkdocs/docs/api/basic_json_view/is_string.md index c40c8ab4b..2425100b2 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_string.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_string.md @@ -37,7 +37,8 @@ Constant. ## See also -- [source_offset](source_offset.md) - byte offset of this value in the document's source text +- [source_offset](source_offset.md) - byte offset of this value in the source text, which differs for strings: an + escaped string has none - [`BasicJsonType::is_string`](../basic_json/is_string.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/materialize.md b/docs/mkdocs/docs/api/basic_json_view/materialize.md index 225af3c9a..5ff007b11 100644 --- a/docs/mkdocs/docs/api/basic_json_view/materialize.md +++ b/docs/mkdocs/docs/api/basic_json_view/materialize.md @@ -14,8 +14,8 @@ if the view is [discarded](is_discarded.md). ## Exception safety -Strong guarantee: if an exception is thrown, there are no changes to the view or the document it refers to (nothing -about either is mutated by this function). +Strong guarantee: there are no changes to the view or the document it refers to (nothing about either is mutated by +this function). ## Exceptions @@ -28,12 +28,13 @@ Linear in the size of the subtree. ## Notes `materialize()` replays the subtree through the same SAX builder [`BasicJsonType::parse()`](../basic_json/parse.md) -uses internally, so the result matches it exactly -- including, for an object, that a repeated key keeps only its +uses, so the result matches it exactly -- including, for an object, that a repeated key keeps only its last value. The replay is iterative, so it is not limited by the call stack the way a naive recursive conversion would be; the JSON nesting depth is limited only by available memory, as for `BasicJsonType::parse()` itself. Unlike parsing with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values -produced by `materialize()` do not carry source positions: there is no lexer run during the replay to record them. +produced by `materialize()` do not carry source positions. The index has the start offset of every value, but not the +end of arrays, objects, and escaped strings, which the positions also need. Calling `materialize()` on the same view repeatedly builds a new, independent `BasicJsonType` value each time; it never caches the result. diff --git a/docs/mkdocs/docs/api/basic_json_view/size.md b/docs/mkdocs/docs/api/basic_json_view/size.md index cd354d2f3..65f15254a 100644 --- a/docs/mkdocs/docs/api/basic_json_view/size.md +++ b/docs/mkdocs/docs/api/basic_json_view/size.md @@ -10,15 +10,15 @@ Returns the number of elements, as [`BasicJsonType::size()`](../basic_json/size. The return value depends on the type and is defined as follows: -| Value type | return value | -|----------------------|-----------------------------| -| null | `0` | -| discarded | `0` | -| boolean | `1` | -| string | `1` | -| number | `1` | -| object | number of key/value pairs | -| array | number of elements | +| Value type | return value | +|------------|---------------------------| +| null | `0` | +| discarded | `0` | +| boolean | `1` | +| string | `1` | +| number | `1` | +| object | number of key/value pairs | +| array | number of elements | ## Exception safety diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 6b49ee2e3..34fc045e3 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -16,8 +16,8 @@ most of the resulting tree is thrown away almost immediately. [`basic_json_document::parse()`](../api/basic_json_document/parse.md) parses the same JSON grammar, with the same options, but instead of a tree it builds a flat index of the values it found: one [16-byte entry](../home/architecture.md#node-index-of-json-views) per value (and one per object key), in document -order. Strings and numbers are not copied out of the input; they stay in the source text, and are only decoded when -actually needed (for a string, only if it contains escape sequences, into one shared buffer owned by the document). +order. Strings and numbers are referenced in place in the source text; only strings that contain escape sequences +are decoded, into one shared buffer owned by the document. [`basic_json_view`](../api/basic_json_view/index.md) is a small, trivially copyable handle (two pointers) into that index. It gives you the read-only, type-inspection part of the `basic_json` interface -- @@ -31,7 +31,8 @@ index. It gives you the read-only, type-inspection part of the `basic_json` inte Include `` in addition to (or instead of) ``. Parse into a [`json_document`](../api/json_document.md), inspect its [`root()`](../api/basic_json_document/root.md), and -`materialize()` when you need a real value: +`materialize()` when you need a real value. `materialize()` works on any view, not just the root, so you can turn just +the subtree you need into a value: ??? example "Example: parse a document, inspect its root, and materialize it" @@ -68,15 +69,19 @@ A view is valid only while all of the following hold: - the document is alive, - the document has not been re-parsed since the view was taken (with [`read()`](../api/basic_json_document/read.md) - or [`parse()`](../api/basic_json_document/parse.md) into it), and has not had - [`shrink_to_fit()`](../api/basic_json_document/shrink_to_fit.md) called on it since, and + or [`parse()`](../api/basic_json_document/parse.md) into it), +- the document has not had [`shrink_to_fit()`](../api/basic_json_document/shrink_to_fit.md) called on it since, and - if the document borrows its source text, that text is still alive. -Moving the document itself is fine and does **not** invalidate its views: the index is a separate heap allocation -that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md) +Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap +allocation that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md) whenever any of the other conditions above was not met. -Because a view dies with its document, [`root()`](../api/basic_json_document/root.md) is not callable on a temporary +A view can dangle, exactly like a `#!cpp std::string_view`: nothing stops you from using it after its document is +gone. This is deliberate. A view is two pointers and trivially copyable; making it safe (a `#!cpp std::shared_ptr` in +the document and a `#!cpp std::weak_ptr` in each view) would make it larger, add reference counting to every copy, +and a `lock()` to every access -- exactly the costs the view exists to avoid. What is caught is the obvious case: +because a view dies with its document, [`root()`](../api/basic_json_document/root.md) is not callable on a temporary document: `#!cpp auto v = json_document::parse(text).root();` does not compile. Give the document a name first. ??? example "Example: borrowed and owned documents, and when views become invalid" @@ -98,14 +103,19 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile. [`basic_json::accept()`](../api/basic_json/accept.md)/[`basic_json::parse()`](../api/basic_json/parse.md), with the same `ignore_comments` and `ignore_trailing_commas` options. - **Errors.** A failing parse throws the same exception -- the same id, message, and position -- because on failure - the library's own parser is run on the same bytes to produce the diagnostic. - `#!cpp allow_exceptions == false` gives a [discarded](../api/basic_json_document/is_discarded.md) document instead - of throwing, just as it gives a discarded value for `#!cpp basic_json::parse()`. + the library's own parser is run on the same bytes to produce the diagnostic. Valid input never pays for this, and + the replay builds no values: it costs about a lexing pass up to the error. + `#!cpp allow_exceptions == false` skips the replay and gives a + [discarded](../api/basic_json_document/is_discarded.md) document instead of throwing, just as it gives a discarded + value for `#!cpp basic_json::parse()`. - **Number classification.** An integer literal that does not fit into the 64-bit integer type becomes a floating-point value, exactly as it does for `#!cpp basic_json::parse()`. - **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md) - behave the same way they do for ``. + behave the same way they do for ``. These are the macros that change what the parser accepts or + how it reports errors. The others either do not apply ([`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) + needs parent pointers in `basic_json` values) or only affect the values `materialize()` produces, through + `BasicJsonType`. ## What is different @@ -113,12 +123,15 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile. `number_unsigned_t` to both be 64 bits wide; this is a compile-time `#!cpp static_assert`. - **A 4 GiB input limit.** An input of 4294967280 bytes (4 GiB minus 16 bytes) or more throws [`out_of_range.416`](../home/exceptions.md#jsonexceptionout_of_range416), a limit - `#!cpp basic_json::parse()` does not have. -- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`. + `#!cpp basic_json::parse()` does not have. It keeps an index entry at 16 bytes: offsets and counts are 32 bits + wide. The limit is 16 bytes below 2^32, because the parser reads in 16-byte steps, and a position one step past the + end must still fit. +- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream + is read to its end into a buffer the document owns, and parsing starts after that. - **No source positions on `materialize()`.** Even with [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled, - [`materialize()`](../api/basic_json_view/materialize.md) does not set them: there is no lexer run during the - replay to record them. + [`materialize()`](../api/basic_json_view/materialize.md) does not set them. The index has the start offset of every + value, but not the end of arrays, objects, and escaped strings, which the positions also need. - **Element access, iteration, `get()`, JSON Pointer, `dump()`, and comparison are not (yet) provided** by `basic_json_view`. For now, [`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can do those things with.