From cdbd969fe495843bba25a4419ce8ff5ac56c31b2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 25 Oct 2026 13:20:10 +0000 Subject: [PATCH] Throw out_of_range.411 for JSON Patch add into a null parent (#5917) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01MKJf48vnGtTE25KscmjfaW Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/patch.md | 1 + .../docs/api/basic_json/patch_inplace.md | 1 + docs/mkdocs/docs/home/exceptions.md | 2 ++ include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- tests/src/unit-json_patch.cpp | 18 ++++++++++++++++++ 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/patch.md b/docs/mkdocs/docs/api/basic_json/patch.md index ed26135a9..39f601683 100644 --- a/docs/mkdocs/docs/api/basic_json/patch.md +++ b/docs/mkdocs/docs/api/basic_json/patch.md @@ -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 diff --git a/docs/mkdocs/docs/api/basic_json/patch_inplace.md b/docs/mkdocs/docs/api/basic_json/patch_inplace.md index 5b0d501bb..1bcfc0067 100644 --- a/docs/mkdocs/docs/api/basic_json/patch_inplace.md +++ b/docs/mkdocs/docs/api/basic_json/patch_inplace.md @@ -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 diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 4b8f6edd9..0a31d2444 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -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 diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 3d40682d9..c2dddd55a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -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: diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 1beba149b..f0978d69e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -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: diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index 67ffee6dd..de868737f 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -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}};