skip to Main Content

If I have the following Json:

{
"a": {
       "b" : {
               "c" : "value"
             }
      }
}

This is loaded into my object (obj) via json.load()

Then I have another variable which is

path = "a.b.c"

To access the value of ‘c’ in the json, I would typically do:

obj["a"]["b"]["c"]

However I would like to leverage the ‘path’ variable, and do something like this:

obj[path]

Is this possible? How can this be achieved

5

Answers


  1. You can split the path on dot and use reduce to get the nested value.

    from functools import reduce
    d = {'a':{'b':{'c':'value'}}}
    path = "a.b.c"
    val = reduce(lambda o, k: o[k], path.split("."), d)
    print(val)
    

    Demo

    Login or Signup to reply.
  2. It’s possible to do so using a combination of operator.getitem and functools.reduce:

    >>> from functools import reduce
    >>> from operator import getitem
    >>> dct = {'a': {'b': {'c': 'value'}}}
    >>> reduce(getitem, "a.b.c".split("."), dct)
    'value'
    

    The way this works is that reduce will initially invoke getitem using dct and the first item from the "a.b.c".split() i.e "a" and then the result of it is then passed to getitem in next iteration but this time with "b" and so on…

    >>> getitem(dct, "a")
    {'b': {'c': 'value'}}
    >>> getitem(getitem(dct, "a"), "b")
    {'c': 'value'}
    >>> getitem(getitem(getitem(dct, "a"), "b"), "c")
    'value'
    

    And getitem in itself works like:

    >>> getitem?
    Signature: getitem(a, b, /)
    Docstring: Same as a[b].
    Type:      builtin_function_or_method
    
    Login or Signup to reply.
  3. You could write a function that takes both the JSON data and the path as arguments. It can iterate through each key in the path (separated by .) and traverse through the JSON that way.

    def json_from_path(data: dict, path: str):
        for key in path.split('.'):
            data = data[key]
        return data
    
    Login or Signup to reply.
  4. It’s a third-party installation, but the jq package lets you traverse data structures using the jq filter language.

    >>> import jq
    >>> d = {"a": {"b": {"c": "value"}}}
    >>> jq.compile(".a.b.c").input(d).first()
    'value'
    

    (Note that . has to precede each key, rather than simply separating the keys, in the path.)

    Login or Signup to reply.
  5. This might be a little beyond the scope of the question, but you can always subclass the dict type and overwrite the __getitem__ method to get the result you are looking for.

    This is the way that you could actually achieve the result the OP is asking for with the syntax used by the OP.

    For example:

    class Dict(dict):
        def __getitem__(self, val):
            try: return super().__getitem__(val)
            except KeyError:
                keys = val.split(".")
                value = super().__getitem__(keys[0])
                d = Dict(value)
                return d['.'.join(keys[1:])]
    
    

    Then it’s as simple as:

    path = 'a.b.c'
    obj = Dict({"a": {"b" : {"c" : "value"}}})
    
    result = obj[path]
    
    print(result)
    

    OUTPUT

    value
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search