Merge two multi dimention dict and only add what is missing

Question:

I have been trying to merge these two:

A
{'v1': {'configuration': {'$schema': '...', 'network': {'$schema': '...'}}}}
E
{'v1': {'configuration': {'network': {'entries': {'$schema': '...'}}}}}

The result should be:

{'v1': {'configuration': {'$schema': '...', 'network': {'$schema': '...', 'entries': {'$schema': '...'}}}}}

This is one set of data. So I have other situations where that follow v1 is not just configuration. All I am trying to do is add in the dict the elements from another dict that it doesn’t have. Also the A always has precedent.

I tried using one of the examples: Python merge two lists of dicts, where dict key matches

    def merge_list_of_dicts(d1: dict, d2: dict):
        def merge(list_of_dicts, current={}):
            try:
                get_key = lambda d: next(iter(d))
                get_value = lambda d: next(iter(d.values()))

                for d in list_of_dicts:
                    key = get_key(d)
                    value = get_value(d)

                    if key not in current:
                        current[key] = value
                    else:
                        current[key].update(value)

                return current
            except:
                return current

        return merge(d2, merge(d1))

print(merge_list_of_dicts(A, E))

I added the try/exception because without it I get this error: AttributeError: ‘str’ object has no attribute ‘values’

The result is:

{}

I also tried:

def myseconfunc(a, b):

        print('===========')
        
        a_keys = set(a.keys())
        b_keys = set(b.keys())

        commons = a_keys.intersection(b_keys)
        #print('commons')
        #print(commons)
        for common in commons:
            if type(a) == dict:
                a = myseconfunc(a[common], b[common])
                print('common:')
                print(common)
                print('a:')
                print(a)
                print('b:')
                print(b[common])

                a.update(b[common])

        return a

But I get this:

{'$schema': '...', 'entries': {'$schema': '...'}, 'network': {'entries': {'$schema': '...'}}, 'configuration': {'network': {'entries': {'$schema': '...'}}}}

For more details here is what the prints look like:

===========
common:
network
a:
{'$schema': '...'}
b:
{'entries': {'$schema': '...'}}
===========
common:
configuration
a:
{'$schema': '...', 'entries': {'$schema': '...'}}
b:
{'network': {'entries': {'$schema': '...'}}}
===========
common:
v1
a:
{'$schema': '...', 'entries': {'$schema': '...'}, 'network': {'entries': {'$schema': '...'}}}
b:
{'configuration': {'network': {'entries': {'$schema': '...'}}}}
===========

I am using: Python 3.9.13

Asked By: jnbdz

||

Answers:

Here is a recursive algorithm that passes your example

import copy

A = {'v1': {'configuration': {'$schema': '...', 'network': {'$schema': '...'}}}}
E = {'v1': {'configuration': {'network': {'entries': {'$schema': '...'}}}}}

expected = {'v1': {'configuration': {'$schema': '...', 'network': {'$schema': '...', 'entries': {'$schema': '...'}}}}}

def safe_update_dict(d_to: dict, d_from: dict, inplace: bool=False):
    def fn(d_to: dict, d_from: dict):
        for k in d_from:
            if k in d_to:
                if isinstance(d_to[k], dict):
                    d_to[k] = fn(d_to[k], d_from[k])
            else:
                d_to[k] = d_from[k]
        return d_to
    if inplace:
        return fn(d_to, d_from)
    return fn(copy.deepcopy(d_to), d_from)

rtn = safe_update_dict(A, E)
print(str(expected) == str(rtn))  # True
Answered By: Tom McLean
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.