eljobso
eljobso

Reputation: 362

Replace placeholder in nested dict with values of mapping dict

I have a nested dict, which can contain placeholders in both keys and values.

example_dict = {'dict1': {'%(map3)s': {'data': 'tmp'},
                                 '%(map2)s': {'freshdata': 'testtest'}},
             'dict2': {'%(map3)s': {'data': '%(map1)s'}, '%(map3)s': {'status': 'available'}}}

I have a mapping dict, with the placeholder mapping:

mapping_dict= {
    "map1": [1,2,2],
    "map2": "qwerz",
    "map3": "asdfasdf"
}

If the placeholder is at a VALUE position, it can also happen that the corresponding mapping of the mapping_dict contains another datatype than a string as value, e.g. list or an int. How can I pass this datatype to the original dict? I don't know howto make a placeholder e.g. for a list.

Info: It can happen that the mapping_dict contains more keys than the given example_dict contains.

I would like to have a function which replaces the placeholders of a given dict with the values of the mapping_dict.

What would be a good recursive implementation for this?

Upvotes: 1

Views: 2185

Answers (3)

Jon Clements
Jon Clements

Reputation: 142156

Using a stack to navigate the key value pairs, you can pop out keys to rename them and for the values, do the same except try and see if you can evaluate them as Python literals using ast.literal_eval to handle your list case.

import ast
from copy import deepcopy

example_dict = {
    'dict1': {
        '%(map3)s': {'data': 'tmp'},
        '%(map2)s': {'freshdata': 'testtest'}
    },
    'dict2': {
        '%(map3)s': {'data': '%(map1)s'}
    }
}

mapping_dict= {
    "map1": [1,2,2],
    "map2": "qwerz",
    "map3": "asdfasdf"
}

def sub_placeholders(orig, subs):
    d = deepcopy(orig)
    todo = [d]
    while todo:
        nxt = todo.pop()
        for k, v in nxt.items():
            nxt[k % mapping_dict] = nxt.pop(k)
            if isinstance(v, dict):
                todo.append(v)
            elif isinstance(v, str):
                nxt[k] = v % subs
                try:
                    nxt[k] = ast.literal_eval(nxt[k])
                except ValueError:
                    pass
    return d

Running sub_placeholders(example_dict, example_mapping) will give you:

{'dict1': {'asdfasdf': {'data': 'tmp'}, 'qwerz': {'freshdata': 'testtest'}},
 'dict2': {'asdfasdf': {'data': [1, 2, 2]}}}

Upvotes: 3

martineau
martineau

Reputation: 123473

I think this does what you want, recursively. It creates a copy of the original dictionary and then modifies that so original can be reused.

from pprint import pprint
import copy

try:
    stringtype = basestring
except NameError:
    stringtype = str  # Python 3

def subst(mapping, replacements):

    def do_subst(mapping, replacements):
        for k, v in list(mapping.items()):
            newk, newv = k, v
            changed = False

            if isinstance(k, stringtype):
                newk = k % replacements
                if newk != k:
                    changed = True

            if isinstance(v, stringtype):
                newv = v % replacements
                if newv != v:
                    changed = True
            elif isinstance(v, dict):
                newv = do_subst(v, replacements)
                if newv != v:
                    changed = True

            if changed:
                del mapping[k]
                mapping[newk] = newv

        return mapping

    return do_subst(copy.deepcopy(mapping), replacements)

example_dict = {'dict1': {'%(map1)s': {'data': 'tmp'},
                          '%(map2)s': {'freshdata': 'testtest'}},
                'dict2': {'%(map1)s': {'data': '%(map1)s'},
                          '%(map2)s': {'status': 'available'}}}

mapping_dict= {"map1": "asdf", "map2": "qwerz"}

print('Before')
pprint(example_dict)
result = subst(example_dict, mapping_dict)
print('After')
pprint(result)

Output:

 Before
 {'dict1': {'%(map1)s': {'data': 'tmp'}, '%(map2)s': {'freshdata': 'testtest'}},
  'dict2': {'%(map1)s': {'data': '%(map1)s'},
            '%(map2)s': {'status': 'available'}}}
 After
 {'dict1': {'asdf': {'data': 'tmp'}, 'qwerz': {'freshdata': 'testtest'}},
  'dict2': {'asdf': {'data': 'asdf'}, 'qwerz': {'status': 'available'}}}

Upvotes: 1

alecxe
alecxe

Reputation: 473873

Here is one recursive option deleting the existing keys and adding new keys them applying the "format" with "named" placeholders. Note: we are modifying the input dictionary in this case:

from pprint import pprint

example_dict = {'dict1': {'%(map1)s': {'data': 'tmp'}, '%(map2)s': {'freshdata': 'testtest'}},
                'dict2': {'%(map1)s': {'data': '%(map1)s'}, '%(map2)s': {'status': 'available'}}}

mapping_dict= {
    "map1": "asdf",
    "map2": "qwerz",
}


def apply_placeholder(d, placeholder):
    for key, value in d.items():
        del d[key]
        if isinstance(value, dict):
            d[key % placeholder] = value
            apply_placeholder(value, placeholder)
        else:
            d[key % placeholder] = value % placeholder


apply_placeholder(example_dict, mapping_dict)
pprint(example_dict)

Prints:

{'dict1': {'asdf': {'data': 'tmp'}, 'qwerz': {'freshdata': 'testtest'}},
 'dict2': {'asdf': {'data': 'asdf'}, 'qwerz': {'status': 'available'}}}

I don't particularly like the del calls and modifying the input object here and would be happy to see a better option.

Upvotes: 3

Related Questions