source: packson3/packson/ObjectMapper.py@ 136

Last change on this file since 136 was 134, checked in by wouter, 4 years ago

added first python project. Needs some more cleanup

File size: 7.3 KB
Line 
1import json
2from packson.JsonTypeInfo import Id,As, getTypeWrappingInfo
3# from packson.JsonSubTypes import getSubTypes
4from typing import _GenericAlias, cast , Dict, Any
5from packson.JsonTools import isPrimitive, getActualClass, getInitArgs,addTypeInfo,getListClass
6from pip._vendor.pyparsing import dictOf
7
8class ObjectMapper:
9 '''
10 A very simple packson-style objectmapper.
11 '''
12 def parse(self, data:object, clas:object )->object:
13 '''
14 @param data either a dict or a built-in object like an int
15 @param clas the expected class contained in the data.
16 If a dict, this class must have a __init__ function specifying the params
17 needed and the data in this case should be a dict.
18 @return a clas instance matching the data
19 '''
20 if getTypeWrappingInfo(clas):
21 if not isinstance(data, dict):
22 raise ValueError("Expected class, therefore data must be a dict but got "+str(data))
23 (data, clas) = getActualClass(cast(dict, data),clas)
24 # now distinguish
25 if isPrimitive(clas):
26 return self.parseBase(data, clas)
27 if (clas==list):
28 raise ValueError("Illegal type, use List[X] instead of list")
29 if (clas==dict):
30 raise ValueError("Illegal type, use Dict[X] instead of dict")
31 if type(clas)==_GenericAlias:
32 return self.parseGeneric(data,clas)
33 raise ValueError("GenericAlias not yet supported")
34 if type(data)==dict: # if it contains class, data must be dict
35 return self.parseClass(cast(dict,data), clas)
36 raise ValueError("Unexpected type of data:"+str(clas))
37
38 def parseBase(self,obj, clas)->object:
39 '''
40 @param obj a built-in object like an int
41 @param clas the class of the expected object
42 class does not contain __init__, it must be a built-in type
43 @return the obj, after checking it's indeed a clas
44 '''
45 if not type(obj)==clas:
46 raise ValueError("expected "+str(clas)+", got "+str(obj))
47 return obj
48
49
50 def parseClass(self, data:dict, clas)->object:
51 '''
52 @param data a dict with the values for class.__init__ function
53 @return a clas instance matching the data
54 '''
55 if not isinstance(data,dict ):
56 raise ValueError("data "+str(data)+" must be dict")
57 # then this class needs initialization
58 initargs={}
59 argclasses=getInitArgs(clas)
60 for arg in argclasses:
61 if not arg in data:
62 raise ValueError(str(clas)+" constructor takes "+str(arg)+", but value missing in dict "+str(data))
63 try:
64 initargs[arg] = self.parse(data[arg], argclasses[arg])
65 except ValueError as e:
66 raise ValueError("Error parsing "+json.dumps(data),e)
67 return clas(**initargs)
68
69 def parseGeneric(self, data, clas:_GenericAlias)->object:
70 '''
71 @param data may be list or dict, depending on the exact clas
72 @return instance of the clas. Don't know how to write this for typing
73 '''
74 gname =clas._name
75
76 if gname=='List' or gname=='Set':
77 elementclas = clas.__args__[0]
78 if type(data)!=list:
79 raise ValueError("expected list[{elementclas}] but got "+str(data))
80 res=[self.parse(listitem, elementclas) for listitem in data]
81 if gname=='List':
82 return res
83 else:
84 return set(res)
85
86 if gname=='Dict':
87 keyclas = clas.__args__[0]
88 if not keyclas.__hash__:
89 raise ValueError("Dict cannot be serialized, key class "+str(keyclas)+" does not have __hash__")
90 elementclas = clas.__args__[1]
91 if type(data)!=dict:
92 raise ValueError("expected dict[{keyclass, elementclas}] but got "+str(data))
93 return { self.parse(key, keyclas) : self.parse(val, elementclas)\
94 for key,val in data.items() }
95
96 raise ValueError("Unsupported generic type "+gname)
97
98 def toJson(self, data)->dict:
99 '''
100 @param data either a dict or a built-in object like an int
101 @return a dict containing this object
102 '''
103 res:dict
104 clas = type(data)
105 if isPrimitive(clas):
106 return self.toJsonBase(data)
107 if clas==list or clas==tuple or clas==set:
108 res=self.toJsonList(data)
109 elif clas==dict:
110 res=self.toJsonDict(data)
111 elif type(clas)==type:
112 # is it general class? FIXME can this be done better?
113 res = self.toJsonClass(data)
114 else:
115 raise ValueError("Unsupported object of type "+str(clas))
116 # check if wrapper setting is requested for this class
117 if getTypeWrappingInfo(clas):
118 res=addTypeInfo(clas,res);
119 return res
120
121 def toJsonClass(self,data:object)->dict:
122 '''
123 @param data a class instance
124 @return data a dict with the values
125 The values are based on the class.__init__ function
126 '''
127 clas=type(data)
128 res={}
129 for arg in getInitArgs(clas):
130 gettername = 'get'+arg
131 if not hasattr(data, gettername) :# in clas.__dict__:
132 raise ValueError("The object "+str(data)+ "of type "+ str(clas)+" has no function "+gettername)
133 argvalue = getattr(data, gettername)() #.__dict__[gettername]()
134 res[arg]=self.toJson(argvalue)
135 return res
136
137
138 def toJsonBase(self,obj):
139 '''
140 @param obj a built-in object like an int
141 obj does not contain __init__, it must be a built-in type
142 '''
143 return obj
144
145 def toJsonList(self, listofobj):
146 '''
147 @param listofobj list or tuple of objects each to be serialized separately.
148 @return list object to be put in the json representation,
149 '''
150 if len(listofobj)==0:
151 return [] # empty list has no type.
152 clas = getListClass(listofobj)
153 # if isPrimitive(clas):
154 # return listofobj
155 if not (isPrimitive(clas) or getTypeWrappingInfo(clas)):
156 raise ValueError("@JsonTypeInfo is required for list objects, but found "+str(clas))
157 return [self.toJson(elt) for elt in listofobj]
158
159 def toJsonDict(self, dictofobj:Dict[Any,Any]):
160 '''
161 @param dictofobj dict with objects each to be serialized separately.
162 The keys must be primitive, values must be all the same class.
163 @return list object to be put in the json representation,
164 '''
165 if len(dictofobj)==0:
166 return {} # empty list has no type.
167 keyclas = getListClass(list(dictofobj.keys()))
168 valclas = getListClass(list(dictofobj.values()))
169 # if isPrimitive(clas):
170 # return listofobj
171 if not isPrimitive(keyclas):
172 raise ValueError("key of dict must be primitive, but keys are of type "+\
173 str(keyclas)+" in "+str(dictofobj))
174 if not getTypeWrappingInfo(valclas):
175 raise ValueError("@JsonTypeInfo is required for list objects, but found "+str(valclas))
176 return { self.toJson(key):self.toJson(val) for key,val in dictofobj.items()}
177
178
Note: See TracBrowser for help on using the repository browser.