Home > Software engineering >  Have a class init as a list object or a standard object
Have a class init as a list object or a standard object

Time:01-15

I have to convert a data structure to an object. This data structure can consist of any number of nested lists and dictionaries. I have written code that is currently capable of doing this as long as the input data is a dictionary on the highest level. if the input data is a list then this will not work because there is no initial key to create an attribute from. The functionality I'm looking for would have this class return a list if the input data is a list.

My current code is as follows:

class DataConverter(object):
    def __init__(self, data):
        if isinstance(data, dict):
            for key in data:
                value = data[key]
                if isinstance(value, list):
                    new_list = []
                    for ind in value:
                        if isinstance(ind, dict):
                            new_list.append(DataConverter(ind))
                        else:
                            new_list.append(ind)
                    setattr(self, key, new_list)
                elif isinstance(value, dict):
                    setattr(self, key, DataConverter(value))
                else:
                    setattr(self, key, value)
        elif isinstance(data, list):
            new_list = []
            for val in data:
                new_list.append(DataConverter(val))
            # TODO: class needs to init as 'new_list' if it gets here

the input data will be in structures similar to this but 1000x larger and deeper:

input_data = {
    "foo": "bar",
    "x": "y",
    "Something": 12345,
    "other": [
        {
            "yes": 1,
            "no": 0
        }
    ],
}

If I were to convert this data, I would expect the resulting object to be used like so:

>>> converted_data = DataConverter(input_data)
>>> converted_data.other[0].yes
1

This issue occurs when I get data structured like so:

input_data = [
    {
        "foo": "bar",
    },
    {
        "x": "y",
    },
    {
        "Something": 12345,
    },
    {
        "other": [
            {
                "yes": 1,
                "no": 0
            }
        ]
    }
]

and I would expect this data to be used like so after conversion:

>>> converted_data = DataConverter(input_data)
>>> converted_data[3].other[0].no
0

I began writing something I imagined would work, but I can't figure out how to have the DataConverter object init as a list object if the input is a list.

CodePudding user response:

Would this work?

I take the subdicts from the list and merge them in a larger dict until the list is exhausted, then we treat the dict as we would if it was given to DataConverter directly, by calling the class __init__ which our newly created dict.

elif isinstance(data, list):
    new_data = {}
    for subdict in data:
        new_data.update(subdict)
    self = self.__class__.__init__(self, new_data)

This is messy but is works. If someone has a better solution (probably using super()) I'm all ears.

CodePudding user response:

I found a semi-valid approach but it results in a less than perfect solution.

class DataConverter(list):
    def __init__(self, input_data):
        if isinstance(input_data, dict):
            for key, data in input_data.items():
                if isinstance(data, list) or isinstance(value, dict):
                    setattr(self, key, DataConverter(value))
                else:
                    setattr(self, key, value)
        elif isinstance(input_data, list):
            for val in data:
                if isinstance(val, list) or isinstance(val, dict):
                    self.append(DataConverter(val))
                else:
                    self.append(val)

This results in an object that is a list of lists of lists of lis... etc. each list then has attributes added on if the data wasn't a list, but if the data is a list then we just continue onward as a list.

The only downside is that now each level of this object is a list, and will have list methods, But since I'm only using this object to read data, it shouldn't be a hindrance.

I would however like to know if there is a better way, this doesn't seem like the best way to me and I believe someone else with better knowledge of inheritance could figure this out.

CodePudding user response:

I wouldn't try to get too smart with the class __init__ itself, rather use a factory method:

    @classmethod
    def factory(cls, data):
        if isinstance(data, dict):
            return cls(data)
        elif isinstance(data, list):
            res = []
            for part in data:
                res.append(cls(part))
            return res

The whole code:

class DataConverter(object):
    def __init__(self, data):
        if isinstance(data, dict):
            for key in data:
                value = data[key]
                if isinstance(value, list):
                    new_list = []
                    for ind in value:
                        if isinstance(ind, dict):
                            new_list.append(DataConverter(ind))
                        else:
                            new_list.append(ind)
                    setattr(self, key, new_list)
                elif isinstance(value, dict):
                    setattr(self, key, DataConverter(value))
                else:
                    setattr(self, key, value)
        elif isinstance(data, list):
            new_list = []
            for val in data:
                new_list.append(DataConverter(val))
            # TODO: class needs to init as 'new_list' if it gets here

    @classmethod
    def factory(cls, data):
        if isinstance(data, dict):
            return cls(data)
        elif isinstance(data, list):
            res = []
            for part in data:
                res.append(cls(part))
            return res


input_data = {
"foo": "bar",
"x": "y",
"Something": 12345,
"other": [
    {
        "yes": 1,
        "no": 0
    }
],
}





input_data_list = [
        {
            "foo": "bar",
        },
        {
            "x": "y",
        },
        {
            "Something": 12345,
        },
        {
            "other": [
                {
                    "yes": 1,
                    "no": 0
                }
            ]
        }
        ]



converted_data = DataConverter.factory(input_data)
print (f"{converted_data.other[0].yes=}")

converted_data_list = DataConverter.factory(input_data_list)
print(f"{converted_data_list[3].other[0].no=}")

and output:

converted_data.other[0].yes=1
converted_data_list[3].other[0].no=0
  •  Tags:  
  • Related