mirror of
https://github.com/nlohmann/json.git
synced 2026-10-26 15:58:55 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdbd969fe4 |
No files matched your search
@@ -39,33 +39,13 @@ Basic guarantee: if an exception is thrown during the operation, the JSON value
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the total number of values (including nested ones) in `apply_patch`, times the cost of a key-based object
|
||||
operation (looking up, adding, or erasing a member): logarithmic for [`json`](../json.md), and linear for
|
||||
[`ordered_json`](../ordered_json.md) (see [`ordered_map` complexity](../ordered_map.md#complexity)). For
|
||||
`ordered_json`, patching an object that has `n` members with a patch that has `m` members therefore takes O(n·m). The
|
||||
cost also includes one copy of `apply_patch` (see the notes below).
|
||||
Linear in the lengths of `apply_patch`.
|
||||
|
||||
## Notes
|
||||
|
||||
- `apply_patch` may be `#!cpp *this` itself or refer to a value contained in `#!cpp *this` (for example, a subobject
|
||||
returned by `#!cpp (*this)[key]`); it is read as it was when `merge_patch()` was called, before any modification of
|
||||
`#!cpp *this`. To make this possible, the function copies `apply_patch` before it modifies `#!cpp *this`, so the cost
|
||||
includes one copy of the patch.
|
||||
- A merge patch uses `#!json null` to remove a member, so it cannot set a member to `#!json null`: applying
|
||||
`#!json {"a":null}` to `#!json {"a":1}` gives `#!json {}`.
|
||||
- Arrays are not merged: an array in the patch replaces the target value as a whole, and a `#!json null` inside an
|
||||
array is kept as an element. `#!json null` members of the target that the patch does not mention are preserved.
|
||||
- A `#!json null` in the patch only removes a member; it does not prevent the creation of the enclosing objects.
|
||||
Applying `#!json {"a":{"b":null}}` to `#!json {}` gives `#!json {"a":{}}` (see example 15 in Appendix A of the RFC).
|
||||
- If `apply_patch` is not an object, it replaces the target as a whole. In particular, a `#!json null` patch turns the
|
||||
target into `#!json null`. If `apply_patch` is an object and the target is not, the target is first replaced by an
|
||||
empty object, so applying `#!json {}` to `#!json 1` gives `#!json {}`.
|
||||
- A merge patch cannot express every change; for example, it cannot reorder array elements or remove a specific array
|
||||
element. Use [JSON Patch](../../features/json_patch.md) ([RFC 6902](https://tools.ietf.org/html/rfc6902)) for that.
|
||||
[`merge_diff`](merge_diff.md) has a matching limitation, see its notes.
|
||||
- A [discarded](is_discarded.md) value is treated like any other non-object value: a discarded
|
||||
`apply_patch` turns the target into a discarded value, and an object patch replaces a discarded target by an object
|
||||
as described above.
|
||||
`apply_patch` may be `#!cpp *this` itself or refer to a value contained in `#!cpp *this` (for example, a subobject
|
||||
returned by `#!cpp (*this)[key]`); it is read as it was when `merge_patch()` was called, before any modification of
|
||||
`#!cpp *this`.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ is thrown. In any case, the original value is not changed: the patch is applied
|
||||
- Added in version 2.0.0.
|
||||
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
||||
target location has a non-object/non-array parent in version 3.13.0.
|
||||
This includes a `null` parent, which was previously silently converted to an object, in version 3.13.0.
|
||||
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
|
||||
location has a non-object/non-array parent in version 3.13.0.
|
||||
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
|
||||
|
||||
@@ -106,6 +106,7 @@ function throws an exception.
|
||||
- Added in version 3.11.0.
|
||||
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
||||
target location has a non-object/non-array parent in version 3.13.0.
|
||||
This includes a `null` parent, which was previously silently converted to an object, in version 3.13.0.
|
||||
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
|
||||
location has a non-object/non-array parent in version 3.13.0.
|
||||
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The library supports JSON Merge Patch ([RFC 7396](https://tools.ietf.org/html/rfc7396)) as a patch format.
|
||||
The merge patch format is primarily intended for use with the HTTP PATCH method as a means of describing a set of
|
||||
modifications to a target resource's content. Applying a merge patch changes the JSON value the patch is applied to.
|
||||
modifications to a target resource's content. This function applies a merge patch to the current JSON value.
|
||||
|
||||
Instead of using [JSON Pointer](json_pointer.md) to specify values to be manipulated, it describes the changes using a
|
||||
syntax that closely mimics the document being modified. Unlike [JSON Patch](json_patch.md), a JSON Merge Patch cannot
|
||||
@@ -19,18 +19,6 @@ another.
|
||||
[`update`](../api/basic_json/update.md) with `merge_objects` set to `#!cpp true`; see
|
||||
[Merging objects](modifying_values.md#merging-objects).
|
||||
|
||||
!!! note "Limitations of RFC 7396"
|
||||
|
||||
- A patch cannot set a member to `#!json null`, because `#!json null` removes the member:
|
||||
`#!json {"a":1}` patched with `#!json {"a":null}` gives `#!json {}`.
|
||||
- Arrays are replaced as a whole, never merged, and a `#!json null` inside an array is kept.
|
||||
- `#!json null` members of the target that the patch does not mention are preserved.
|
||||
- A `#!json null` that would remove a member of a not yet existing object still creates that object:
|
||||
`#!json {}` patched with `#!json {"a":{"b":null}}` gives `#!json {"a":{}}`.
|
||||
- A patch that is not an object replaces the whole target (a `#!json null` patch makes the target
|
||||
`#!json null`); an object patch applied to a non-object target starts from an empty object.
|
||||
- [`merge_diff`](../api/basic_json/merge_diff.md#notes) has a matching limitation for `#!json null` members.
|
||||
|
||||
??? example
|
||||
|
||||
The following code shows how a JSON Merge Patch is applied to a JSON document.
|
||||
|
||||
@@ -1100,6 +1100,8 @@ A JSON Patch `add` operation cannot be applied because the target location's par
|
||||
|
||||
This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled.
|
||||
|
||||
A `null` parent is rejected as well. Before version 3.13.0, an `add` into a `null` parent silently converted it to an object (e.g., `add` at `/foo/bar` turned `{"foo":null}` into `{"foo":{"bar":1}}`).
|
||||
|
||||
### json.exception.out_of_range.412
|
||||
|
||||
BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer, and MessagePack
|
||||
|
||||
@@ -7003,7 +7003,6 @@ public:
|
||||
|
||||
switch (parent.m_data.m_type)
|
||||
{
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
{
|
||||
// use operator[] to add value
|
||||
@@ -7035,6 +7034,7 @@ public:
|
||||
|
||||
// the parent of an "add" target must be an object or array
|
||||
// (see #4292)
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
|
||||
Generated
+1
-1
@@ -42793,7 +42793,6 @@ public:
|
||||
|
||||
switch (parent.m_data.m_type)
|
||||
{
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
{
|
||||
// use operator[] to add value
|
||||
@@ -42825,6 +42824,7 @@ public:
|
||||
|
||||
// the parent of an "add" target must be an object or array
|
||||
// (see #4292)
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
|
||||
@@ -1515,6 +1515,24 @@ TEST_CASE("JSON patch - add to a primitive parent (regression #4292)")
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("null parent")
|
||||
{
|
||||
json const doc = {{"foo", nullptr}};
|
||||
json const patch = {{{"op", "add"}, {"path", "/foo/bar"}, {"value", 1}}};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] (/foo) cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("null document")
|
||||
{
|
||||
json const doc = nullptr;
|
||||
json const patch = {{{"op", "add"}, {"path", "/-"}, {"value", 1}}};
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("number parent")
|
||||
{
|
||||
json const doc = {{"foo", 1}};
|
||||
|
||||
@@ -423,86 +423,3 @@ TEST_CASE("merge_patch() with an argument that aliases *this (#5641)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON Merge Patch on non-object, discarded, and binary values")
|
||||
{
|
||||
SECTION("an empty object patch turns a non-object target into an empty object")
|
||||
{
|
||||
json target = 1;
|
||||
target.merge_patch(json::object());
|
||||
CHECK(target == json::object());
|
||||
|
||||
json from_null;
|
||||
from_null.merge_patch(json::object());
|
||||
CHECK(from_null == json::object());
|
||||
}
|
||||
|
||||
SECTION("a non-object patch replaces a deeply nested target")
|
||||
{
|
||||
// checked by type, since == recurses
|
||||
SECTION("number")
|
||||
{
|
||||
json target = json::parse(nested_objects(1000, 0));
|
||||
target.merge_patch(json(42));
|
||||
CHECK(target.is_number_integer());
|
||||
CHECK(target.get<int>() == 42);
|
||||
}
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
json target = json::parse(nested_objects(1000, 0));
|
||||
target.merge_patch(json({1, nullptr}));
|
||||
CHECK(target.is_array());
|
||||
CHECK(target.size() == 2);
|
||||
CHECK(target[1].is_null());
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
{
|
||||
json target = json::parse(nested_objects(1000, 0));
|
||||
target.merge_patch(json(nullptr));
|
||||
CHECK(target.is_null());
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("discarded values")
|
||||
{
|
||||
const json discarded(json::value_t::discarded);
|
||||
|
||||
// a discarded patch is not an object and replaces the target
|
||||
json target = {{"a", 1}};
|
||||
target.merge_patch(discarded);
|
||||
CHECK(target.is_discarded());
|
||||
|
||||
// a discarded target is replaced by an empty object first
|
||||
json discarded_target(json::value_t::discarded);
|
||||
discarded_target.merge_patch(json({{"a", 1}}));
|
||||
CHECK(discarded_target == json({{"a", 1}}));
|
||||
|
||||
json both(json::value_t::discarded);
|
||||
both.merge_patch(discarded);
|
||||
CHECK(both.is_discarded());
|
||||
}
|
||||
|
||||
SECTION("binary values")
|
||||
{
|
||||
const json bin = json::binary({1, 2, 3}, 7);
|
||||
|
||||
// a binary patch is not an object and replaces the target
|
||||
json target = {{"a", 1}};
|
||||
target.merge_patch(bin);
|
||||
CHECK(target.is_binary());
|
||||
CHECK(target.get_binary() == bin.get_binary());
|
||||
|
||||
// a binary target is replaced by an object
|
||||
json binary_target = bin;
|
||||
binary_target.merge_patch(json({{"a", 1}}));
|
||||
CHECK(binary_target == json({{"a", 1}}));
|
||||
|
||||
// binary values nested in the patch are kept
|
||||
json nested = json::object();
|
||||
nested.merge_patch(json({{"a", bin}}));
|
||||
CHECK(nested["a"].is_binary());
|
||||
CHECK(nested["a"].get_binary() == bin.get_binary());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user