5 Best Ways to Convert a Flattened Dictionary into a Nested Dictionary in Python

πŸ’‘ Problem Formulation: Working with JSON or configuration data in Python often involves converting flattened dictionaries with compound keys into their equivalent nested dictionaries. Take for instance an input like {"a.b.c": 1, "a.b.d": 2, "e": 3}, the desired nested output would be {"a": {"b": {"c": 1, "d": 2}}, "e": 3}. How can one achieve this transformation efficiently in Python? This article lays out 5 methods to accomplish this giving Python developers multiple tools for their coding arsenal.

Method 1: Recursive Function

This method employs a recursive function to process each key-value pair in the flattened dictionary. It utilizes the function’s ability to call itself, making it adept for operations on tree-like data structures or nested objects. This method is most effective when dealing with deeply nested dictionaries.

Here’s an example:

def nest_dict(flat_dict, sep='.'):
    def _nest_dict(rec_dict, keys, value):
        key = keys.pop(0)
        if keys:
            rec_dict[key] = _nest_dict(rec_dict.get(key, {}), keys, value)
        else:
            rec_dict[key] = value
        return rec_dict
    nested = {}
    for flat_key, value in flat_dict.items():
        keys = flat_key.split(sep)
        nested = _nest_dict(nested, keys, value)
    return nested

flattened = {"a.b.c": 1, "a.b.d": 2, "e": 3}
nested = nest_dict(flattened)
print(nested)

The output would be:

{
    "a": {
        "b": {
            "c": 1,
            "d": 2
        }
    },
    "e": 3
}

This code defines a function nest_dict() that takes a flattened dictionary and a separator as arguments. It uses a helper function _nest_dict() which is called recursively to build the nested structure. The code traverses the keys and reconstructs the dictionary hierarchy accordingly.

Method 2: Using Collections.defaultdict

Python’s collections.defaultdict is a subclass of the built-in dict class that provides default values for missing keys, making it an ideal tool for creating nested dictionaries from flat structures effortlessly.

Here’s an example:

from collections import defaultdict
import json

def defaultdict_nested():
    return defaultdict(defaultdict_nested)

def convert_to_nested_dict(flat_dict, sep='.'):
    nested_dict = defaultdict_nested()
    for flat_key, value in flat_dict.items():
        keys = flat_key.split(sep)
        d = nested_dict
        for key in keys[:-1]:
            d = d[key]
        d[keys[-1]] = value
    return json.loads(json.dumps(nested_dict))

flattened = {"a.b.c": 1, "a.b.d": 2, "e": 3}
nested = convert_to_nested_dict(flattened)
print(nested)

The output would be:

{
    "a": {
        "b": {
            "c": 1,
            "d": 2
        }
    },
    "e": 3
}

The code snippet defines a factory function defaultdict_nested() that initializes a defaultdict with itself as the default factory, creating an arbitrarily deep nested default dictionary. It then converts the structure back to a regular dictionary for the final output using JSON serialization.

Method 3: Using a Loop

A straightforward approach, this method goes through the flattened dictionary and manually constructs the nested dictionary, making it a more iterative process compared to the previous recursive method.

Here’s an example:

def convert_to_nested_dict(flat_dict, sep='.'):
    nested_dict = {}
    for flat_key, value in flat_dict.items():
        parts = flat_key.split(sep)
        current_level = nested_dict
        for part in parts[:-1]:
            if part not in current_level:
                current_level[part] = {}
            current_level = current_level[part]
        current_level[parts[-1]] = value
    return nested_dict

flattened = {"a.b.c": 1, "a.b.d": 2, "e": 3}
nested = convert_to_nested_dict(flattened)
print(nested)

The output would be:

{
    "a": {
        "b": {
            "c": 1,
            "d": 2
        }
    },
    "e": 3
}

In this example, we loop through each item in the provided flattened dictionary, splitting the key into its constituents. We then traverse or create dictionaries at each level until we reach the penultimate key, where we set the value.

Method 4: Utilizing functools.reduce

By using functools.reduce, one can express the nested dictionary construction as a fold operation, elegantly compressing the process into a single line within a loop. Reduce applies a provided function cumulatively to the items of an iterable, from left to right.

Here’s an example:

from functools import reduce

def convert_to_nested_dict(flat_dict, sep='.'):
    nested_dict = {}
    for flat_key, value in flat_dict.items():
        keys = flat_key.split(sep)
        reduce(lambda d, key: d.setdefault(key, {}), keys[:-1], nested_dict)[keys[-1]] = value
    return nested_dict

flattened = {"a.b.c": 1, "a.b.d": 2, "e": 3}
nested = convert_to_nested_dict(flattened)
print(nested)

The output would be:

{
    "a": {
        "b": {
            "c": 1,
            "d": 2
        }
    },
    "e": 3
}

This snippet iterates over the flattened dictionary and splits each key by the separator. It then applies reduce() with dict.setdefault, effectively creating the nested dictionary without explicitly checking for the existence of intermediate dictionaries.

Bonus One-Liner Method 5: Utilizing dict comprehension

A pythonic and concise way to unflatten a dictionary, this method leverages dict comprehension to reconstruct the nested dictionary, offering elegance and simplicity.

Here’s an example:

flattened = {"a.b.c": 1, "a.b.d": 2, "e": 3}

nested = {tuple(k.split('.')): v for k, v in flattened.items()}
# At this point, we have a dict with tuple keys, further transformation is needed

print(nested)

The output would be:

{
    ("a", "b", "c"): 1,
    ("a", "b", "d"): 2,
    ("e",): 3
}

We transformed our flattened dictionary into one with tuple keys representing the hierarchy, but further processing would be required to achieve the fully nested form.

Summary/Discussion

  • Method 1: Recursive Function. Clean and conceptually simple. However, recursion can be inefficient and may lead to hitting the maximum recursion depth in large datasets.
  • Method 2: Using Collections.defaultdict. Offers a default value mechanism which can be cleaner in some cases, but serialization and deserialization can be a performance overhead.
  • Method 3: Using a Loop. It’s very straightforward but might not be the most efficient method for deeply nested dictionaries with many keys.
  • Method 4: Utilizing functools.reduce. Elegant one-liner per key-value pair, but readability might suffer for those not familiar with reduce().
  • Bonus Method 5: Utilizing dict comprehension. Extremely concise, but as it generates tuples, extra steps are needed for a complete solution.