diff --git a/.github/labeler.yml b/.github/labeler.yml
index 3e8914a14..014d9f9bb 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -67,8 +67,8 @@ labels:
- "tools/amalgamate/config_json_view\\.json"
- "docs/mkdocs/docs/features/json_view\\.md"
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
- - "docs/mkdocs/docs/api/(ordered_)?json_(document|view)\\.md"
- - "docs/mkdocs/docs/examples/(basic_json_(document|view)__|(ordered_)?json_(document|view)).*"
+ - "docs/mkdocs/docs/api/(ordered_)?json_(editable_)?(document|view)\\.md"
+ - "docs/mkdocs/docs/examples/(basic_json_(document|view)__|(ordered_)?json_(editable_)?(document|view)).*"
- "tests/benchmarks/src/benchmarks_view\\.cpp"
- label: "aspect: json_view"
diff --git a/BUILD.bazel b/BUILD.bazel
index 04b72f8cc..4c218fa57 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -70,6 +70,8 @@ cc_library(
"include/nlohmann/detail/view/builder.hpp",
"include/nlohmann/detail/view/compare.hpp",
"include/nlohmann/detail/view/document_data.hpp",
+ "include/nlohmann/detail/view/edit.hpp",
+ "include/nlohmann/detail/view/edit_storage.hpp",
"include/nlohmann/detail/view/errors.hpp",
"include/nlohmann/detail/view/input.hpp",
"include/nlohmann/detail/view/iterator.hpp",
diff --git a/docs/mkdocs/docs/api/basic_json_document/erase.md b/docs/mkdocs/docs/api/basic_json_document/erase.md
new file mode 100644
index 000000000..cbdc79765
--- /dev/null
+++ b/docs/mkdocs/docs/api/basic_json_document/erase.md
@@ -0,0 +1,128 @@
+# nlohmann::basic_json_document::erase
+
+```cpp
+// (1)
+std::size_t erase(view_type object, string_view_t key);
+
+// (2)
+template
+void erase(view_type array, I idx);
+
+// (3)
+std::size_t erase(const json_pointer& ptr);
+```
+
+Only an **editable** document (`#!cpp Editable == true`, 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
+ were removed; `#!cpp 0` if `object` has no member with this key.
+2. Removes the element at index `idx` of `array`, which must already exist (`#!cpp idx < array.size()`).
+3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), and returns how many values
+ were removed: the *parent* of the target must already exist, and the target itself is removed as in 1. (an object
+ member; `#!cpp 0` or more) or 2. (an array element; always `#!cpp 1`). `ptr` must not be empty -- [`root()`](root.md)
+ itself cannot be erased.
+
+## Template parameters
+
+`I`
+: an integral type other than `#!cpp bool`, deduced (overloads taking a `#!cpp bool` or a non-integral type for
+ `idx` do not participate in overload resolution).
+
+## Parameters
+
+`object` (in)
+: the object to remove a member of
+
+`array` (in)
+: the array to remove an element of
+
+`key` (in)
+: the key of the member(s) to remove
+
+`idx` (in)
+: the index of the element to remove; a negative value throws (see [Exceptions](#exceptions))
+
+`ptr` (in)
+: a JSON pointer to the value to remove, relative to `root()`
+
+## Return value
+
+1. the number of removed members (`#!cpp 0` if `object` had none with this `key`)
+2. (nothing)
+3. the number of removed values (`#!cpp 0` or more for an object member, always `#!cpp 1` for an array element)
+
+## Exceptions
+
+1. Throws [`type_error.307`](../../home/exceptions.md#jsonexceptiontype_error307) if `object` is not an object -- the
+ same message [`BasicJsonType::erase`](../basic_json/erase.md) throws for the same type.
+2. Throws `type_error.307` if `array` is not an array. Throws
+ [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is negative, or if
+ `#!cpp idx >= array.size()`.
+3. Throws [`out_of_range.405`](../../home/exceptions.md#jsonexceptionout_of_range405) ("JSON pointer has no parent")
+ if `ptr` is empty. Throws what [`at`](../basic_json_view/at.md) throws (overload 3) for resolving `ptr`'s parent.
+ For the last reference token itself: if the parent is an array, throws what 2. throws for an index that is out of
+ range, or, for a token that is not a valid array index,
+ [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) (a leading `#!cpp '0'`),
+ [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) (not a number),
+ [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) (too large for `size_type`), or
+ [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token); otherwise (an
+ object, or a primitive value the pointer's parent resolves to) throws what 1. throws.
+
+Every overload also throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view
+does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a
+view of a *different* document (overloads 1-2 only; overload 3 always starts from this document's own
+[`root()`](root.md)).
+
+## Complexity
+
+1. Linear in the number of members of `object`.
+2. Linear in the number of elements of `array` at or after `idx` (they move one slot over).
+3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
+ that level or the index into the array (as [`at`](../basic_json_view/at.md)), plus the complexity of 1. or 2. for
+ the last token.
+
+## Notes
+
+!!! info "Duplicate keys"
+
+ Overload 1. removes *every* member with `key`, not just the first -- unlike [`set`](set.md), which assigns the
+ first occurrence and drops the rest. This is why it returns a count rather than a single view: there may be
+ more than one member removed, or none.
+
+Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still
+referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) -- it just no
+longer appears when `array`/`object` is read, dumped, or iterated. Removing an element of `array` (2.) does shift the
+*links* to the elements after it, the same way `insert`, `set`, or `push_back` on the same array would; any iterator
+already taken over `array`/`object` is invalidated by an erase, since it was walking the old layout.
+
+## Examples
+
+??? example
+
+ The example below drops a deprecated field and a decommissioned entry from a configuration document -- using all
+ three overloads -- and shows what stays intact that would not with a plain `json`/`ordered_json` value: the order
+ of the fields around the ones removed, and the exact spelling of a number that was never touched.
+
+ ```cpp
+ --8<-- "examples/basic_json_document__erase.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/basic_json_document__erase.output"
+ ```
+
+## See also
+
+- [insert](insert.md) - insert an element into an array
+- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to
+- [push_back](push_back.md) - append to an array
+- [root](root.md) - the view of the root value, the starting point of overload 3
+- [`BasicJsonType::erase`](../basic_json/erase.md) - the corresponding function of `basic_json`
+- [Edits](index.md#edits) - what an edit guarantees, for every overload
+
+## Version history
+
+- Added in version 3.13.0.
diff --git a/docs/mkdocs/docs/api/basic_json_document/index.md b/docs/mkdocs/docs/api/basic_json_document/index.md
index 89564369a..534b63755 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;
```
@@ -19,6 +19,11 @@ it (a copy, or an rvalue `#!cpp std::string` that was moved in); see [`owns_sour
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.
+With `#!cpp Editable == true`, 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
+bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp static_assert`).
+
## Template parameters
`BasicJsonType`
@@ -26,14 +31,22 @@ claiming to borrow the same buffer, so it is disabled.
[`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.
+
## Specializations
-- [**json_document**](../json_document.md) - documents of the default specialization [`json`](../json.md)
-- [**ordered_json_document**](../ordered_json_document.md) - documents of [`ordered_json`](../ordered_json.md)
+- [**json_document**](../json_document.md) - read-only documents of the default specialization [`json`](../json.md)
+- [**ordered_json_document**](../ordered_json_document.md) - read-only documents of
+ [`ordered_json`](../ordered_json.md)
+- [**json_editable_document**](../json_editable_document.md) - editable documents of [`json`](../json.md)
+- [**ordered_json_editable_document**](../ordered_json_editable_document.md) - editable documents of
+ [`ordered_json`](../ordered_json.md)
## 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)
## Member functions
@@ -50,6 +63,41 @@ claiming to borrow the same buffer, so it is disabled.
- [**node_count**](node_count.md) - the number of index entries (values plus object keys)
- [**memory_usage**](memory_usage.md) - the number of bytes held by the document
- [**shrink_to_fit**](shrink_to_fit.md) - release unused index capacity
+- [**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)
+- [**erase**](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
+ (`#!cpp Editable` documents only)
+
+## Edits
+
+An editable document (`#!cpp Editable == true`) 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
+points apply to every edit:
+
+- **The source text is never written**, and the parsed index never moves: every value keeps the node it was parsed
+ into, so [views](../basic_json_view/index.md) taken before an edit stay valid, including
+ [`root()`](root.md). New values (and the element sequences of an edited array/object) go to storage owned by the
+ document, allocated on demand.
+- **A view keeps referring to the same value.** After [`set`](set.md) replaces the value a view refers to, that view
+ sees the new value; a view of a value that a later edit replaces or drops keeps showing what it last held. An edit
+ of an array or object, however, **invalidates the iterators taken over it** (its members may now live in a
+ different sequence), and a string obtained with [`get_string()`](../basic_json_view/get_string.md) stays valid even
+ as further edits happen (earlier buffers of edited text are kept alive, not overwritten).
+- **Values are accepted three ways:** a [`basic_json_view`](../basic_json_view/index.md) of *any* document
+ (read-only or editable; it is copied, nothing is shared with the source document), a `BasicJsonType` value, or
+ anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
+- [`dump()`](../basic_json_view/dump.md) writes an edited document with members in document order, new members at
+ the end, and, with [`number_format::source`](../basic_json_view/number_format.md), keeps the spelling of every
+ number that was not itself edited -- see [Editing a document](../../features/json_view.md#editing-a-document) for
+ why this matters.
+- [`read()`](read.md) discards all edits, [`shrink_to_fit()`](shrink_to_fit.md) does not move the node index once
+ there are edits, and [`memory_usage()`](memory_usage.md) includes the memory edits use.
+ [`source_offset()`](../basic_json_view/source_offset.md) of a value introduced by an edit is
+ `#!cpp static_cast(-1)`, the same value it reports for a decoded string.
## Version history
diff --git a/docs/mkdocs/docs/api/basic_json_document/insert.md b/docs/mkdocs/docs/api/basic_json_document/insert.md
new file mode 100644
index 000000000..b513dfc0d
--- /dev/null
+++ b/docs/mkdocs/docs/api/basic_json_document/insert.md
@@ -0,0 +1,114 @@
+# nlohmann::basic_json_document::insert
+
+```cpp
+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))
+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
+(`#!cpp idx <= array.size()`; `#!cpp idx == array.size()` appends, like [`push_back`](push_back.md)). Unlike
+[`push_back`](push_back.md), a [null](../basic_json_view/is_null.md) `array` does *not* first become an empty array:
+`array` must already be an array.
+
+`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document -- read-only or
+editable, and it does not have to be `array`'s own document -- which is copied so that nothing is shared with the
+source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers,
+strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
+
+## Template parameters
+
+`I`
+: an integral type other than `#!cpp bool`, deduced (overloads taking a `#!cpp bool` or a non-integral type for
+ `idx` do not participate in overload resolution).
+
+`V`
+: the type of `value`, deduced; see above for what is accepted.
+
+## Parameters
+
+`array` (in)
+: the array to insert into
+
+`idx` (in)
+: the position to insert `value` before; a negative value throws (see [Exceptions](#exceptions))
+
+`value` (in)
+: the value to insert
+
+## Return value
+
+a view of the new element, now holding `value`
+
+## Exception safety
+
+Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document
+before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an
+invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated
+for the encoding that is not reclaimed. A failure of a later allocation -- while `array` switches from its parsed
+layout to a growable block, or while that block grows, see [Notes](#notes) -- can still leave `array` already
+switched to that layout even though `value` itself was not inserted.
+
+## Exceptions
+
+Throws [`type_error.309`](../../home/exceptions.md#jsonexceptiontype_error309) if `array` is not an array -- the same
+message [`BasicJsonType::insert`](../basic_json/insert.md) throws for the same type; a null `array` throws this too
+(see above). Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is negative,
+or if `#!cpp idx > array.size()`. Throws
+[`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this
+document") if `array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a *different* document.
+Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a
+[discarded](../basic_json_view/is_discarded.md) view or a [discarded](../basic_json/is_discarded.md) `BasicJsonType`
+value, and [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or contains) a
+binary value -- `BasicJsonType` can hold one, but a `json_document` cannot. Throws
+[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `value` is (or contains) a string that is
+not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string.
+
+## Complexity
+
+Linear in the number of elements of `array` at or after `idx` (they move one slot over), plus time linear in the
+size of `value` to encode it into the document's storage (constant for a scalar, linear in the number of nested
+values for an array or object): like [`push_back`](push_back.md), the elements of `array` move to a growable block
+of links the first time it is inserted into (or [`set`](set.md)/[`push_back`](push_back.md) on), and that block
+grows in amortized constant time; inserting before the end within that block still shifts every later element.
+
+## Notes
+
+Like [`set`](set.md) on a member or an element, `insert` never moves an existing *element's value* -- only where
+`array`'s *links* to its elements live -- so a view of an existing element of `array` stays valid across an
+`insert`, and keeps referring to the same element even though its index shifts. Any iterator already taken over
+`array` is invalidated, since it was walking the old layout. See [Edits](index.md#edits) for what stays valid across
+an edit in general.
+
+## Examples
+
+??? example
+
+ The example below inserts a step into the middle of a deployment plan, without touching the steps that come
+ after it, and shows that a view taken before the insert keeps referring to the same element even though its
+ index shifts -- something a plain `json`/`ordered_json` array, or its `std::vector`-based storage, has no
+ equivalent for.
+
+ ```cpp
+ --8<-- "examples/basic_json_document__insert.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/basic_json_document__insert.output"
+ ```
+
+## See also
+
+- [push_back](push_back.md) - append to an array
+- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
+- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to
+- [`BasicJsonType::insert`](../basic_json/insert.md) - the corresponding function of `basic_json`
+- [Edits](index.md#edits) - what an edit guarantees, for every overload
+
+## Version history
+
+- Added in version 3.13.0.
diff --git a/docs/mkdocs/docs/api/basic_json_document/push_back.md b/docs/mkdocs/docs/api/basic_json_document/push_back.md
new file mode 100644
index 000000000..37ce10bc5
--- /dev/null
+++ b/docs/mkdocs/docs/api/basic_json_document/push_back.md
@@ -0,0 +1,102 @@
+# nlohmann::basic_json_document::push_back
+
+```cpp
+template
+view_type push_back(view_type array, V&& value);
+```
+
+Appends `value` as a new last element of `array`. A [null](../basic_json_view/is_null.md) `array` first becomes an
+empty array, the same way [`set`](set.md) turns a null `object` into an empty object.
+
+`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document -- read-only or
+editable, and it does not have to be `array`'s own document -- which is copied so that nothing is shared with the
+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))
+has `push_back`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
+
+## Template parameters
+
+`V`
+: the type of `value`, deduced; see above for what is accepted.
+
+## Parameters
+
+`array` (in)
+: the array (or null value) to append to
+
+`value` (in)
+: the value to append
+
+## Return value
+
+a view of the new last element of `array`, now holding `value`
+
+## Exception safety
+
+Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document
+before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an
+invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated
+for the encoding that is not reclaimed. A failure of a later allocation -- while `array` switches from its parsed
+layout to a growable block, or while that block grows, see [Notes](#notes) -- can still leave a partial effect, such
+as a null `array` argument already turned into an empty array even though `value` itself was not appended.
+
+## Exceptions
+
+Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) if `array` is neither an array nor
+null -- the same message [`BasicJsonType::push_back`](../basic_json/push_back.md) throws for the same type. Throws
+[`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this
+document") if `array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a *different* document.
+Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a
+[discarded](../basic_json_view/is_discarded.md) view or a [discarded](../basic_json/is_discarded.md) `BasicJsonType`
+value, and [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or contains) a
+binary value -- `BasicJsonType` can hold one, but a `json_document` cannot. Throws
+[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `value` is (or contains) a string that is
+not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string.
+
+## Complexity
+
+Amortized constant, plus time linear in the size of `value` to encode it into the document's storage (constant for
+a scalar, linear in the number of nested values for an array or object): the elements of `array` move to a growable
+block of links the first time it is appended to (or [`set`](set.md) on), and that block itself grows -- doubling its
+capacity, so the cost of growing it amortizes to constant per element -- only once it runs out of room. See
+[Notes](#notes).
+
+## Notes
+
+Like [`set`](set.md) on a member or an element, `push_back` never moves an existing element itself -- only where
+`array`'s *links* to its elements live -- so a view of an existing element of `array` stays valid across a
+`push_back`, but any iterator already taken over `array` is invalidated, since it was walking the old layout. See
+[Edits](index.md#edits) for what stays valid across an edit in general.
+
+## Examples
+
+??? example
+
+ The example below appends records to an array one at a time, as they might arrive from a stream of events,
+ without ever building a `BasicJsonType` value for the array or for the records already in it, and shows that a
+ view taken from an earlier `push_back` still refers to the same element once later ones have run.
+
+ ```cpp
+ --8<-- "examples/basic_json_document__push_back.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/basic_json_document__push_back.output"
+ ```
+
+## See also
+
+- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to
+- [insert](insert.md) - insert an element into an array before a given position
+- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
+- [root](root.md) - the view of the root value
+- [`BasicJsonType::push_back`](../basic_json/push_back.md) - the corresponding function of `basic_json`
+- [Edits](index.md#edits) - what an edit guarantees, for every overload
+
+## Version history
+
+- Added in version 3.13.0.
diff --git a/docs/mkdocs/docs/api/basic_json_document/set.md b/docs/mkdocs/docs/api/basic_json_document/set.md
new file mode 100644
index 000000000..f9d73b94c
--- /dev/null
+++ b/docs/mkdocs/docs/api/basic_json_document/set.md
@@ -0,0 +1,194 @@
+# nlohmann::basic_json_document::set
+
+```cpp
+// (1)
+template
+view_type set(view_type target, V&& value);
+
+// (2)
+template
+view_type set(view_type object, string_view_t key, V&& value);
+
+// (3)
+template
+view_type set(view_type array, I idx, V&& value);
+
+// (4)
+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))
+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`.
+2. Sets the member `key` of the object `object` to `value`: assigns it if `object` already has a member with this
+ key -- the first one, should the key occur more than once, and the later duplicates are then dropped (see the
+ [Notes](#notes) below) -- or appends a new member at the end otherwise. A [null](../basic_json_view/is_null.md)
+ `object` first becomes an empty object.
+3. Assigns `value` to the element at index `idx` of the array `array`, which must already exist (`#!cpp idx <
+ array.size()`).
+4. Sets the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), to `value`. The *parent* of the
+ target must already exist: an object member is set as in 2. (added if it does not exist yet), an array element is
+ assigned as in 3., and a last reference token of `#!cpp "-"`, or equal to the size of the array, appends `value`
+ instead, exactly as [`push_back`](push_back.md) would. An empty `ptr` sets [`root()`](root.md) itself, as in 1.
+
+In every overload, `value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any*
+document -- read-only or editable, and it does not have to be `target`'s/`object`'s/`array`'s own document -- which
+is copied so that nothing is shared with the source document afterward; a `BasicJsonType` value; or anything
+`BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
+
+## Template parameters
+
+`V`
+: the type of `value`, deduced; see above for what is accepted.
+
+`I`
+: an integral type other than `#!cpp bool`, deduced (overloads taking a `#!cpp bool` or a non-integral type for
+ `idx` do not participate in overload resolution).
+
+## Parameters
+
+`target` (in)
+: the value to replace
+
+`object` (in)
+: the object (or null value) whose member to set
+
+`array` (in)
+: the array whose element to assign
+
+`key` (in)
+: the key of the member to set
+
+`idx` (in)
+: the index of the element to assign; a negative value throws (see [Exceptions](#exceptions))
+
+`ptr` (in)
+: a JSON pointer to the value to set, relative to `root()`
+
+`value` (in)
+: the new value
+
+## Return value
+
+1. a view of `target`, now holding `value`
+2. a view of the member `key` of `object`, now holding `value`
+3. a view of the element `idx` of `array`, now holding `value`
+4. a view of the value `ptr` refers to, now holding `value`
+
+## Exception safety
+
+Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document
+before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an
+invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated
+for the encoding that is not reclaimed. A failure of a later allocation -- while an edited array or object switches
+from its parsed layout to a growable block, see [Notes](#notes) -- can still leave a partial effect, such as a
+[null](../basic_json_view/is_null.md) `object`/`array` argument already turned into an empty object/array even
+though `value` itself was not linked in.
+
+## Exceptions
+
+1. Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a
+ [discarded](../basic_json_view/is_discarded.md) view, or a [discarded](../basic_json/is_discarded.md)
+ `BasicJsonType` value (e.g. `#!cpp BasicJsonType(value_t::discarded)`) -- an object or array `value`, of either
+ kind, is fine and is encoded as a whole subtree.
+2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if `object` is neither an object
+ nor null -- the same message [`operator[]`](../basic_json_view/operator%5B%5D.md) throws for a string argument on
+ such a value. Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `key` is not
+ valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string.
+ Also throws what 1. throws for `value`.
+3. Throws `type_error.305` if `array` is not an array -- the same message `operator[]` throws for a numeric argument
+ on such a value. Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is
+ negative, or if `#!cpp idx >= array.size()`. Also throws what 1. throws for `value`.
+4. Throws what [`at`](../basic_json_view/at.md) throws (overload 3) for resolving `ptr`'s parent, except that a
+ missing object member or an array index equal to the array's size at the very last reference token is not an
+ error there (it becomes a new member or an appended element) instead of
+ [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403)/[`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402).
+ For the last reference token itself: if the parent is an object (or a primitive value, where it throws
+ `type_error.305`), throws what 2. throws; if the parent is an array, throws what 3. throws for an index that is
+ out of range, or, for a token that is not a valid array index,
+ [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) (a leading `#!cpp '0'`),
+ [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) (not a number),
+ [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) (too large for `size_type`), or
+ [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token). Also throws what 1.
+ throws for `value`.
+
+Every overload also throws [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or
+contains) a binary value -- `BasicJsonType` can hold one, but a `json_document` cannot -- and
+[`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this
+document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a
+*different* document (overloads 1-3 only; overload 4 always starts from this document's own [`root()`](root.md)).
+
+## Complexity
+
+1. Linear in the size of `value` (encoding it into the document's storage): constant for a scalar, linear in the
+ number of nested values for an array or object. If `target` is itself an array or object that spans more than one
+ node in its parent's original, unedited layout, and `value` is a scalar, replacing it additionally costs time
+ linear in the number of elements of that parent, the *first* time -- see [Notes](#notes).
+2. Linear in the number of members of `object`, to find an existing member with `key`, plus the complexity of 1. for
+ `value`.
+3. Constant, plus the complexity of 1. for `value`.
+4. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
+ that level or the index into the array (as [`at`](../basic_json_view/at.md)), plus the complexity of 2. or 3. for
+ the last token.
+
+## Notes
+
+!!! info "Duplicate keys"
+
+ If `object` already has more than one member with `key` (2.), the *first* one is assigned `value` and every
+ later member with the same key is removed -- so that a lookup, an iteration, and
+ [`materialize()`](../basic_json_view/materialize.md) of `object` afterward all agree on a single value for
+ `key`, the same way [`operator[]`](../basic_json_view/operator%5B%5D.md) already picks the first occurrence of a
+ duplicate key for reading. See the [Notes on duplicate keys](../basic_json_view/operator%5B%5D.md#notes) of
+ `operator[]`.
+
+Setting a member (2.) or an element (3., through 4.) of an array or object whose elements have not been edited
+before switches it from its parsed layout to a growable block holding links to its elements; a later
+[`push_back`](push_back.md) or `set` on the same container reuses that block, growing it (amortized constant time)
+only once it runs out of room. This never moves an element itself -- only where the container's *links* to its
+elements live -- so a view of an element stays valid, but any iterator already taken over the container is
+invalidated, since it was walking the old layout. See [Edits](index.md#edits) for what stays valid across an edit in
+general.
+
+The same switch happens, for the same reason, when overload 1. replaces a multi-node array/object value with a
+scalar: the *parent's* element sequence is what has to switch to links, not `target` itself, because the parent
+originally stepped over `target`'s whole subtree by its node count, which no longer applies once `target` is a
+one-node scalar.
+
+## Examples
+
+??? example "Example: (1)/(2)/(3)/(4) replace a value, set a member, assign an element, set via a JSON pointer"
+
+ The example below edits a small configuration document -- replacing a value, adding an object member, assigning
+ an array element, and reaching a field through a JSON pointer -- and shows what
+ [`dump()`](../basic_json_view/dump.md) preserves that is lost once the same edits are made on a `BasicJsonType`
+ value instead: the order object members were written in, and the exact spelling of a number that was never
+ touched.
+
+ ```cpp
+ --8<-- "examples/basic_json_document__set.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/basic_json_document__set.output"
+ ```
+
+## See also
+
+- [push_back](push_back.md) - append to an array
+- [insert](insert.md) - insert an element into an array
+- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
+- [root](root.md) - the view of the root value, the starting point of overload 4
+- [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document, keeping an untouched number's
+ spelling with `#!cpp number_format::source`
+- [Edits](index.md#edits) - what an edit guarantees, for every overload
+- [Editing a document](../../features/json_view.md#editing-a-document) - why editable documents keep the source
+ text's order and number spelling
+
+## Version history
+
+- Added in version 3.13.0.
diff --git a/docs/mkdocs/docs/api/basic_json_view/index.md b/docs/mkdocs/docs/api/basic_json_view/index.md
index a8b518b0a..a81c6d161 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;
```
@@ -27,16 +27,30 @@ subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`
[`operator!=`](operator_ne.md) compare two views, or a view and a `BasicJsonType` value, without ever building a
`BasicJsonType` value for a view; no ordering comparison (`#!cpp operator<`) is provided.
+`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
+[`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
+[Edits](../basic_json_document/index.md#edits).
+
## Template parameters
`BasicJsonType`
: 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).
+
## Specializations
- [**json_view**](../json_view.md) - views of a [`json_document`](../json_document.md)
- [**ordered_json_view**](../ordered_json_view.md) - views of an [`ordered_json_document`](../ordered_json_document.md)
+- [**json_editable_view**](../json_editable_view.md) - views of a [`json_editable_document`](../json_editable_document.md)
+- [**ordered_json_editable_view**](../ordered_json_editable_view.md) - views of an
+ [`ordered_json_editable_document`](../ordered_json_editable_document.md)
## Member types
diff --git a/docs/mkdocs/docs/api/json_editable_document.md b/docs/mkdocs/docs/api/json_editable_document.md
new file mode 100644
index 000000000..ab3fc3868
--- /dev/null
+++ b/docs/mkdocs/docs/api/json_editable_document.md
@@ -0,0 +1,43 @@
+# nlohmann::json_editable_document
+
+Defined in header ``
+
+```cpp
+using json_editable_document = basic_json_document;
+```
+
+This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the default
+[`json`](json.md) specialization: in addition to everything [`json_document`](json_document.md) offers,
+[`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) change values after
+parsing, without ever rewriting the source text -- see [Edits](basic_json_document/index.md#edits) and
+[Editing a document](../features/json_view.md#editing-a-document).
+
+## Examples
+
+??? example
+
+ The example below patches two fields of a small configuration document -- changing one and adding another --
+ and dumps it back out with the member order and the spelling of the untouched number preserved, something a
+ plain [`json`](json.md) value cannot do.
+
+ ```cpp
+ --8<-- "examples/json_editable_document.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/json_editable_document.output"
+ ```
+
+## See also
+
+- [json_editable_view](json_editable_view.md) - a view of a value of a `json_editable_document`
+- [json_document](json_document.md) - the read-only document this type adds edits to
+- [ordered_json_editable_document](ordered_json_editable_document.md) - the corresponding editable document for
+ `ordered_json`
+- [Edits](basic_json_document/index.md#edits) - what an edit guarantees
+
+## Version history
+
+Since version 3.13.0.
diff --git a/docs/mkdocs/docs/api/json_editable_view.md b/docs/mkdocs/docs/api/json_editable_view.md
new file mode 100644
index 000000000..f5fa59e57
--- /dev/null
+++ b/docs/mkdocs/docs/api/json_editable_view.md
@@ -0,0 +1,41 @@
+# nlohmann::json_editable_view
+
+Defined in header ``
+
+```cpp
+using json_editable_view = basic_json_view;
+```
+
+This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a
+[`json_editable_document`](json_editable_document.md). It offers the same read-only interface as
+[`json_view`](json_view.md); what is different is what it can be a view *of* -- a value that
+[`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) can change, with every view
+still referring to it seeing the change, see [Edits](basic_json_document/index.md#edits).
+
+## Examples
+
+??? example
+
+ The example below is the same as [`json_editable_document`'s](json_editable_document.md): every view read back
+ out of the document -- `#!cpp doc.root()` and the views nested under it -- sees the edits made through `set`.
+
+ ```cpp
+ --8<-- "examples/json_editable_document.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/json_editable_document.output"
+ ```
+
+## See also
+
+- [json_editable_document](json_editable_document.md) - the document type this view refers into
+- [json_view](json_view.md) - the corresponding read-only view
+- [ordered_json_editable_view](ordered_json_editable_view.md) - the corresponding view for
+ `ordered_json_editable_document`
+
+## Version history
+
+Since version 3.13.0.
diff --git a/docs/mkdocs/docs/api/ordered_json_editable_document.md b/docs/mkdocs/docs/api/ordered_json_editable_document.md
new file mode 100644
index 000000000..3127eea70
--- /dev/null
+++ b/docs/mkdocs/docs/api/ordered_json_editable_document.md
@@ -0,0 +1,47 @@
+# nlohmann::ordered_json_editable_document
+
+Defined in header ``
+
+```cpp
+using ordered_json_editable_document = basic_json_document;
+```
+
+This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the
+[`ordered_json`](ordered_json.md) specialization: [`set`](basic_json_document/set.md) and
+[`push_back`](basic_json_document/push_back.md) change values after parsing, as for
+[`json_editable_document`](json_editable_document.md), and
+[`materialize()`](basic_json_view/materialize.md) preserves the document order of object members -- including
+members [`set`](basic_json_document/set.md) added -- instead of sorting them like
+[`json_editable_document`](json_editable_document.md) does.
+
+## Examples
+
+??? example
+
+ The example below edits a document with `set`, then shows that `materialize()` keeps the member order of the
+ source text (with the new member at the end) for `ordered_json_editable_document`, where it would sort the
+ members alphabetically for [`json_editable_document`](json_editable_document.md).
+
+ ```cpp
+ --8<-- "examples/ordered_json_editable_document.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/ordered_json_editable_document.output"
+ ```
+
+## See also
+
+- [ordered_json_editable_view](ordered_json_editable_view.md) - a view of a value of an
+ `ordered_json_editable_document`
+- [ordered_json_document](ordered_json_document.md) - the read-only document this type adds edits to
+- [json_editable_document](json_editable_document.md) - the corresponding editable document for the default `json`
+ specialization
+- [Object Order](../features/object_order.md)
+- [Edits](basic_json_document/index.md#edits) - what an edit guarantees
+
+## Version history
+
+Since version 3.13.0.
diff --git a/docs/mkdocs/docs/api/ordered_json_editable_view.md b/docs/mkdocs/docs/api/ordered_json_editable_view.md
new file mode 100644
index 000000000..78351121e
--- /dev/null
+++ b/docs/mkdocs/docs/api/ordered_json_editable_view.md
@@ -0,0 +1,40 @@
+# nlohmann::ordered_json_editable_view
+
+Defined in header ``
+
+```cpp
+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
+[`ordered_json_editable_document`](ordered_json_editable_document.md), the corresponding view for
+[`ordered_json_view`](ordered_json_view.md) the way [`json_editable_view`](json_editable_view.md) is for
+[`json_view`](json_view.md).
+
+## Examples
+
+??? example
+
+ The example below is the same as [`ordered_json_editable_document`'s](ordered_json_editable_document.md): the
+ views `set` returns see the document's member order preserved on `materialize()`, unlike for a
+ [`json_editable_document`](json_editable_document.md).
+
+ ```cpp
+ --8<-- "examples/ordered_json_editable_document.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/ordered_json_editable_document.output"
+ ```
+
+## See also
+
+- [ordered_json_editable_document](ordered_json_editable_document.md) - the document type this view refers into
+- [ordered_json_view](ordered_json_view.md) - the corresponding read-only view
+- [json_editable_view](json_editable_view.md) - the corresponding view for `json_editable_document`
+
+## Version history
+
+Since version 3.13.0.
diff --git a/docs/mkdocs/docs/examples/basic_json_document__erase.cpp b/docs/mkdocs/docs/examples/basic_json_document__erase.cpp
new file mode 100644
index 000000000..4b3197698
--- /dev/null
+++ b/docs/mkdocs/docs/examples/basic_json_document__erase.cpp
@@ -0,0 +1,38 @@
+#include
+#include
+
+using json = nlohmann::json;
+using json_editable_document = nlohmann::json_editable_document;
+using json_editable_view = nlohmann::json_editable_view;
+
+int main()
+{
+ // a deprecated field is dropped from a configuration file, and a
+ // decommissioned replica is removed from the list -- "price" keeps its
+ // trailing zero, and the fields around the removed ones keep their order
+ const std::string text = R"({
+ "name": "cache",
+ "legacy_host": "db0",
+ "host": "db1",
+ "price": 19.90,
+ "replicas": ["db2", "db3", "db4"]
+})";
+
+ json_editable_document doc = json_editable_document::parse(text);
+
+ doc.erase(doc.root(), "legacy_host"); // (1) an object member
+ doc.erase(doc.root()["replicas"], 1); // (2) an array element ("db3")
+ const std::size_t removed = doc.erase(json::json_pointer("/replicas/0")); // (3) via a JSON pointer
+
+ std::cout << removed << '\n';
+ std::cout << doc.root().dump(2, ' ', false, json_editable_view::number_format::source) << "\n\n";
+
+ // the same edits on a plain json value: object_t is a std::map, so
+ // parsing already sorted the keys, and dump() rewrites every number to
+ // its shortest form, even "price", which was never touched
+ json plain = json::parse(text);
+ plain.erase("legacy_host");
+ plain["replicas"].erase(1);
+ plain["replicas"].erase(0);
+ std::cout << plain.dump(2) << '\n';
+}
diff --git a/docs/mkdocs/docs/examples/basic_json_document__erase.output b/docs/mkdocs/docs/examples/basic_json_document__erase.output
new file mode 100644
index 000000000..de764877a
--- /dev/null
+++ b/docs/mkdocs/docs/examples/basic_json_document__erase.output
@@ -0,0 +1,18 @@
+1
+{
+ "name": "cache",
+ "host": "db1",
+ "price": 19.90,
+ "replicas": [
+ "db4"
+ ]
+}
+
+{
+ "host": "db1",
+ "name": "cache",
+ "price": 19.9,
+ "replicas": [
+ "db4"
+ ]
+}
diff --git a/docs/mkdocs/docs/examples/basic_json_document__insert.cpp b/docs/mkdocs/docs/examples/basic_json_document__insert.cpp
new file mode 100644
index 000000000..1c04bd6cc
--- /dev/null
+++ b/docs/mkdocs/docs/examples/basic_json_document__insert.cpp
@@ -0,0 +1,38 @@
+#include
+#include
+
+using json = nlohmann::json;
+using json_editable_document = nlohmann::json_editable_document;
+using json_editable_view = nlohmann::json_editable_view;
+
+int main()
+{
+ // a deployment plan -- "budget" is written with a trailing zero that has
+ // no effect on its value
+ const std::string text = R"({
+ "release": "2026.09",
+ "steps": ["build", "test", "deploy"],
+ "budget": 19.90
+})";
+
+ json_editable_document doc = json_editable_document::parse(text);
+
+ const std::size_t deploy_index = 2;
+ const auto deploy = doc.root()["steps"][deploy_index]; // held across the insert
+
+ doc.insert(doc.root()["steps"], deploy_index, "smoke-test"); // insert before "deploy"
+
+ // the held view still refers to "deploy", even though its index moved
+ // from 2 to 3, and nothing else in the document was touched
+ std::cout << deploy.dump() << '\n';
+ std::cout << doc.root().dump(2, ' ', false, json_editable_view::number_format::source) << "\n\n";
+
+ // the same edit on a plain json value: an index held from before the
+ // insert now refers to whatever moved into that slot, and dump()
+ // rewrites "budget" to its shortest form even though it was never
+ // touched
+ json plain = json::parse(text);
+ plain["steps"].insert(plain["steps"].begin() + static_cast(deploy_index), "smoke-test");
+ std::cout << plain["steps"][deploy_index].dump() << '\n';
+ std::cout << plain.dump(2) << '\n';
+}
diff --git a/docs/mkdocs/docs/examples/basic_json_document__insert.output b/docs/mkdocs/docs/examples/basic_json_document__insert.output
new file mode 100644
index 000000000..1b2480df1
--- /dev/null
+++ b/docs/mkdocs/docs/examples/basic_json_document__insert.output
@@ -0,0 +1,23 @@
+"deploy"
+{
+ "release": "2026.09",
+ "steps": [
+ "build",
+ "test",
+ "smoke-test",
+ "deploy"
+ ],
+ "budget": 19.90
+}
+
+"smoke-test"
+{
+ "budget": 19.9,
+ "release": "2026.09",
+ "steps": [
+ "build",
+ "test",
+ "smoke-test",
+ "deploy"
+ ]
+}
diff --git a/docs/mkdocs/docs/examples/basic_json_document__push_back.cpp b/docs/mkdocs/docs/examples/basic_json_document__push_back.cpp
new file mode 100644
index 000000000..16385ea99
--- /dev/null
+++ b/docs/mkdocs/docs/examples/basic_json_document__push_back.cpp
@@ -0,0 +1,24 @@
+#include
+#include
+
+using json = nlohmann::json;
+using json_editable_document = nlohmann::json_editable_document;
+
+int main()
+{
+ // "events" starts out null -- the first push_back() turns it into an
+ // array, exactly like set() turns a null object member into an object
+ json_editable_document doc = json_editable_document::parse(R"({"source": "sensor-1", "events": null})");
+
+ const auto first = doc.push_back(doc.root()["events"], json{{"type", "start"}, {"t", 0}});
+ for (int t = 1; t <= 3; ++t)
+ {
+ doc.push_back(doc.root()["events"], json{{"type", "tick"}, {"t", t}});
+ }
+
+ // push_back() never moves an existing element: a view taken from an
+ // earlier call still refers to the same element after later ones
+ std::cout << first.dump() << '\n';
+ std::cout << doc.root()["events"].size() << '\n';
+ std::cout << doc.root().dump(2) << '\n';
+}
diff --git a/docs/mkdocs/docs/examples/basic_json_document__push_back.output b/docs/mkdocs/docs/examples/basic_json_document__push_back.output
new file mode 100644
index 000000000..5a7307aa4
--- /dev/null
+++ b/docs/mkdocs/docs/examples/basic_json_document__push_back.output
@@ -0,0 +1,23 @@
+{"t":0,"type":"start"}
+4
+{
+ "source": "sensor-1",
+ "events": [
+ {
+ "t": 0,
+ "type": "start"
+ },
+ {
+ "t": 1,
+ "type": "tick"
+ },
+ {
+ "t": 2,
+ "type": "tick"
+ },
+ {
+ "t": 3,
+ "type": "tick"
+ }
+ ]
+}
diff --git a/docs/mkdocs/docs/examples/basic_json_document__set.cpp b/docs/mkdocs/docs/examples/basic_json_document__set.cpp
new file mode 100644
index 000000000..7631a7b1b
--- /dev/null
+++ b/docs/mkdocs/docs/examples/basic_json_document__set.cpp
@@ -0,0 +1,41 @@
+#include
+#include
+
+using json = nlohmann::json;
+using json_editable_document = nlohmann::json_editable_document;
+using json_editable_view = nlohmann::json_editable_view;
+
+int main()
+{
+ // a configuration file, as it might be read from disk -- "price" is
+ // written with a trailing zero that has no effect on its value
+ const std::string text = R"({
+ "name": "cache",
+ "host": "db1",
+ "port": 6379,
+ "price": 19.90,
+ "replicas": ["db2", "db3"],
+ "timeout": 30
+})";
+
+ json_editable_document doc = json_editable_document::parse(text);
+
+ doc.set(doc.root()["port"], 6380); // (1) replace a value
+ doc.set(doc.root(), "region", "us-east"); // (2) add a member
+ doc.set(doc.root()["replicas"], 0, "db4"); // (3) assign an element
+ doc.set(json::json_pointer("/timeout"), 45); // (4) via a JSON pointer
+
+ // members stay in document order (the new one at the end), and a number
+ // that was not itself edited keeps its exact spelling
+ std::cout << doc.root().dump(2, ' ', false, json_editable_view::number_format::source) << "\n\n";
+
+ // the same edits on a plain json value: object_t is a std::map, so
+ // parsing already sorted the keys, and dump() rewrites every number to
+ // its shortest form, even "price", which was never touched
+ json plain = json::parse(text);
+ plain["port"] = 6380;
+ plain["region"] = "us-east";
+ plain["replicas"][0] = "db4";
+ plain[json::json_pointer("/timeout")] = 45;
+ std::cout << plain.dump(2) << '\n';
+}
diff --git a/docs/mkdocs/docs/examples/basic_json_document__set.output b/docs/mkdocs/docs/examples/basic_json_document__set.output
new file mode 100644
index 000000000..74392692e
--- /dev/null
+++ b/docs/mkdocs/docs/examples/basic_json_document__set.output
@@ -0,0 +1,25 @@
+{
+ "name": "cache",
+ "host": "db1",
+ "port": 6380,
+ "price": 19.90,
+ "replicas": [
+ "db4",
+ "db3"
+ ],
+ "timeout": 45,
+ "region": "us-east"
+}
+
+{
+ "host": "db1",
+ "name": "cache",
+ "port": 6380,
+ "price": 19.9,
+ "region": "us-east",
+ "replicas": [
+ "db4",
+ "db3"
+ ],
+ "timeout": 45
+}
diff --git a/docs/mkdocs/docs/examples/json_editable_document.cpp b/docs/mkdocs/docs/examples/json_editable_document.cpp
new file mode 100644
index 000000000..bf08c0d9b
--- /dev/null
+++ b/docs/mkdocs/docs/examples/json_editable_document.cpp
@@ -0,0 +1,30 @@
+#include
+#include
+
+using json = nlohmann::json;
+using json_editable_document = nlohmann::json_editable_document;
+using json_editable_view = nlohmann::json_editable_view;
+
+int main()
+{
+ // a configuration file, as it might be read from disk
+ const std::string text = R"({"name": "cache", "host": "db1", "port": 6379, "price": 19.90})";
+ std::cout << text << "\n\n";
+
+ // patch two fields -- "price" is never touched
+ json_editable_document doc = json_editable_document::parse(text);
+ doc.set(doc.root(), "host", "db2");
+ doc.set(doc.root(), "retries", 3);
+
+ // member order (the new member at the end) and the untouched number's
+ // exact spelling survive
+ std::cout << doc.root().dump(-1, ' ', false, json_editable_view::number_format::source) << '\n';
+
+ // the same patch on a plain json value: keys are sorted (object_t is a
+ // std::map), and "price" is rewritten even though the patch never
+ // touched it
+ json plain = json::parse(text);
+ plain["host"] = "db2";
+ plain["retries"] = 3;
+ std::cout << plain.dump() << '\n';
+}
diff --git a/docs/mkdocs/docs/examples/json_editable_document.output b/docs/mkdocs/docs/examples/json_editable_document.output
new file mode 100644
index 000000000..c949e3828
--- /dev/null
+++ b/docs/mkdocs/docs/examples/json_editable_document.output
@@ -0,0 +1,4 @@
+{"name": "cache", "host": "db1", "port": 6379, "price": 19.90}
+
+{"name":"cache","host":"db2","port":6379,"price":19.90,"retries":3}
+{"host":"db2","name":"cache","port":6379,"price":19.9,"retries":3}
diff --git a/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp b/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp
new file mode 100644
index 000000000..c6e7fa08e
--- /dev/null
+++ b/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp
@@ -0,0 +1,15 @@
+#include
+#include
+
+using ordered_json_editable_document = nlohmann::ordered_json_editable_document;
+
+int main()
+{
+ // 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
+
+ // materialize() preserves the document order (with "b" at the end),
+ // instead of sorting the keys the way json_editable_document does
+ std::cout << doc.root().materialize().dump() << '\n';
+}
diff --git a/docs/mkdocs/docs/examples/ordered_json_editable_document.output b/docs/mkdocs/docs/examples/ordered_json_editable_document.output
new file mode 100644
index 000000000..a845dbaff
--- /dev/null
+++ b/docs/mkdocs/docs/examples/ordered_json_editable_document.output
@@ -0,0 +1 @@
+{"z":1,"a":2,"m":3,"b":4}
diff --git a/docs/mkdocs/docs/features/index.md b/docs/mkdocs/docs/features/index.md
index d995c97d8..b1173b662 100644
--- a/docs/mkdocs/docs/features/index.md
+++ b/docs/mkdocs/docs/features/index.md
@@ -14,6 +14,7 @@ C++ types, and finally serialize it again.
[parsing untrusted input](parsing/untrusted_input.md).
- [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree;
strings and numbers stay in the input and are only decoded when needed.
+ [Editable documents](json_view.md#editing-a-document) can also be modified.
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
## Accessing and modifying values
diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md
index 2b7d927c0..c1db83d1a 100644
--- a/docs/mkdocs/docs/features/json_view.md
+++ b/docs/mkdocs/docs/features/json_view.md
@@ -234,14 +234,77 @@ a number to its parsed `#!cpp double`/`#!cpp int64_t` value.
[`operator<<`](../api/basic_json_view/operator_ltlt.md) writes a view to a stream the way `basic_json`'s does, using
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
+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
+none of the bookkeeping edits need, and calling `set`/`push_back`/`insert`/`erase` on one is a compile error, not a
+runtime one.
+
+### Why: editing without reformatting
+
+The `#!cpp 19.90` price from [above](#writing-a-view-back) is exactly the kind of value that makes editing a `json`
+or `ordered_json` value in place lossy. Say you parse a configuration file, patch one field, and write it back:
+
+- **`json`** re-sorts every key on the way in (`object_t` is a `#!cpp std::map`) and rewrites every number to its
+ shortest round-trip form on the way out -- a one-field patch turns into a diff that reorders the whole file and
+ rewrites `#!cpp 19.90` to `#!cpp 19.9`.
+- **`ordered_json`** keeps the key order, but still rewrites every number the same way: parsing has already reduced
+ it to a `#!cpp double`/`#!cpp int64_t`, and there is no way back to how it was spelled in the source text.
+
+An editable document keeps both. [`dump()`](../api/basic_json_view/dump.md) of an edited document writes members in
+document order -- a member [`set`](../api/basic_json_document/set.md) added goes at the end, exactly where it was
+inserted, and an [`erase`](../api/basic_json_document/erase.md)d member simply leaves a gap: everything around it
+keeps its place -- and [`number_format::source`](../api/basic_json_view/number_format.md) keeps the exact spelling
+of every number an edit did not itself touch; a number an edit *did* touch is written the way
+[`BasicJsonType::dump()`](../api/basic_json/dump.md) would write it, since there is no source spelling for a brand
+new value.
+
+??? example "Example: patch a configuration, keeping member order and number spellings"
+
+ ```cpp
+ --8<-- "examples/json_editable_document.cpp"
+ ```
+
+ Output:
+
+ ```json
+ --8<-- "examples/json_editable_document.output"
+ ```
+
+### What stays valid, and what an edit costs
+
+The source text itself is **never written**, and the parsed index never moves -- a value keeps the node it was
+parsed into for as long as it is not itself replaced. So every [view](../api/basic_json_view/index.md) taken before
+an edit, including a previously obtained [`root()`](../api/basic_json_document/root.md), stays valid and, if it
+still refers to the edited value, sees the edit; a view of a value a later edit drops or replaces just keeps showing
+what it last held. New values go to storage the document allocates and owns on demand. The one thing an edit does
+invalidate is the **iterators** taken over an edited array or object: the first time one of its elements is set,
+appended to, inserted into, or erased, its elements move from the parsed, fixed layout to a growable block of links
+so that [`push_back`](../api/basic_json_document/push_back.md) can later grow it in amortized constant time --
+existing elements are not touched, but an iterator that was walking the old layout no longer matches. A string
+obtained with
+[`get_string()`](../api/basic_json_view/get_string.md) is unaffected either way and stays valid across further
+edits. See [`basic_json_document`'s Edits](../api/basic_json_document/index.md#edits) for the details, and
+[`set`'s Exception safety](../api/basic_json_document/set.md#exception-safety) for what an edit guarantees if it
+throws (the *basic* guarantee, not the strong one `dump()` and the read-only functions provide). How edits are kept in
+the index is described in the [architecture overview](../home/architecture.md#node-index-of-json-views).
+
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
-| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) |
-|---|---|---|---|
-| **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document |
-| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only |
-| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use |
-| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives |
+| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) | [`json_editable_document`](../api/json_editable_document.md) / [`json_editable_view`](../api/json_editable_view.md) |
+|---|---|---|---|---|
+| **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document | same as `json_document`; edits go to storage the document owns |
+| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only | [`set`](../api/basic_json_document/set.md)/[`push_back`](../api/basic_json_document/push_back.md)/[`insert`](../api/basic_json_document/insert.md)/[`erase`](../api/basic_json_document/erase.md) edit in place; the source text is never rewritten |
+| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use | the same, plus [`dump()`](../api/basic_json_view/dump.md) of an edited document that keeps the member order and, with [`number_format::source`](../api/basic_json_view/number_format.md), the spelling of every untouched number |
+| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back -- a configuration file, for instance -- where the rest of it should come back exactly as it was |
## Version history
diff --git a/docs/mkdocs/docs/home/architecture.md b/docs/mkdocs/docs/home/architecture.md
index 2a5fb58a1..3499ead5f 100644
--- a/docs/mkdocs/docs/home/architecture.md
+++ b/docs/mkdocs/docs/home/architecture.md
@@ -194,8 +194,8 @@ packet-beta
| Bytes | Field | Type | Contents |
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
-| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float |
-| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes are (0: the source text, 1: the buffer of decoded strings, for strings with escapes); bit 2: the value of a boolean |
+| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below) |
+| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bits 3 and 4: moved and new (see below) |
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their hash index (1-based), or 0; otherwise 0 |
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
@@ -245,6 +245,21 @@ flowchart LR
All `flags` are 0. The integer's bytes 8-15 hold its value, 1; its `extra` says it has one digit. The float's `extra`
says it has one integer and one fraction digit, and its `len` is that of the token `2.5`.
+Editable documents ([`json_editable_document`](../api/json_editable_document.md),
+[`detail/view/edit.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/edit.hpp) and
+[`detail/view/edit_storage.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/edit_storage.hpp))
+never write the source text and never move or resize the parsed index, so views stay valid while the document is
+edited:
+
+- A new scalar is written over its node. Its text (a string, or the token of a number as `dump()` writes it) goes to
+ the edit buffer, which `flags` bits 0-1 then name.
+- An array or object whose elements change gets the flag *moved* (bit 3): its elements then live in a separate
+ sequence (a header node, then the entries), whose number is in `off`. The entries are links (`kind` 10), whose bytes
+ 8-15 hold the address of the value's node, so values never move.
+- A node written by an edit gets the flag *new* (bit 4): it has no position in the source text.
+- Views of read-only documents compile without any of this: how views walk the index is a template parameter
+ (`navigation`).
+
## Input adapters
Input is read via **input adapters** that abstract a source. Every input adapter provides this interface:
diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md
index d03fff607..6232e3993 100644
--- a/docs/mkdocs/docs/home/exceptions.md
+++ b/docs/mkdocs/docs/home/exceptions.md
@@ -812,6 +812,24 @@ does not list an enumerator and it is therefore converted like the first listed
[json.exception.type_error.318] duplicate object key 'red'
```
+### json.exception.type_error.319
+
+[`basic_json_document::set`](../api/basic_json_document/set.md) and
+[`basic_json_document::push_back`](../api/basic_json_document/push_back.md) can store any `basic_json` value except
+a binary one: a `json_document` has no representation for [binary values](../features/binary_values.md), which only
+ever arise from parsing a binary format or from an explicit [`json::binary`](../api/basic_json/binary.md) value, not
+from JSON text.
+
+!!! failure "Example message"
+
+ ```
+ [json.exception.type_error.319] cannot store a binary value in a json_document
+ ```
+
+!!! note
+
+ This exception was added in version 3.13.0, together with editable [`json_document`s](../features/json_view.md).
+
### json.exception.type_error.321
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
@@ -1056,13 +1074,19 @@ MessagePack's ext type and BSON's binary subtype are each stored in a single byt
[`basic_json_document::parse()`](../api/basic_json_document/parse.md) and the other parsing functions of
[`basic_json_document`](../api/basic_json_document/index.md) index a value's position in the source text in 32 bits,
-so they do not support an input of 4294967280 bytes (4 GiB minus 16 bytes) or more.
+so they do not support an input of 4294967280 bytes (4 GiB minus 16 bytes) or more. The same 32-bit limit applies to an **editable** document's own
+storage: [`set`](../api/basic_json_document/set.md) and [`push_back`](../api/basic_json_document/push_back.md) throw
+this exception once the strings and number tokens written by edits reach 4 GiB in total, or once more than
+4294967295 arrays/objects have had an element set or appended to them.
-!!! failure "Example message"
+!!! failure "Example messages"
```
[json.exception.out_of_range.416] input of 4294967280 bytes or more is not supported by json_document
```
+ ```
+ [json.exception.out_of_range.416] edits of 4 GiB or more are not supported by json_document
+ ```
!!! note
diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml
index 7b7783e06..0c9d43c3d 100644
--- a/docs/mkdocs/mkdocs.yml
+++ b/docs/mkdocs/mkdocs.yml
@@ -238,14 +238,18 @@ nav:
- 'Overview': api/basic_json_document/index.md
- '(Constructor)': api/basic_json_document/basic_json_document.md
- 'accept': api/basic_json_document/accept.md
+ - 'erase': api/basic_json_document/erase.md
+ - 'insert': api/basic_json_document/insert.md
- 'is_discarded': api/basic_json_document/is_discarded.md
- 'memory_usage': api/basic_json_document/memory_usage.md
- 'node_count': api/basic_json_document/node_count.md
- 'owns_source': api/basic_json_document/owns_source.md
- 'parse': api/basic_json_document/parse.md
- 'parse_copy': api/basic_json_document/parse_copy.md
+ - 'push_back': api/basic_json_document/push_back.md
- 'read': api/basic_json_document/read.md
- 'root': api/basic_json_document/root.md
+ - 'set': api/basic_json_document/set.md
- 'shrink_to_fit': api/basic_json_document/shrink_to_fit.md
- 'source': api/basic_json_document/source.md
- basic_json_view:
@@ -307,6 +311,8 @@ nav:
- 'to_json': api/adl_serializer/to_json.md
- 'json': api/json.md
- 'json_document': api/json_document.md
+ - 'json_editable_document': api/json_editable_document.md
+ - 'json_editable_view': api/json_editable_view.md
- json_pointer:
- 'Overview': api/json_pointer/index.md
- '(Constructor)': api/json_pointer/json_pointer.md
@@ -348,6 +354,8 @@ nav:
- 'operator""_json_pointer': api/operator_literal_json_pointer.md
- 'ordered_json': api/ordered_json.md
- 'ordered_json_document': api/ordered_json_document.md
+ - 'ordered_json_editable_document': api/ordered_json_editable_document.md
+ - 'ordered_json_editable_view': api/ordered_json_editable_view.md
- 'ordered_json_view': api/ordered_json_view.md
- 'ordered_map': api/ordered_map.md
- macros:
diff --git a/include/nlohmann/detail/view/document_data.hpp b/include/nlohmann/detail/view/document_data.hpp
index 926e612d3..3aebac00e 100644
--- a/include/nlohmann/detail/view/document_data.hpp
+++ b/include/nlohmann/detail/view/document_data.hpp
@@ -12,7 +12,9 @@
#include // size_t
#include // uint32_t
#include // memcpy
-#include // numeric_limits
+#include // less
+#include