Search code examples
python-3.xlistdictionarynested-json

Convert list with hierarchy into nested dictionary with similar hierarchy using Python


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

Solution

  • 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)