I have the following string
{\"action\":\"print\",\"method\":\"onData\",\"data\":\"Madan Mohan\"}
I Want to deserialize to a object of cla
If you are embracing the type hints in Python 3.6, you can do it like this:
def from_json(data, cls):
annotations: dict = cls.__annotations__ if hasattr(cls, '__annotations__') else None
if issubclass(cls, List):
list_type = cls.__args__[0]
instance: list = list()
for value in data:
instance.append(from_json(value, list_type))
return instance
elif issubclass(cls, Dict):
key_type = cls.__args__[0]
val_type = cls.__args__[1]
instance: dict = dict()
for key, value in data.items():
instance.update(from_json(key, key_type), from_json(value, val_type))
return instance
else:
instance : cls = cls()
for name, value in data.items():
field_type = annotations.get(name)
if inspect.isclass(field_type) and isinstance(value, (dict, tuple, list, set, frozenset)):
setattr(instance, name, from_json(value, field_type))
else:
setattr(instance, name, value)
return instance
Which then allows you do instantiate typed objects like this:
class Bar:
value : int
class Foo:
x : int
bar : List[Bar]
obj : Foo = from_json(json.loads('{"x": 123, "bar":[{"value": 3}, {"value": 2}, {"value": 1}]}'), Foo)
print(obj.x)
print(obj.bar[2].value)
This syntax requires Python 3.6 though and does not cover all cases - for example, support for typing.Any... But at least it does not pollute the classes that need to be deserialized with extra init/tojson methods.
Another way is to simply pass the json string as a dict to the constructor of your object. For example your object is:
class Payload(object):
def __init__(self, action, method, data, *args, **kwargs):
self.action = action
self.method = method
self.data = data
And the following two lines of python code will construct it:
j = json.loads(yourJsonString)
payload = Payload(**j)
Basically, we first create a generic json object from the json string. Then, we pass the generic json object as a dict to the constructor of the Payload class. The constructor of Payload class interprets the dict as keyword arguments and sets all the appropriate fields.
pydantic is an increasingly popular library for python 3.6+ projects. It mainly does data validation and settings management using type hints.
A basic example using different types:
from pydantic import BaseModel
class ClassicBar(BaseModel):
count_drinks: int
is_open: bool
data = {'count_drinks': '226', 'is_open': 'False'}
cb = ClassicBar(**data)
>>> cb
ClassicBar(count_drinks=226, is_open=False)
What I love about the lib is that you get a lot of goodies for free, like
>>> cb.json()
'{"count_drinks": 226, "is_open": false}'
>>> cb.dict()
{'count_drinks': 226, 'is_open': False}
If you want to save lines of code and leave the most flexible solution, we can deserialize the json string to a dynamic object:
p = lambda:None
p.__dict__ = json.loads('{"action": "print", "method": "onData", "data": "Madan Mohan"}')
>>>> p.action
output: u'print'
>>>> p.method
output: u'onData'
I thought I lose all my hairs for solving this 'challenge'. I faced following problems:
I found a library called jsonpickle
which is has proven to be really useful.
Installation:
pip install jsonpickle
Here is a code example with writing nested objects to file:
import jsonpickle
class SubObject:
def __init__(self, sub_name, sub_age):
self.sub_name = sub_name
self.sub_age = sub_age
class TestClass:
def __init__(self, name, age, sub_object):
self.name = name
self.age = age
self.sub_object = sub_object
john_junior = SubObject("John jr.", 2)
john = TestClass("John", 21, john_junior)
file_name = 'JohnWithSon' + '.json'
john_string = jsonpickle.encode(john)
with open(file_name, 'w') as fp:
fp.write(john_string)
john_from_file = open(file_name).read()
test_class_2 = jsonpickle.decode(john_from_file)
print(test_class_2.name)
print(test_class_2.age)
print(test_class_2.sub_object.sub_name)
Output:
John
21
John jr.
Website: http://jsonpickle.github.io/
Hope it will save your time (and hairs).
To elaborate on Sami's answer:
From the docs:
class Payload(object):
def __init__(self, action, method, data):
self.action = action
self.method = method
self.data = data
import json
def as_payload(dct):
return Payload(dct['action'], dct['method'], dct['data'])
payload = json.loads(message, object_hook = as_payload)
My objection to the
.__dict__
solution is that while it does the job and is concise, the Payload class becomes totally generic - it doesn't document its fields.
For example, if the Payload message had an unexpected format, instead of throwing a key not found error when the Payload was created, no error would be generated until the payload was used.