I have a set of python webservices that work with data objects they get via a JSON POSTs. In my old services I have a lot of boilerplate to (de)serialize and check JSONs for each object. With Python 3.5s new typing and PEP 484 I have the feeling that could be substantially reduced. Is it worth it? Does anyone have a good solution for it?
Additional info
My old boilerplate looks like this for every object:
class Data:
class Nested1:
def __init__(self, nested1_flat1):
self.nested1_flat1 = nested1_flat1
@classmethod
def from_jsond(cls, jsond):
# jsond : dict of strings, lists and dicts as usually receivied from reading JSON
kwargs = {}
for key, val in jsond.items():
# lots of code to deal with special objects , e.g.
if key=='date' : kwargs[key] = cleverly_build_datetime_from_js_format(val)
return cls.__init__(**kwargs)
def __init__(self, flat1, nested1):
self.flat1 = flat1
self.nested1 = nested1
@classmethod
def from_jsond(cls, jsond):
kwargs = {}
for key, val in jsond.items():
# lots of code to deal with nested and special objects, e.g.
if key=='nested1' : kwargs[key] = Nested1.from_jsond(val)
return cls.__init__(**kwargs)
I managed to reduce it down to the following
@from_jsond
class Data:
@from_jsond
class Nested1:
@auto_assign
@beartype
def __init__(self, nested1_flat1: str):
pass
@auto_assign
@beartype
def __init__(self, flat1: str, nested1: Nested1)
pass
Here I used snippets for @auto_assign and @beartype and my own from_jsond.
import inspect
from typing import Any
_JSON_READABLE = [str, list, dict, Any]
def _from_jsond(cls, json_dict):
'''
Entity specific conversion of string dictionary to entity.
The json_dict is a dict of string, lists and other dicts as typically encoded in a JSON.
'''
kwargs = {}
init_parameters = inspect.signature(cls.__init__).parameters
for key, val in json_dict.items():
if key in init_parameters.keys():
if init_parameters[key].annotation in _JSON_READABLE:
kwargs[key] = val
else:
if hasattr(init_parameters[key].annotation, 'from_jsond'):
kwargs[key] = init_parameters[key].annotation.from_jsond(val)
else:
raise TypeError('No method to unserialize type "' + init_parameters[key].annotation.__name__ + '"')
else:
raise AttributeError('Class "' + cls.__name__ + '" does not accept attribute "' + key + '"')
return cls(**kwargs)
def from_jsond(cls):
''' Wrapper to add _from_jsonlike to cls as classmethod '''
cls.from_jsonlike = classmethod(_from_jsond)
return cls
With inheritance one could most likely reduce it even further, but I don't know if it is all worth the hassle and stable enough. Opinions and experiences are welcome :)