== Pyson Pyson converts between dicts/lists and python3 objects. It uses some annotations inspired by jackson. install: {{{ pip install https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/export/153/pyson/dist/pyson-1.0.0.tar.gz }}} or from your setup.py {{{ install_requires=[ "pyson@pip install https://tracinsy.ewi.tudelft.nl/pubtrac/Utilities/export/142/pyson/dist/pyson-1.0.0.tar.gz"], }}} NOTICE you may want to use the latest version. Check [source:/packson3/dist] for the latest. WARNING: there exists another other python library named "pyson", which is an entirely different project. The basic version determines the types for (de)serialization from the {{{__init__}}} function in the involved classes. Polymorphism is supported, so derived classes can (de)serialized from a superclass. == Basic Mechanism The basic mechanism for pyson is to look at the {{{__init__}}} function of the class under serialization. The arguments in the {{{__init__}}} function are matched to the json fields. The arguments in the {{{__init__}}} must be fully typed, and these types are used to determine how to interpret the json content. === Deserialization For Deserialization of a json object, it goes like this * if the target class has @JsonSubtypes, * check which the subclasses are and what their ID is. * Determine the actual class contained in the json * "unwrap" the json, so that we have the remaining json to deserialize the actual class * If it's not a JsonSubtypes, then the actual class is the requested target class * now that the actual class is known, * the parameter names and param-classes from the {{{__init__}}} function of the actual class are taken * for each of the parameters, recursively deserialize the json value for that parameter, using the param-class as targetclass. * call the constructor of the target class, using the parsed json for each parameter === Serialization Serialization of an object is much more straightforward. * Create a json dict, with keys the arguments of the __init__ function of the object and the value the serialized value returned by the getter (also considering @JsonGetter) * If the object is an instance of a class with @JsonSubtypes, add/wrap the json with class info according to the @JsonTypeInfo == Annotations The following annotations are available === {{{@JsonGetter}}} Added to a function definition in a class. Takes "value" as argument, containing the name of a json field (which is also used as argument in the constructor). Indicates that that function is to be used to get the value of the given json field. === {{{@JsonSubTypes}}} Added to a class definition. Takes a list of strings as argument. Each string is the full class path to another class. Indicates that that other class is a sub-class of the annotated class. The other class can be used for deserialization as well. If this is used, the @JsonTypeInfo must also be provided. === {{{@JsonTypeInfo}}} Added to a class definition. Contains "Id" and "As" values. Indicates that the class name/id should be included in the json code. This is especially useful in combination with {{{@JsonSubTypes.}}} The "Id" value can have the value NONE, NAME, CLASS. We recommend to use NAME, ||NONE|| Do not include class id at all. Do not use this with @JsonTypeInfo. Only included because it was availale in Jackson|| ||NAME||Use the name of the class to refer to the class. All classes referred must have different name (not two the same names with different classpath).|| ||CLASS||Use the full.class.path to refer to the class. || We recommend NAME, because it is shorter and gives more readable json, and gives you flexibility to move around the actual classes if needed without breaking compabibility with existing json files|| Indicates how to include the class name is included in/extracted from the json. ==== === Inheritance of annotations The usual inheritance mechanism of python applies also to the annotatinos. == Examples See [source:pyson/test/ObjectMapperTest.py] for many examples. A simple example, deserializng a dict with objects {{{ from pyson.ObjectMapper import ObjectMapper from pyson.JsonTypeInfo import JsonTypeInfo from pyson.JsonTypeInfo import Id,As from typing import Dict @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) class Simple: def __init__(self, a:int): self._a=a def geta(self)->int: return self._a def __eq__(self, other): return isinstance(other, self.__class__) and \ self._a==other._a def __str__(self): return self._name+","+str(self._a) pyson=ObjectMapper() objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}} obj=pyson.parse(objson, Dict[str,Simple]) obj['a'].geta() }}} A complex example showing many things at once {{{ from pyson.ObjectMapper import ObjectMapper from pyson.JsonSubTypes import JsonSubTypes from pyson.JsonTypeInfo import JsonTypeInfo from pyson.JsonTypeInfo import Id,As from typing import Dict,List,Set import json class Props: ''' compound class with properties, used for testing ''' def __init__(self, age:int, name:str): if age<0: raise ValueError("age must be >0, got "+str(age)) self._age=age self._name=name; def __str__(self): return self._name+","+str(self._age) def getage(self): return self._age def getname(self): return self._name def __eq__(self, other): return isinstance(other, self.__class__) and \ self._name==other._name and self._age==other._age @JsonSubTypes(["__main__.Bear"]) @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) class Animal: pass class Bear(Animal): def __init__(self, props:Props): self._props=props def __str__(self): return "Bear["+str(self._props)+"]" def getprops(self): return self._props def __eq__(self, other): return isinstance(other, self.__class__) and \ self._props==other._props pyson=ObjectMapper() obj=Bear(Props(1,'bruno')) res=pyson.toJson(obj) print("result:"+str(res)) bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}} res=pyson.parse(bson, Animal) print("Deserialized an Animal! -->"+str(res)) }}} NOTICE: our code allows you to use objects as keys, as python does allow this. However json requires strings as keys. And an example using {{{@JsonGetter}}} {{{ from pyson.ObjectMapper import ObjectMapper from pyson.JsonGetter import JsonGetter class Getter: def __init__(self, a:int): self._a=a @JsonGetter("a") def getValue(self): return self._a getter=Getter(17) pyson=ObjectMapper() pyson.toJson(getter) }}} == Add/Extend annotations at runtime You can also add/extend existing annotations at runtime. You just need to update the class and function attributes. For now, check the source code for details.