Change how merge_jsonlike treats lists

Current behaviour is to check item-against-item
and merge based on whether items match, irrelevant
of position. This doesn't produce the results we
need for our usecases (merging data to send to
GOVUK Frontend components).

We actually want:
- items to be compared based on their position
- new primitive items at the same position to
  overwrite existing ones
- dicts or lists at the same position to be merged

For example,

Starting with this list:

[{"name": "option-1", "value": "1"}]

Merging in this list:

[{"hint": {"text": "Choose one option"}}]

You currently get this:

[
  {"name": "option-1", "value": "1"},
  {"hint": {"text": "Choose one option"}}
]

We want to get this:

[
  {
    "name": "option-1", "value": "1",
    "hint": {"text": "Choose one option"}
  }
]
This commit is contained in:
Tom Byers
2021-01-21 16:49:12 +00:00
parent e06c1f5daa
commit 23d391e3fc
2 changed files with 15 additions and 8 deletions

View File

@@ -651,8 +651,13 @@ def merge_jsonlike(source, destination):
return True
def merge_lists(source, destination):
for item in destination:
if item not in source:
last_dest_idx = len(destination) - 1
for idx, item in enumerate(destination):
if idx <= last_dest_idx:
# assign destination value if can't be merged into source
if merge_items(source[idx], destination[idx]) is False:
source[idx] = destination[idx]
else:
source.append(item)
def merge_dicts(source, destination):

View File

@@ -614,18 +614,20 @@ def test_get_sample_template_returns_template(template_type):
({"a": "b"}, {"c": "d"}, {"a": "b", "c": "d"}),
# dicts with nested dict, both under same key, additive behaviour:
({"a": {"b": "c"}}, {"a": {"e": "f"}}, {"a": {"b": "c", "e": "f"}}),
# same key in both dicts, value is a string, destination supersedes source:
# same key in both dicts, value is a string, destination supercedes source:
({"a": "b"}, {"a": "c"}, {"a": "c"}),
# lists with same length but different items, destination supercedes source:
(["b", "c", "d"], ["b", "e", "f"], ["b", "e", "f"]),
# lists in dicts behave as top level lists
({"a": ["b", "c", "d"]}, {"a": ["b", "e", "f"]}, {"a": ["b", "c", "d", "e", "f"]}),
# lists with same string in both result in a list of unique values
(["a", "b", "c", "d"], ["d", "e", "f"], ["a", "b", "c", "d", "e", "f"]),
({"a": ["b", "c", "d"]}, {"a": ["b", "e", "f"]}, {"a": ["b", "e", "f"]}),
# lists with same string in both, at different positions, result in duplicates keeping their positions
(["a", "b", "c", "d"], ["d", "e", "f"], ["d", "e", "f", "d"]),
# lists with same dict in both result in a list with one instance of that dict
([{"b": "c"}], [{"b": "c"}], [{"b": "c"}]),
# if dicts in lists have different values, they are not merged
([{"b": "c"}], [{"b": "e"}], [{"b": "c"}, {"b": "e"}]),
([{"b": "c"}], [{"b": "e"}], [{"b": "e"}]),
# merge a dict with a null object returns that dict (does not work the other way round)
({"a": {"b": "c"}}, None, {"a": {"b": "c"}}),
({"a": {"b": "c"}}, None, {"a": {"b": "c"}})
])
def test_merge_jsonlike_merges_jsonlike_objects_correctly(source_object, destination_object, expected_result):
merge_jsonlike(source_object, destination_object)