# Ownership and lifetime A document either **borrows** the text it was parsed from, or **owns** its own copy of it; call [`owns_source()`](../../api/basic_json_document/owns_source.md) to find out which happened. [`parse()`](../../api/basic_json_document/parse.md) decides this from the value category and type of its argument (an lvalue `#!cpp std::string` is borrowed; an rvalue `#!cpp std::string` is moved in, owned without a copy; a stream is read into an owned buffer; and so on — see [`parse`'s Notes](../../api/basic_json_document/parse.md#notes) for the full table). [`parse_copy()`](../../api/basic_json_document/parse_copy.md) always owns a copy, regardless of the input. !!! warning "A borrowed document depends on your buffer" If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior, exactly as it would be for a dangling `#!cpp std::string_view`. ## When a view is valid 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), - 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 and unmodified. 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. Edits of an [editable document](editing.md) keep views valid, too; see [What stays valid](editing.md#what-stays-valid-and-what-an-edit-costs).
When a json_view becomes invalid Four timelines. First: a document borrows a std::string; when the string is destroyed, the document and every view taken from it dangle. Second: doc.read() re-parses the document; views taken before are invalid, a fresh view from root() is valid. Third: doc.shrink_to_fit() has the same effect. Fourth: moving the document does not invalidate its views; destroying the moved-to document does. alive / valid dangling: using it is undefined behavior time → The borrowed text is destroyed std::string text json_document doc json_view v text destroyed every view, and the document itself The document is re-parsed json_document doc json_view v v = doc.root() doc.read(other) views taken before a fresh view is valid again The document is shrunk json_document doc json_view v v = doc.root() doc.shrink_to_fit() views taken before a fresh view is valid again The document is moved, then destroyed json_document doc moved = std::move(doc) json_view v std::move: views stay valid moved is destroyed

Four timelines of a document and its views, each bar showing when an object is alive or valid and when it dangles. Destroying the borrowed text, re-parsing, or shrinking the document invalidates the views taken before; moving the document does not.

## Why views can dangle 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. The same holds for what a view hands out without copying: a string from [`get_string()`](../../api/basic_json_view/get_string.md), a token from [`number_token()`](../../api/basic_json_view/number_token.md), and [`string_view` members](reading.md#string_view-members) of decoded structs are valid only as long as the document and, if it borrows, its source text. ??? example "Example: borrowed and owned documents, and when views become invalid" ```cpp #include #include #include #include using nlohmann::json_document; int main() { std::cout << std::boolalpha; // BORROWED: doc only points into `text`; `text` must outlive `doc` std::string text = R"({"a": 1})"; json_document doc = json_document::parse(text); std::cout << doc.owns_source() << '\n'; // false // a view is valid as long as the document is alive, has not been // re-parsed (read()) or shrunk (shrink_to_fit()), and -- if borrowed -- // the source text is alive nlohmann::json_view v = doc.root(); std::cout << v.is_object() << '\n'; // re-parsing the SAME document invalidates views taken before the call; // `v` above must not be used after this line doc.read(R"([1, 2, 3])"); v = doc.root(); // take a fresh view instead std::cout << v.is_array() << '\n'; // moving the document does not invalidate views: the node index is // heap-allocated and does not move with the document object json_document moved = std::move(doc); std::cout << v.is_array() << '\n'; } ``` Output: ```json false true true true ``` ## See also - [Zero-copy JSON views](index.md) - overview - [`basic_json_document`](../../api/basic_json_document/index.md) - the document that owns the parsed index - [`owns_source()`](../../api/basic_json_document/owns_source.md) - whether a document borrows its source text ## Version history - Added in version 3.13.0.