Python convert path to dict

Question:

I have a list of paths that need to be converted to a dict

[
    "/company/accounts/account1/accountId=11111",
    "/company/accounts/account1/accountName=testacc",
    "/company/accounts/account1/environment=test",
    "/company/accounts/account2/accountId=22222",
    "/company/accounts/account2/accountName=stageacc",
    "/company/accounts/account2/environment=stage",
    "/program/releases/program1/stage/version=1.1",
    "/program/releases/program1/stage/date=2021-02-01",
    "/program/releases/program1/prod/version=1.0",
    "/program/releases/program1/prod/date=2021-01-15",
]

Here is what it should look like:

{
    "company": {
        "accounts": {
            "account1": {
                "accountId": 11111,
                "accountName": "testacc",
                "environment": "test"
            },
            "account2": {
                "accountId": 22222,
                "accountName": "stageacc",
                "environment": "stage"
            }
        }
    },
    "program": {
        "releases": {
            "program1": {
                "stage": {
                    "version": "1.1",
                    "date": "2021-02-01"
                },
                "prod": {
                    "version": "1.0",
                    "date": "2021-01-15"
                }
            }
        }
    }
}

I am trying to solve this iteratively but I can’t seem to get it to work.
Not sure what is the right approach here when it comes to nested dictionaries.

Here is my code:

class Deserialize:
    def __init__(self):
        self.obj = {}

    def deserialize_iteratively(self, paths):
        
        def helper(path):
            path_elements = path.split('/')
            for e in path_elements[::-1]:
                if "=" in e:
                    k,v = e.split("=")
                    self.obj[k] = v
                else:
                    tmp = {}
                    tmp[e] = self.obj
                    self.obj = tmp
            return self.obj
        
        for path in paths:
            helper(path)
        return self.obj

And the erroneous output this generates with first two paths:

{'': {'company': {'accounts': {'account1': {'': {'company': {'accounts': {'account1': {'accountId': '11111'}}}},
                                            'accountName': 'testacc'}}}}}
Asked By: Savvy

||

Answers:

You can use recursion with collections.defaultdict:

import collections as cl, re, json
def to_tree(data):
   d = cl.defaultdict(list)
   for a, *b in data:
      d[a].append(b)
   return {a:b[0][0] if len(b) == 1 else to_tree(b) for a, b in d.items()}

vals = ['/company/accounts/account1/accountId=11111', '/company/accounts/account1/accountName=testacc', '/company/accounts/account1/environment=test', '/company/accounts/account2/accountId=22222', '/company/accounts/account2/accountName=stageacc', '/company/accounts/account2/environment=stage', '/program/releases/program1/stage/version=1.1', '/program/releases/program1/stage/date=2021-02-01', '/program/releases/program1/prod/version=1.0', '/program/releases/program1/prod/date=2021-01-15']
result = to_tree([[*filter(None, re.split('=|/', i))] for i in vals])
print(json.dumps(result, indent=4))

Output:

{
    "company": {
        "accounts": {
            "account1": {
                "accountId": "11111",
                "accountName": "testacc",
                "environment": "test"
            },
            "account2": {
                "accountId": "22222",
                "accountName": "stageacc",
                "environment": "stage"
            }
        }
    },
    "program": {
        "releases": {
            "program1": {
                "stage": {
                    "version": "1.1",
                    "date": "2021-02-01"
                },
                "prod": {
                    "version": "1.0",
                    "date": "2021-01-15"
                }
            }
        }
    }
}
Answered By: Ajax1234

You’ll want to use .setdefault() to create the dicts as you dig through the path.

from pprint import pprint

s = [
    "/company/accounts/account1/accountId=11111",
    "/company/accounts/account1/accountName=testacc",
    "/company/accounts/account1/environment=test",
    "/company/accounts/account2/accountId=22222",
    "/company/accounts/account2/accountName=stageacc",
    "/company/accounts/account2/environment=stage",
    "/program/releases/program1/stage/version=1.1",
    "/program/releases/program1/stage/date=2021-02-01",
    "/program/releases/program1/prod/version=1.0",
    "/program/releases/program1/prod/date=2021-01-15",
]

root = {}

for path in s:
    # separate by slashes, disregarding the first `/`
    path = path.lstrip("/").split("/")
    # pop off the last key-value component
    key, _, val = path.pop(-1).partition("=")
    # find the target dict starting from the root
    target_dict = root
    for component in path:
        target_dict = target_dict.setdefault(component, {})
    # assign key-value
    target_dict[key] = val

pprint(root)

Outputs:

{'company': {'accounts': {'account1': {'accountId': '11111',
                                       'accountName': 'testacc',
                                       'environment': 'test'},
                          'account2': {'accountId': '22222',
                                       'accountName': 'stageacc',
                                       'environment': 'stage'}}},
 'program': {'releases': {'program1': {'prod': {'date': '2021-01-15',
                                                'version': '1.0'},
                                       'stage': {'date': '2021-02-01',
                                                 'version': '1.1'}}}}}
Answered By: AKX
from collections import defaultdict
import json

def nested_dict():
   """
   Creates a default dictionary where each value is an other default dictionary.
   """
   return defaultdict(nested_dict)

def default_to_regular(d):
    """
    Converts defaultdicts of defaultdicts to dict of dicts.
    """
    if isinstance(d, defaultdict):
        d = {k: default_to_regular(v) for k, v in d.items()}
    return d

def get_path_dict(paths):
    new_path_dict = nested_dict()
    for path in paths:
        parts = path.split('/')
        if parts:
            marcher = new_path_dict
            for key in parts[:-1]:
               marcher = marcher[key]
            marcher[parts[-1]] = parts[-1]
    return default_to_regular(new_path_dict)

l1 = ['foo/e.txt','foo/bar/a.txt','foo/bar/b.cfg','foo/bar/c/d.txt', 'test.txt']
result = get_path_dict(l1)
print(json.dumps(result, indent=2))
Answered By: ZahraRezaei
def paths_to_dict(paths: list):
    root = {}

    for path in paths:
        # separate by slashes, disregarding the first `/`
        path = path.lstrip("/").split("/")
        # pop off the last key-value component
        key, _, val = path.pop(-1).partition("=")
        # find the target dict starting from the root
        target_dict = root
        is_dict = True
        for component in path:
            if not isinstance(target_dict, dict):
                is_dict = False
                break
            target_dict = target_dict.setdefault(component, {})
        if is_dict:
            # assign key-value
            target_dict[key] = val

    return root
Answered By: Guido Modarelli
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.