Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recursive update function #3069

Merged
merged 4 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions doc/examples/update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ using json = nlohmann::json;
int main()
{
// create two JSON objects
json o1 = R"( {"color": "red", "price": 17.99} )"_json;
json o2 = R"( {"color": "blue", "speed": 100} )"_json;
json o1 = R"( {"color": "red", "price": 17.99, "names": {"de": "Flugzeug"}} )"_json;
json o2 = R"( {"color": "blue", "speed": 100, "names": {"en": "plane"}} )"_json;
json o3 = o1;

// add all keys from o2 to o1 (updating "color")
// add all keys from o2 to o1 (updating "color", replacing "names")
o1.update(o2);

// output updated object o1
// add all keys from o2 to o1 (updating "color", merging "names")
o3.update(o2, true);

// output updated object o1 and o3
std::cout << std::setw(2) << o1 << '\n';
std::cout << std::setw(2) << o3 << '\n';
}
12 changes: 12 additions & 0 deletions doc/examples/update.output
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
{
"color": "blue",
"names": {
"en": "plane"
},
"price": 17.99,
"speed": 100
}
{
"color": "blue",
"names": {
"de": "Flugzeug",
"en": "plane"
},
"price": 17.99,
"speed": 100
}
13 changes: 9 additions & 4 deletions doc/examples/update__range.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ using json = nlohmann::json;
int main()
{
// create two JSON objects
json o1 = R"( {"color": "red", "price": 17.99} )"_json;
json o2 = R"( {"color": "blue", "speed": 100} )"_json;
json o1 = R"( {"color": "red", "price": 17.99, "names": {"de": "Flugzeug"}} )"_json;
json o2 = R"( {"color": "blue", "speed": 100, "names": {"en": "plane"}} )"_json;
json o3 = o1;

// add all keys from o2 to o1 (updating "color")
// add all keys from o2 to o1 (updating "color", replacing "names")
o1.update(o2.begin(), o2.end());

// output updated object o1
// add all keys from o2 to o1 (updating "color", merging "names")
o3.update(o2.begin(), o2.end(), true);

// output updated object o1 and o3
std::cout << std::setw(2) << o1 << '\n';
std::cout << std::setw(2) << o3 << '\n';
}
12 changes: 12 additions & 0 deletions doc/examples/update__range.output
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
{
"color": "blue",
"names": {
"en": "plane"
},
"price": 17.99,
"speed": 100
}
{
"color": "blue",
"names": {
"de": "Flugzeug",
"en": "plane"
},
"price": 17.99,
"speed": 100
}
72 changes: 68 additions & 4 deletions doc/mkdocs/docs/api/basic_json/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

```cpp
// (1)
void update(const_reference j);
void update(const_reference j, bool merge_objects = false);

// (2)
void update(const_iterator first, const_iterator last);
void update(const_iterator first, const_iterator last, bool merge_objects = false);
```

1. Inserts all values from JSON object `j` and overwrites existing keys.
2. Inserts all values from from range `[first, last)` and overwrites existing keys.
1. Inserts all values from JSON object `j`.
2. Inserts all values from from range `[first, last)`

When `merge_objects` is `#!c false` (default), existing keys are overwritten. When `merge_objects` is `#!c true`,
recursively merges objects with common keys.

The function is motivated by Python's [dict.update](https://docs.python.org/3.6/library/stdtypes.html#dict.update)
function.
Expand All @@ -19,6 +22,10 @@ function.
`j` (in)
: JSON object to read values from

`merge_objects` (in)
: when `#!c true`, existing keys are not overwritten, but contents of objects are merged recursively (default:
`#!c false`)

`first` (in)
: begin of the range of elements to insert

Expand Down Expand Up @@ -73,6 +80,63 @@ function.
--8<-- "examples/update__range.output"
```

??? example

One common usecase for this function is the handling of user settings. Assume your application can configured in
some aspects:

```json
{
"color": "red",
"active": true,
"name": {"de": "Maus", "en": "mouse"}
}
```

The user may override the default settings selectively:

```json
{
"color": "blue",
"name": {"es": "ratón"},
}
```

Then `update` manages the merging of default settings and user settings:

```cpp
auto user_settings = json::parse("config.json");
auto effective_settings = get_default_settings();
effective_settings.update(user_settings);
```

Now `effective_settings` contains the default settings, but those keys set by the user are overwritten:

```json
{
"color": "blue",
"active": true,
"name": {"es": "ratón"}
}
```

Note existing keys were just overwritten. To merge objects, `merge_objects` setting should be set to `#!c true`:

```cpp
auto user_settings = json::parse("config.json");
auto effective_settings = get_default_settings();
effective_settings.update(user_settings, true);
```

```json
{
"color": "blue",
"active": true,
"name": {"de": "Maus", "en": "mouse", "es": "ratón"}
}
```

## Version history

- Added in version 3.0.0.
- Added `merge_objects` parameter in 3.10.4.
58 changes: 24 additions & 34 deletions include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5984,6 +5984,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
Inserts all values from JSON object @a j and overwrites existing keys.

@param[in] j JSON object to read values from
@param[in] merge_objects when true, existing keys are not overwritten, but
contents of objects are merged recursively
(default: false)

@throw type_error.312 if called on JSON values other than objects; example:
`"cannot use update() with string"`
Expand All @@ -5995,34 +5998,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec

@sa https://docs.python.org/3.6/library/stdtypes.html#dict.update

