Convert dict/JSON to paths with python

Viewed 56

I have a dict/JSON

d = {
    "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"
                }
            }
        }
    }
}

which needs to be converted as "path-strings" as below:

[
    "/company/accounts/account1/accountId=11111",
    "/company/accounts/account1/accountName=testacc",
    "/compan1/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
]

So the form is basically like this: [ “/key/nestedkey=value“]

I am using recursion to check the nested value type and build the string, but I fail.

Here is what I have tried:

from pprint import pprint
path = []
main_list = []

def serializer(json):
    for k,v in json.items():
        if type(v) is dict:
            path.append(k)
            serializer(v)
        else: 
            build_key(k, v) 

def build_key(k, v):
    return main_list.append("/".join(path) + f"/{k}={v}")

serializer(d)
pprint(main_list)

My erroneous output:

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

Check out the code here: https://replit.com/join/jvjngast-deeprony7

How do I solve this?

2 Answers

You can use a recursive generator function:

def get_paths(d, c = []):
  for a, b in d.items():
    if not isinstance(b, dict):
       yield f'{"/".join(c+[a])}={b}'
    else:
       yield from get_paths(b, c+[a])

d = {'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'}}}}}
print(list(get_paths(d)))

Output:

['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'
]

Edit: iterative approach with a breadth-first search:

from collections import deque
d = {'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'}}}}}
def get_paths(tree):
   q = deque([([a], b) for a, b in tree.items()])
   while q:
      a, b = q.popleft()
      for j, k in b.items():
         if not isinstance(k, dict):
            yield ('/'.join(a+[j])+f'={k}')
         else:
            q.append((a+[j], k))

print(list(get_paths(d)))

Output:

['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'
]

Using python list comprehension

def get_paths(nested_dict:dict, path_list = [])->list:
    return list([(yield f'{"/".join(path_list+[key])}={value}')  
                 if not isinstance(value, dict) else (yield from get_paths(value, path_list+[key])) 
                 for key, value in nested_dict.items()
                ])
get_paths(nested_dict = d)

Output:

['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']
Related