source: pyson/test/ObjectMapperTest.py@ 151

Last change on this file since 151 was 151, checked in by wouter, 3 years ago

#66 added JsonGetter annotation

File size: 13.1 KB
Line 
1
2import re
3import unittest
4import sys, traceback
5from pyson.ObjectMapper import ObjectMapper
6from pyson.JsonSubTypes import JsonSubTypes
7from pyson.JsonTypeInfo import JsonTypeInfo
8from pyson.JsonTypeInfo import Id,As
9from typing import Dict,List,Set
10from pyson.JsonGetter import JsonGetter
11import json
12from abc import ABC
13from datetime import datetime
14
15class Props:
16 '''
17 compound class with properties, used for testing
18 '''
19 def __init__(self, age:int, name:str):
20 if age<0:
21 raise ValueError("age must be >0, got "+str(age))
22 self._age=age
23 self._name=name;
24 def __str__(self):
25 return self._name+","+str(self._age)
26 def getage(self):
27 return self._age
28 def getname(self):
29 return self._name
30 def __eq__(self, other):
31 return isinstance(other, self.__class__) and \
32 self._name==other._name and self._age==other._age
33
34@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
35class Simple:
36 def __init__(self, a:int):
37 self._a=a
38 def geta(self)->int:
39 return self._a
40 def __eq__(self, other):
41 return isinstance(other, self.__class__) and \
42 self._a==other._a
43 def __str__(self):
44 return self._name+","+str(self._a)
45
46
47class SimpleWithHash(Simple):
48 def __hash__(self):
49 return hash(self.geta())
50
51# define abstract root class
52# These need to be reachable globally for reference
53@JsonSubTypes(["test.ObjectMapperTest.Bear"])
54@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
55class Animal:
56 pass
57
58
59class Bear(Animal):
60 def __init__(self, props:Props):
61 self._props=props
62
63 def __str__(self):
64 return "Bear["+str(self._props)+"]"
65
66 def getprops(self):
67 return self._props
68 def __eq__(self, other):
69 return isinstance(other, self.__class__) and \
70 self._props==other._props
71
72
73
74# A wrongly configured type, you must add jsonsubtypes here.
75@JsonSubTypes(["test.ObjectMapperTest.BadSubclass"])
76class BadSuperclassMissingTypeInfo:
77 pass
78
79class BadSubclass(BadSuperclassMissingTypeInfo):
80 def __init__(self, a:int):
81 self._a=a
82 def geta(self)->int:
83 return self._a
84 def __eq__(self, other):
85 return isinstance(other, self.__class__) and \
86 self._a==other._a
87 def __str__(self):
88 return self._name+","+str(self._a)
89
90#module instead of class.
91@JsonSubTypes(["test.ObjectMapperTest"])
92@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
93class BadSuperclassModuleInstead:
94 pass
95
96@JsonSubTypes(["test.ObjectMapperTest.AbstractBear"])
97@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
98class AbstractAnimal(ABC):
99 pass
100
101class AbstractBear(AbstractAnimal):
102 def __init__(self, props:Props):
103 self._props=props
104
105 def __str__(self):
106 return "Bear["+str(self._props)+"]"
107
108 def getprops(self):
109 return self._props
110 def __eq__(self, other):
111 return isinstance(other, self.__class__) and \
112 self._props==other._props
113
114
115# our parser supports non-primitive keys but python dict dont.
116# therefore the following fails even before we can start testing our code...
117# we need to create a hashable dict to get around ths
118class mydict(dict):
119 def __hash__(self, *args, **kwargs):
120 return 1
121
122
123class ObjectMapperTest(unittest.TestCase):
124 def testPrimitives(self):
125 pyson=ObjectMapper()
126
127 res=pyson.parse(3, int)
128 self.assertEquals(3, res)
129
130
131 # this throws correct,
132 self.assertRaises(ValueError, lambda:pyson.parse(3, str))
133
134 # this throws correct,
135 self.assertRaises(ValueError, lambda:pyson.parse("ja", int))
136
137 #DEMO with nested classes of different types.
138 res=pyson.parse('three', str)
139 print(res, type(res))
140
141 pyson.parse(3.0, float)
142 pyson.parse(3.1, float)
143 pyson.parse(3j, complex)
144 pyson.parse(range(6), range)
145 pyson.parse(True, bool)
146 pyson.parse(False, bool)
147 pyson.parse(b"Hello", bytes)
148 pyson.parse(bytearray(b'\x00\x00\x00\x01'), bytearray)
149
150
151 def testProps(self):
152 pyson=ObjectMapper()
153 propsjson={'age': 10, 'name': 'pietje'}
154 props=Props(10, "pietje")
155 self.assertEquals(propsjson,pyson.toJson(props))
156 self.assertEquals(props, pyson.parse(propsjson, Props))
157
158 def testParseDeepError(self):
159 pyson=ObjectMapper()
160 propsjson={'age': 10, 'name': 12}
161 try:
162 pyson.parse(propsjson, Props)
163 raise AssertionError("parser did not throw")
164 except ValueError as e:
165 # we catch this to assure the exception contains
166 # both top error and details.
167 print("received error "+str(e))
168 self.assertTrue(str(e).find("Error parsing"))
169 self.assertTrue(str(e).find("ValueError"))
170 self.assertTrue(str(e).find("expected"))
171
172
173 def testEmpty(self):
174 pyson=ObjectMapper()
175
176 class EmptyClass:
177 def __init__(self):
178 pass
179 def __eq__(self, other):
180 return isinstance(other, self.__class__)
181
182 obj=EmptyClass()
183 print(pyson.toJson(obj))
184 res=pyson.parse({}, EmptyClass)
185 self.assertEqual(obj, res)
186
187 def testSubType(self):
188 pyson=ObjectMapper()
189
190 class Cat():
191 def __init__(self, props:Props):
192 self._props=props
193
194 def __str__(self):
195 return "Cat["+str(self._props)+"]"
196
197 def getprops(self):
198 return self._props
199
200 obj=Cat(Props(1,'bruno'))
201 print(pyson.toJson(obj))
202
203
204 bson={'props':{'age':1, 'name':'bruno'}}
205 res=pyson.parse(bson, Cat)
206 print(res, type(res))
207 self.assertEquals(type(res.getprops()), Props)
208
209
210
211 def testInheritance(self):
212 pyson=ObjectMapper()
213
214
215 obj=Bear(Props(1,'bruno'))
216 res=pyson.toJson(obj)
217 print("result:"+str(res))
218 bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
219 self.assertEquals(bson, res)
220
221 res=pyson.parse(bson, Animal)
222 print("Deserialized an Animal! -->"+str(res))
223 self. assertEqual(obj, res)
224
225 def testAbcInheritance(self):
226 pyson=ObjectMapper()
227
228
229 obj=AbstractBear(Props(1,'bruno'))
230 res=pyson.toJson(obj)
231 print("result:"+str(res))
232 bson={'AbstractBear': {'props': {'age': 1, 'name': 'bruno'}}}
233 self.assertEquals(bson, res)
234
235 res=pyson.parse(bson, AbstractAnimal)
236 print("Deserialized an Animal! -->"+str(res))
237 self. assertEqual(obj, res)
238
239
240
241 def testUntypedList(self):
242 class Prim:
243 def __init__(self, a:list):
244 self._a=a
245 def geta(self)->list:
246 return self._a
247
248 pyson=ObjectMapper()
249 obj=Prim([1,2])
250 objson = {'a':[1,2]}
251
252 self.assertEqual(objson, pyson.toJson(obj))
253
254 self.assertRaises(ValueError, lambda:pyson.parse(objson, Prim))
255
256 def testDateTime(self):
257 pyson=ObjectMapper()
258 objson = 1000120 # 1000.12ms since 1970
259 obj=datetime.fromtimestamp(objson/1000.0);
260 self.assertEqual(objson, pyson.toJson(obj))
261 self.assertEqual(obj, pyson.parse(objson, datetime))
262
263
264 def testTypedList(self):
265 '''
266 deserializes typed list contained in another object
267 '''
268 class Prim:
269 def __init__(self, a:List[str]):
270 self._a=a
271 def geta(self)->List[str]:
272 return self._a
273 def __eq__(self, other):
274 return isinstance(other, self.__class__) and \
275 self._a==other._a
276
277 pyson=ObjectMapper()
278 obj=Prim(["x","y"])
279 objson = {'a':["x","y"]}
280
281 self.assertEqual(objson, pyson.toJson(obj))
282 self.assertEqual(obj, pyson.parse(objson, Prim))
283
284 def testTypedListDirect(self):
285 '''
286 deserializes typed list directly
287 '''
288
289 pyson=ObjectMapper()
290 obj=["x","y"]
291 objson = ["x","y"]
292
293 self.assertEqual(objson, pyson.toJson(obj))
294 self.assertEqual(obj, pyson.parse(objson, List[str]))
295
296 def testTypedListOfObjMissingAnnotation(self):
297 class Prim:
298 def __init__(self, a:int):
299 self._a=a
300 def geta(self)->int:
301 return self._a
302 def __eq__(self, other):
303 return isinstance(other, self.__class__) and \
304 self._a==other._a
305 pyson=ObjectMapper()
306 obj=[Prim(1),Prim(3)]
307 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
308 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
309 # object misses annotation, therefore this will try to parse
310 # Prim objects without header here.
311 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
312
313 def testTypedListOfObj(self):
314 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
315 class Prim:
316 def __init__(self, a:int):
317 self._a=a
318 def geta(self)->int:
319 return self._a
320 def __eq__(self, other):
321 return isinstance(other, self.__class__) and \
322 self._a==other._a
323
324 pyson=ObjectMapper()
325 obj=[Prim(1),Prim(3)]
326 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
327 self.assertEqual(objson, pyson.toJson(obj))
328 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
329
330 def testTypedSetOfObj(self):
331 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
332 class Prim:
333 def __init__(self, a:int):
334 self._a=a
335 def geta(self)->int:
336 return self._a
337 def __eq__(self, other):
338 return isinstance(other, self.__class__) and \
339 self._a==other._a
340
341 pyson=ObjectMapper()
342 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
343 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
344 self.assertEqual(objson, pyson.toJson(obj))
345 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
346 self.assertEqual(obj, parsedobj)
347
348
349 def testExpectListButGiveDict(self):
350 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
351 class Prim:
352 def __init__(self, a:int):
353 self._a=a
354 def geta(self)->int:
355 return self._a
356 def __eq__(self, other):
357 return isinstance(other, self.__class__) and \
358 self._a==other._a
359
360 pyson=ObjectMapper()
361 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
362 # we request List but obj is a dict.
363 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
364
365 def testSerializeDict(self):
366
367 pyson=ObjectMapper()
368 obj={'a':Simple(1),'c':Simple(3)}
369 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
370 self.assertEqual(objson, pyson.toJson(obj))
371
372
373 def testTypedDictOfObj(self):
374 pyson=ObjectMapper()
375 obj={'a':Simple(1),'c':Simple(3)}
376 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
377 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
378 print("deserialized obj"+str(objson)+"="+str(obj))
379
380 def testTypedDictSimpleKey(self):
381 pyson=ObjectMapper()
382
383 key=mydict()
384 key["Simple"]={'a':1}
385 # simple is not hashable
386 objson = { key : 'a' }
387 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
388
389 def testTypedDictSimpleKeyHashable(self):
390 pyson=ObjectMapper()
391 # key is now not primitive!
392 obj={SimpleWithHash(1):'a'}
393
394 # simple is not hashable
395 key=mydict()
396 key["SimpleWithHash"]={'a':1}
397 # simple is not hashable
398 objson = { key : 'a' }
399 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
400
401 def testDeserializeBadSubclass(self):
402 pyson=ObjectMapper()
403 objson= { 'BadSubclass':{ 'a':1}}
404 # FIXME the error message is poor in this case.
405 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassMissingTypeInfo))
406
407
408 def testModuleInsteadOfClassAsSubclasses(self):
409 pyson=ObjectMapper()
410 objson= { 'BadSubclass':{ 'a':1}}
411 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassModuleInstead))
412
413
414
415 def testJsonGetter(self):
416 pyson=ObjectMapper()
417 class Getter:
418 def __init__(self, a:int):
419 self._a=a
420 @JsonGetter("a")
421 def getValue(self):
422 return self._a
423 def __eq__(self, other):
424 return isinstance(other, self.__class__) and \
425 self._a==other._a
426
427 getter=Getter(17)
428 objson={'a':17}
429 self.assertEqual(objson, pyson.toJson(getter))
430 self.assertEqual(getter, pyson.parse(objson, Getter))
431
Note: See TracBrowser for help on using the repository browser.