@since version 3.0.0
@since version 3.0.0, `merge_objects` parameter added in 3.10.4.
*/
void update(const_reference j)
void update(const_reference j, bool merge_objects = false)
{
// implicitly convert null value to an empty object
if (is_null())
{
m_type = value_t::object;
m_value.object = create<object_t>();
assert_invariant();
}

if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()), *this));
}
if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
{
JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()), *this));
}

for (auto it = j.cbegin(); it != j.cend(); ++it)
{
m_value.object->operator[](it.key()) = it.value();
#if JSON_DIAGNOSTICS
m_value.object->operator[](it.key()).m_parent = this;
#endif
}
update(j.begin(), j.end(), merge_objects);
}

/*!
Expand All @@ -6033,12 +6013,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec

@param[in] first begin of the range of elements to insert
@param[in] last end of the range of elements to insert
@param[in] merge_objects when true, existing keys are not overwritten, but
contents of objects are merged recursively
(default: false)

@throw type_error.312 if called on JSON values other than objects; example:
`"cannot use update() with string"`
@throw invalid_iterator.202 if iterator @a first or @a last does does not
point to an object; example: `"iterators first and last must point to
objects"`
@throw type_error.312 if iterator @a first or @a last does does not
point to an object; example: `"cannot use update() with string"`
@throw invalid_iterator.210 if @a first and @a last do not belong to the
same JSON value; example: `"iterators do not fit"`

Expand All @@ -6049,9 +6031,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec

@sa https://docs.python.org/3.6/library/stdtypes.html#dict.update

@since version 3.0.0
@since version 3.0.0, `merge_objects` parameter added in 3.10.4.
*/
void update(const_iterator first, const_iterator last)
void update(const_iterator first, const_iterator last, bool merge_objects = false)
{
// implicitly convert null value to an empty object
if (is_null())
Expand All @@ -6073,14 +6055,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}

// passed iterators must belong to objects
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()
|| !last.m_object->is_object()))
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()))
{
JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", *this));
JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(first.m_object->type_name()), *first.m_object));
}

for (auto it = first; it != last; ++it)
{
if (merge_objects && it.value().is_object())
{
auto it2 = m_value.object->find(it.key());
if (it2 != m_value.object->end())
{
it2->second.update(it.value(), true);
continue;
}
}
m_value.object->operator[](it.key()) = it.value();
#if JSON_DIAGNOSTICS
m_value.object->operator[](it.key()).m_parent = this;
Expand Down
58 changes: 24 additions & 34 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23471,6 +23471,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
Inserts all values from JSON object @a j and overwrites existing keys.

@param[in] j JSON object to read values from
@param[in] merge_objects when true, existing keys are not overwritten, but
contents of objects are merged recursively
(default: false)

@throw type_error.312 if called on JSON values other than objects; example:
`"cannot use update() with string"`
Expand All @@ -23482,34 +23485,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec

@sa https://docs.python.org/3.6/library/stdtypes.html#dict.update

@since version 3.0.0
@since version 3.0.0, `merge_objects` parameter added in 3.10.4.
*/
void update(const_reference j)
void update(const_reference j, bool merge_objects = false)
{
// implicitly convert null value to an empty object
if (is_null())
{
m_type = value_t::object;
m_value.object = create<object_t>();
assert_invariant();
}

if (JSON_HEDLEY_UNLIKELY(!is_object()))
{
JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()), *this));
}
if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
{
JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()), *this));
}

for (auto it = j.cbegin(); it != j.cend(); ++it)
{
m_value.object->operator[](it.key()) = it.value();
#if JSON_DIAGNOSTICS
m_value.object->operator[](it.key()).m_parent = this;
#endif
}
update(j.begin(), j.end(), merge_objects);
}

/*!
Expand All @@ -23520,12 +23500,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec

@param[in] first begin of the range of elements to insert
@param[in] last end of the range of elements to insert
@param[in] merge_objects when true, existing keys are not overwritten, but
contents of objects are merged recursively
(default: false)

@throw type_error.312 if called on JSON values other than objects; example:
`"cannot use update() with string"`
@throw invalid_iterator.202 if iterator @a first or @a last does does not
point to an object; example: `"iterators first and last must point to
objects"`
@throw type_error.312 if iterator @a first or @a last does does not
point to an object; example: `"cannot use update() with string"`
@throw invalid_iterator.210 if @a first and @a last do not belong to the
same JSON value; example: `"iterators do not fit"`

Expand All @@ -23536,9 +23518,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec

@sa https://docs.python.org/3.6/library/stdtypes.html#dict.update

@since version 3.0.0
@since version 3.0.0, `merge_objects` parameter added in 3.10.4.
*/
void update(const_iterator first, const_iterator last)
void update(const_iterator first, const_iterator last, bool merge_objects = false)
{
// implicitly convert null value to an empty object
if (is_null())
Expand All @@ -23560,14 +23542,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}

// passed iterators must belong to objects
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()
|| !last.m_object->is_object()))
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()))
{
JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", *this));
JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(first.m_object->type_name()), *first.m_object));
}

for (auto it = first; it != last; ++it)
{
if (merge_objects && it.value().is_object())
{
auto it2 = m_value.object->find(it.key());
if (it2 != m_value.object->end())
{
it2->second.update(it.value(), true);
continue;
}
}
m_value.object->operator[](it.key()) = it.value();
#if JSON_DIAGNOSTICS
m_value.object->operator[](it.key()).m_parent = this;
Expand Down
Loading