Home > Software design >  Convert list with hierarchy into nested dictionary with similar hierarchy using Python
Convert list with hierarchy into nested dictionary with similar hierarchy using Python

Time:09-17

Giving the following list:

mapping_list = ['location/name', 'location/address/address1', 'location/address/zip', 'location/business/business_name', 'occupant/occupant_type']

How to turn it into a nested dictionary as following where the last value is the last key with an empty string as a default value.

{
    "location":
    {
        "name": "",
        "address":
        {
            "address1": "",
            "zip": ""
        },
        "business":
        {
            "business_name": ""
        }
    },
    "occupant":
    {
        "occupant_type": ""
    }
}

Note : the given list could be written as such:

mapping_list_of_lists = []

for full_path in mapping_list:
  path_list = full_path.split('/')
  mapping_list_of_lists.append(path_list)

print(mapping_list_of_lists)


[['location', 'name'], ['location', 'address', 'address1'], ['location', 'address', 'zip'], ['location', 'business', 'business_name'], ['occupant', 'occupant_type']]

CodePudding user response:

I'm sure there are better ways but you could write a recursive function that populates a given dict with a mapping such as you have.

mapping_list = [
    'location/name', 
    'location/address/address1', 
    'location/address/zip', 
    'location/business/business_name', 
    'occupant/occupant_type'
]

def populate(mapping, dct, default=''):
    # check if '/' is in your current mapping
    if '/' in mapping:
        pos = mapping.find('/')
        part = mapping[:pos]
        # if it is and the next dict level does not exist yet
        # create the empty dict and recursively call the 
        # function to populate the inner parts with the
        # remainder of the mapping
        if part not in dct:
            dct[part] = dict()
        populate(mapping[pos 1:], dct[part], default=default)
    # otherwise, you're on your last part
    # and can safely fill the default value
    else:
        dct[mapping] = default

dct = {}
for mapping in mapping_list:
    populate(mapping, dct)
print(dct)

Alternative method utilizing str.split('/'):

mapping_list = [
    'location/name', 
    'location/address/address1', 
    'location/address/zip', 
    'location/business/business_name', 
    'occupant/occupant_type'
]

def populate(mapping, dct, default=''):
    if len(mapping) > 1:
        if mapping[0] not in dct:
            dct[mapping[0]] = dict()
        populate(mapping[1:], dct[mapping[0]], default=default)
    else:
        dct[mapping[0]] = default

mapping_list_of_lists = [i.split('/') for i in mapping_list]
dct = {}
for part in mapping_list_of_lists:
    populate(part, dct)
print(dct)

CodePudding user response:

I think that setdefault() goes a long way to solving this though you might also use a nested collections.defaultdict(). I think the only wrinkle is that in the final leaf you want an empty string rather than an empty dict, otherwise it would be dead simple.

mapping_list = [
    'location/name', 
    'location/address/address1', 
    'location/address/zip', 
    'location/business/business_name', 
    'occupant/occupant_type'
]

result = {}

for keys in [keys.split("/") for keys in mapping_list]:
    tmp = result
    for index, key in enumerate(keys):
        default = "" if index == (len(keys) -1) else {}
        tmp = tmp.setdefault(key, default)

print(result)

This will give you:

{'location': {'name': '', 'address': {'address1': '', 'zip': ''}, 'business': {'business_name': ''}}, 'occupant': {'occupant_type': ''}}
  • Related