user2879704
user2879704

Reputation:

parse a dot seperated string into dictionary variable

I have string values as,

"a"
"a.b"
"b.c.d"

How to convert them into python dictionary variables as,

a
a["b"]
b["c"]["d"]

The first part of the string (before dot) will become the dictionary name and the rest of the substrings will become the dictionary keys

Upvotes: 2

Views: 5898

Answers (5)

Johal
Johal

Reputation: 71

A little late to the party, but a solution to create the actual dict entries from a dotted path can be done with what is essentially fold-right, where the accumulator starts out as the "leaf" value but changes to a dict after first "iteration" of the reversed path

>>> from functools import reduce
>>> path = 'a.b.c'
>>> leaf_val = 'd'
>>> reduce(lambda accum, val: {val: accum}, reversed(path.split(".")), leaf_val)
{'a': {'b': {'c': 'd'}}}

This is not quite what the question asks, but could be worked into the other solution to avoid eval and lengthy recursive function defs

Upvotes: 1

MatrixManAtYrService
MatrixManAtYrService

Reputation: 9131

The pyjq library does something quite similar to this, except you have to explicitly provide a dictionary to be the root, and you have to prefix your strings with a . to refer to whatever dictionary was your root.

python:

import pyjq
d = { 'a' : { 'b' : { 'c' : 'd' } } }
for path in ['.a', '.a.b', '.b.c.d', '.x']:
    print(pyjq.first(path, d))

output:

{'b': {'c': 'd'}}
{'c': 'd'}
None
None

Upvotes: 0

iJames
iJames

Reputation: 650

I ran into this same problem for parsing ini files with dot-delimited keys in different sections. e.g.:

[app]
site1.ftp.host = hostname
site1.ftp.username = username
site1.database.hostname = db_host
; etc..

So I wrote a little function to add "add_branch" to an existing dict tree:

def add_branch(tree, vector, value):
    """
    Given a dict, a vector, and a value, insert the value into the dict
    at the tree leaf specified by the vector.  Recursive!

    Params:
        data (dict): The data structure to insert the vector into.
        vector (list): A list of values representing the path to the leaf node.
        value (object): The object to be inserted at the leaf

    Example 1:
    tree = {'a': 'apple'}
    vector = ['b', 'c', 'd']
    value = 'dog'

    tree = add_branch(tree, vector, value)

    Returns:
        tree = { 'a': 'apple', 'b': { 'c': {'d': 'dog'}}}

    Example 2:
    vector2 = ['b', 'c', 'e']
    value2 = 'egg'

    tree = add_branch(tree, vector2, value2)    

    Returns:
        tree = { 'a': 'apple', 'b': { 'c': {'d': 'dog', 'e': 'egg'}}}

    Returns:
        dict: The dict with the value placed at the path specified.

    Algorithm:
        If we're at the leaf, add it as key/value to the tree
        Else: If the subtree doesn't exist, create it.
              Recurse with the subtree and the left shifted vector.
        Return the tree.

    """
    key = vector[0]
    tree[key] = value \
        if len(vector) == 1 \
        else add_branch(tree[key] if key in tree else {},
                        vector[1:],
                        value)
    return tree

Upvotes: 5

Adam Smith
Adam Smith

Reputation: 54183

eval is fairly dangerous here, since this is untrusted input. You could use regex to grab the dict name and key names and look them up using vars and dict.get.

import re

a = {'b': {'c': True}}

in_ = 'a.b.c'
match = re.match(
    r"""(?P<dict>      # begin named group 'dict'
          [^.]+        #   one or more non-period characters
        )              # end named group 'dict'
        \.             # a literal dot
        (?P<keys>      # begin named group 'keys'
          .*           #   the rest of the string!
        )              # end named group 'keys'""",
    in_,
    flags=re.X)

d = vars()[match.group('dict')]
for key in match.group('keys'):
    d = d.get(key, None)
    if d is None:
        # handle the case where the dict doesn't have that (sub)key!
        print("Uh oh!")
        break
result = d

# result == True

Or even more simply: split on dots.

in_ = 'a.b.c'
input_split = in_.split('.')
d_name, keys = input_split[0], input_split[1:]

d = vars()[d_name]
for key in keys:
    d = d.get(key, None)
    if d is None:
        # same as above
result = d

Upvotes: 4

Mehdi
Mehdi

Reputation: 4318

s = "a.b.c"
s = s.replace(".", "][")+"]" # 'a][b][c]'
i = s.find("]") # find the first "]"
s = s[:i]+s[i+1:] # remove it 'a[b][c]'
s = s.replace("]", "\"]").replace("[", "[\"") # add quotations 'a["b"]["c"]'
# you can now execute it:
v = eval(s)

Upvotes: 2

Related Questions