'Recursively creates dataclasses based in nested dictionary

I have a dataclass called Config that is created through the properties and values of a dictionary. Since this dictionary can have nested dictionaries, i would like to make nested dictionaries as Config objects. Here is an example:

## Dummy example of a config dict
data = {
  'a' : 1,
  'b' : [2,2,2],
  'c': {
    'c_1' : 3.1
  }
}

final_config = create_config(data)

# Expected result
Config(a=1, b=[2,2,2], c=Config(c_1=3.1) )

Here is what i've came up, using dataclasses.make_dataclass:

def _Config(params_dict):
  config = make_dataclass('Config', params_dict.keys())
  return config(**params_dict)
  
def get_inner_dict(d):
    for _, v in d.items():
        if isinstance(v, dict):
            return get_inner_dict(v) 
        else:
            return _Config(**d)

Unfortunately, this doesn't work because the recursion will try to create a dataclass object when it finds a single value. I feel like i'm in the right way, but couldn't figure out what needs to change.



Solution 1:[1]

It looks like you (technically) don't need to use dataclasses or make_dataclass in this scenario.

You can implement a custom class with a __dict__ update approach as mentioned by @Stef. Check out the following example:

from __future__ import annotations


## Dummy example of a config dict
data = {
    'a': 1,
    'b': [2, 2, 2],
    'c': {
        'c_1': 3.1
    },
    'd': [
        1,
        '2',
        {'k1': 'v1'}
    ]
}

_CONTAINER_TYPES = (dict, list)


class Config:

    def __init__(self, **kwargs):
        self.__dict__ = kwargs

    @classmethod
    def create(cls, data: dict | list) -> Config | list:
        if isinstance(data, list):
            return [cls.create(e) if isinstance(e, _CONTAINER_TYPES) else e
                    for e in data]

        new_data = {
            k: cls.create(v) if isinstance(v, _CONTAINER_TYPES) else v
            for k, v in data.items()
        }

        return cls(**new_data)

    def __repr__(self):
        return f"Config({', '.join([f'{name}={val!r}' for name, val in self.__dict__.items()])})"


final_config = Config.create(data)
print(final_config)
# Prints:
#   Config(a=1, b=[2, 2, 2], c=Config(c_1=3.1), d=[1, '2', Config(k1='v1')])

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1