dynsne
dynsne

Reputation: 341

How to generically serialize and de-serialize objects from dictionaries

While writing a little service I found that I'd like to build Python objects from dictionaries and then "serialize" them back into dictionaries.

Currently my problem is the result of doing so using a mix of itemgetter and vars. Not giving me the expected result. More concretely:

from operator import itemgetter

class Object(object):
    def __init__(self, foo: str, bar: int):
        self.foo, self.bar = foo, bar

    @staticmethod
    def from_dict(dictionary):
        attributes = ["foo", "bar"]
        return Object(*itemgetter(*attributes)(dictionary))

So we have this static method called from_dict that should take a dictionary and return a Object object.

This is the behavior I would like:

>> d = {"foo": "frobulate", "bar": 42}
>> obj = Object.from_dict(d)
>> vars(obj)
{"foo": "frobulate", "bar": 42}

However, what I get instead is:

>> d = {"foo": "frobulate", "bar": 42}
>> obj = Object.from_dict(d)
>> vars(obj)
{"foo": ("frobulate",), "bar": (42,)}

Where the attributes are set as single element tuples.

Is there a clean python way of getting this give me the behavior I expect?

I'm trying to avoid duplication like:

class Object(object):
    ...
    @staticmethod
    def from_dict(dictionary):
        return Object(foo=dictionary["foo"],bar=dictionary["bar"])

Eventually I'd like to have a generic method that takes a class cls and can parse any object from a dictionary:

def parse_object_from_dict(cls, attributes, dictionary):
    return cls(*itemgetter(*attributes)(dictionary))

That could be used like:

>> foo = parse_object_from_dict(Foo, ["a", "b"], {"a": 42, "b": 42})
>> obj = parse_object_from_dict(Object, ["foo", "bar"], {"foo": 42, "bar": 42}

And have the attributes set as expected (not as single element tuples!).

Is this possible? Or even a good idea?

Upvotes: 0

Views: 1311

Answers (2)

James
James

Reputation: 36623

For what you are describing, you should be using a class method. You are requiring that the input dictionary have the same keys as the arguments in the __init__ function, but beyond that you are just pushing the key-value pairs to the object.

class Object:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    @classmethod
    def from_dict(cls, d):
        ob = cls(**d)
        for k,v in d.items():
            setattr(ob, str(k), v)    
        return ob

Upvotes: 2

Kevin Welch
Kevin Welch

Reputation: 1508

One way to do this is with setattr

class Object(object):
    def __init__(self, foo=None, bar=None):
        self.foo = foo
        self.bar = bar

    @staticmethod
    def from_dict(dic):
        obj = Object()
        [setattr(obj, attr, dic[attr]) for attr in ["foo", "bar"]]
        return obj

Another way is using ** but can be a sloppy way to do things and can throw errors if your dictionary contains extra keys.

class Object(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    @staticmethod
    def from_dict(dic):
        return Object(**dic)

data = {'foo': 'a', 'bar': 2}
o = Object.from_dict(data)

data = {'foo': 'a', 'bar': 2, 'baz': 3}
o = Object.from_dict(data) # this will throw an error due to the 'baz' key

One final way to do it, which is probably the cleanest way, is to try using Marshmallow, which will handle serialization and deserialization for you.

https://marshmallow.readthedocs.io/en/3.0/api_reference.html

Upvotes: 1

Related Questions