13

How do I change all dots . to underscores (in the dict's keys), given an arbitrarily nested dictionary?

What I tried is write two loops, but then I would be limited to 2-level-nested dictionaries.

This ...

{
    "brown.muffins": 5,
    "green.pear": 4,
    "delicious.apples": {
        "green.apples": 2
    {
}

... should become:

{
    "brown_muffins": 5,
    "green_pear": 4,
    "delicious_apples": {
        "green_apples": 2
    {
}

Is there an elegant way?

8
  • 2
    Define method to loop over dictionary keys/values, if key has dot, replace, if value is another dict, enter recursion (call the same method but now with this dict) Commented Jan 5, 2016 at 15:24
  • 1
    What have you tried? Don't get that 'arbitrarely nested' with 'got two loops'. Commented Jan 5, 2016 at 15:25
  • The beauty of recursion, baby Commented Jan 5, 2016 at 15:26
  • 1
    @PeterWood - That's a possibility with any recursion. If the structure isn't nested very deeply, it'll probably be fine. Commented Jan 5, 2016 at 15:36
  • 2
    @PeterWood stack overflow, baby :P Commented Jan 5, 2016 at 15:37

2 Answers 2

22

You can write a recursive function, like this

from collections.abc import Mapping
def rec_key_replace(obj):
    if isinstance(obj, Mapping):
        return {key.replace('.', '_'): rec_key_replace(val) for key, val in obj.items()}
    return obj

and when you invoke this with the dictionary you have shown in the question, you will get a new dictionary, with the dots in keys replaced with _s

{'delicious_apples': {'green_apples': 2}, 'green_pear': 4, 'brown_muffins': 5}

Explanation

Here, we just check if the current object is an instance of dict and if it is, then we iterate the dictionary, replace the key and call the function recursively. If it is actually not a dictionary, then return it as it is.

Sign up to request clarification or add additional context in comments.

4 Comments

why not dict instead of Mapping ?
@IronFist When you check if it is an instance of dict, you are restricting it to just dict and its subclasses. Instead, you relax the condition a little and allow any class which has few methods which would make it look like a dictionary. You might want to look at mapping definition.
forgive lack of knowledge here..:P .. One more thing, it's just as suggestion, don't you think it's safe to wrap it with tr-except block to be safe from reaching maximum iteration if that matters, and raise an exception when so or any control measure against it...?
@IronFist This is not production ready code :D I don't expect anyone to use it as it is. They can increase the recursion limit if they like :-)
7

Assuming . is only present in keys and all the dictionary's contents are primitive literals, the really cheap way would be to use str() or repr(), do the replacement, then ast.literal_eval() to get it back:

d ={
    "brown.muffins": 5,
    "green.pear": 4,
    "delicious_apples": {
        "green.apples": 2
    } # correct brace
}

Result:

>>> import ast
>>> ast.literal_eval(repr(d).replace('.','_'))
{'delicious_apples': {'green_apples': 2}, 'green_pear': 4, 'brown_muffins': 5}

If the dictionary has . outside of keys, we can replace more carefully by using a regular expression to look for strings like 'ke.y': and replace only those bits:

>>> import re
>>> ast.literal_eval(re.sub(r"'(.*?)':", lambda x: x.group(0).replace('.','_'), repr(d)))
{'delicious_apples': {'green_apples': 2}, 'green_pear': 4, 'brown_muffins': 5}

If your dictionary is very complex, with '.' in values and dictionary-like strings and so on, use a real recursive approach. Like I said at the start, though, this is the cheap way.

12 Comments

That would also replace in the values, though
@cricket_007 I think he's talking about integral amounts of fruits and confections.
@erip - still not what OP asked
Assuming you don't have {'my.key': __my_method} :P
@erip ideally, it should also be useful for future visitors, not just OPs specific case. Very creative nonetheless
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.