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
