Fix update(merge_objects=true) throwing on primitive-to-object merge (#5414)
When merge_objects is true, recurse only if the existing value is an
object. Otherwise overwrite, matching the documented "all other values
are overwritten as usual" behavior.
Fixes #5402
Signed-off-by: elix3r <157088510+22elix3r@users.noreply.github.com>
diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp
index 235e6b7..4e05706 100644
--- a/include/nlohmann/json.hpp
+++ b/include/nlohmann/json.hpp
@@ -3516,7 +3516,10 @@
if (merge_objects && it.value().is_object())
{
auto it2 = m_data.m_value.object->find(it.key());
- if (it2 != m_data.m_value.object->end())
+ // Only recurse when the existing value is itself an object.
+ // Otherwise overwrite, matching the documented "all other values
+ // are overwritten as usual" behavior (see #5402).
+ if (it2 != m_data.m_value.object->end() && it2->second.is_object())
{
it2->second.update(it.value(), true);
#if JSON_DIAGNOSTICS
diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp
index 904d865..4c346b4 100644
--- a/single_include/nlohmann/json.hpp
+++ b/single_include/nlohmann/json.hpp
@@ -24870,7 +24870,10 @@
if (merge_objects && it.value().is_object())
{
auto it2 = m_data.m_value.object->find(it.key());
- if (it2 != m_data.m_value.object->end())
+ // Only recurse when the existing value is itself an object.
+ // Otherwise overwrite, matching the documented "all other values
+ // are overwritten as usual" behavior (see #5402).
+ if (it2 != m_data.m_value.object->end() && it2->second.is_object())
{
it2->second.update(it.value(), true);
#if JSON_DIAGNOSTICS
diff --git a/tests/src/unit-modifiers.cpp b/tests/src/unit-modifiers.cpp
index 18185ec..de14b3f 100644
--- a/tests/src/unit-modifiers.cpp
+++ b/tests/src/unit-modifiers.cpp
@@ -801,6 +801,30 @@
j1.update(j2, true);
CHECK(j1 == json({{"string", "t"}, {"numbers", 1}}));
}
+
+ SECTION("overwrite primitive with object")
+ {
+ json j1 = {{"k", 1}};
+ json const j2 = {{"k", {{"x", 2}}}};
+ j1.update(j2, true);
+ CHECK(j1 == json({{"k", {{"x", 2}}}}));
+ }
+
+ SECTION("overwrite array with object")
+ {
+ json j1 = {{"k", {1, 2}}};
+ json const j2 = {{"k", {{"x", 2}}}};
+ j1.update(j2, true);
+ CHECK(j1 == json({{"k", {{"x", 2}}}}));
+ }
+
+ SECTION("overwrite nested primitive with object")
+ {
+ json j1 = {{"k", {{"inner", 1}}}};
+ json const j2 = {{"k", {{"inner", {{"x", 2}}}}}};
+ j1.update(j2, true);
+ CHECK(j1 == json({{"k", {{"inner", {{"x", 2}}}}}}));
+ }
}
}
}
diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp
index 29b52d7..2e7450e 100644
--- a/tests/src/unit-regression2.cpp
+++ b/tests/src/unit-regression2.cpp
@@ -1555,4 +1555,15 @@
}
}
+TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object")
+{
+ json t = {{"k", 1}};
+ t.update(json{{"k", {{"x", 2}}}}, true);
+ CHECK(t == json({{"k", {{"x", 2}}}}));
+
+ json mixed = {{"keep", {{"a", 1}}}, {"replace", 1}};
+ mixed.update(json{{"keep", {{"b", 2}}}, {"replace", {{"x", 2}}}}, true);
+ CHECK(mixed == json({{"keep", {{"a", 1}, {"b", 2}}}, {"replace", {{"x", 2}}}}));
+}
+
DOCTEST_CLANG_SUPPRESS_WARNING_POP