source: pyson/test/ObjectMapperTest.py@ 160

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

#70 modifications accepting primitives

File size: 14.2 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, Any
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
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 def testDeserializeNoSuchField(self):
210 pyson=ObjectMapper()
211 # Bear has a 'props' field, bot 'b'
212 self.assertRaises(ValueError, lambda:pyson.parse({'b':1}, Props))
213 #pyson.parse({'b':1}, Props)
214
215 def testInheritance(self):
216 pyson=ObjectMapper()
217
218
219 obj=Bear(Props(1,'bruno'))
220 res=pyson.toJson(obj)
221 print("result:"+str(res))
222 bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
223 self.assertEquals(bson, res)
224
225 res=pyson.parse(bson, Animal)
226 print("Deserialized an Animal! -->"+str(res))
227 self. assertEqual(obj, res)
228
229 def testAbcInheritance(self):
230 pyson=ObjectMapper()
231
232
233 obj=AbstractBear(Props(1,'bruno'))
234 res=pyson.toJson(obj)
235 print("result:"+str(res))
236 bson={'AbstractBear': {'props': {'age': 1, 'name': 'bruno'}}}
237 self.assertEquals(bson, res)
238
239 res=pyson.parse(bson, AbstractAnimal)
240 print("Deserialized an Animal! -->"+str(res))
241 self. assertEqual(obj, res)
242
243
244
245 def testUntypedList(self):
246 class Prim:
247 def __init__(self, a:list):
248 self._a=a
249 def geta(self)->list:
250 return self._a
251
252 pyson=ObjectMapper()
253 obj=Prim([1,2])
254 objson = {'a':[1,2]}
255
256 self.assertEqual(objson, pyson.toJson(obj))
257
258 self.assertRaises(ValueError, lambda:pyson.parse(objson, Prim))
259
260 def testDateTime(self):
261 pyson=ObjectMapper()
262 objson = 1000120 # 1000.12ms since 1970
263 obj=datetime.fromtimestamp(objson/1000.0);
264 self.assertEqual(objson, pyson.toJson(obj))
265 self.assertEqual(obj, pyson.parse(objson, datetime))
266
267
268 def testTypedList(self):
269 '''
270 deserializes typed list contained in another object
271 '''
272 class Prim:
273 def __init__(self, a:List[str]):
274 self._a=a
275 def geta(self)->List[str]:
276 return self._a
277 def __eq__(self, other):
278 return isinstance(other, self.__class__) and \
279 self._a==other._a
280
281 pyson=ObjectMapper()
282 obj=Prim(["x","y"])
283 objson = {'a':["x","y"]}
284
285 self.assertEqual(objson, pyson.toJson(obj))
286 self.assertEqual(obj, pyson.parse(objson, Prim))
287
288 def testTypedListDirect(self):
289 '''
290 deserializes typed list directly
291 '''
292
293 pyson=ObjectMapper()
294 obj=["x","y"]
295 objson = ["x","y"]
296
297 self.assertEqual(objson, pyson.toJson(obj))
298 self.assertEqual(obj, pyson.parse(objson, List[str]))
299
300 def testMixedDict(self):
301 pyson=ObjectMapper()
302 obj={'a':1, 'b':'blabla'}
303
304 # primitive types, should not be changed
305 self.assertEqual(obj, pyson.toJson(obj))
306 self.assertEqual(obj, pyson.parse(obj, Dict[Any,Any]))
307
308 def testTypedListOfObjMissingAnnotation(self):
309 class Prim:
310 def __init__(self, a:int):
311 self._a=a
312 def geta(self)->int:
313 return self._a
314 def __eq__(self, other):
315 return isinstance(other, self.__class__) and \
316 self._a==other._a
317 pyson=ObjectMapper()
318 obj=[Prim(1),Prim(3)]
319 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
320 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
321 # object misses annotation, therefore this will try to parse
322 # Prim objects without header here.
323 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
324
325 def testTypedListOfObj(self):
326 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
327 class Prim:
328 def __init__(self, a:int):
329 self._a=a
330 def geta(self)->int:
331 return self._a
332 def __eq__(self, other):
333 return isinstance(other, self.__class__) and \
334 self._a==other._a
335
336 pyson=ObjectMapper()
337 obj=[Prim(1),Prim(3)]
338 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
339 self.assertEqual(objson, pyson.toJson(obj))
340 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
341
342 def testTypedSetOfObj(self):
343 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
344 class Prim:
345 def __init__(self, a:int):
346 self._a=a
347 def geta(self)->int:
348 return self._a
349 def __eq__(self, other):
350 return isinstance(other, self.__class__) and \
351 self._a==other._a
352
353 pyson=ObjectMapper()
354 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
355 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
356 self.assertEqual(objson, pyson.toJson(obj))
357 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
358 self.assertEqual(obj, parsedobj)
359
360
361 def testExpectListButGiveDict(self):
362 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
363 class Prim:
364 def __init__(self, a:int):
365 self._a=a
366 def geta(self)->int:
367 return self._a
368 def __eq__(self, other):
369 return isinstance(other, self.__class__) and \
370 self._a==other._a
371
372 pyson=ObjectMapper()
373 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
374 # we request List but obj is a dict.
375 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
376
377 def testSerializeDict(self):
378
379 pyson=ObjectMapper()
380 obj={'a':Simple(1),'c':Simple(3)}
381 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
382 self.assertEqual(objson, pyson.toJson(obj))
383
384
385 def testTypedDictOfObj(self):
386 pyson=ObjectMapper()
387 obj={'a':Simple(1),'c':Simple(3)}
388 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
389 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
390 print("deserialized obj"+str(objson)+"="+str(obj))
391
392 def testTypedDictSimpleKey(self):
393 pyson=ObjectMapper()
394
395 key=mydict()
396 key["Simple"]={'a':1}
397 # simple is not hashable
398 objson = { key : 'a' }
399 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
400
401 def testTypedDictSimpleKeyHashable(self):
402 pyson=ObjectMapper()
403 # key is now not primitive!
404 obj={SimpleWithHash(1):'a'}
405
406 # simple is not hashable
407 key=mydict()
408 key["SimpleWithHash"]={'a':1}
409 # simple is not hashable
410 objson = { key : 'a' }
411 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
412
413 def testDeserializeBadSubclass(self):
414 pyson=ObjectMapper()
415 objson= { 'BadSubclass':{ 'a':1}}
416 # FIXME the error message is poor in this case.
417 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassMissingTypeInfo))
418
419
420 def testModuleInsteadOfClassAsSubclasses(self):
421 pyson=ObjectMapper()
422 objson= { 'BadSubclass':{ 'a':1}}
423 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassModuleInstead))
424
425
426
427 def testJsonGetter(self):
428 class Getter:
429 def __init__(self, a:int):
430 self._a=a
431 @JsonGetter("a")
432 def getValue(self):
433 return self._a
434 def __eq__(self, other):
435 return isinstance(other, self.__class__) and \
436 self._a==other._a
437
438 pyson=ObjectMapper()
439 getter=Getter(17)
440 objson={'a':17}
441 self.assertEqual(objson, pyson.toJson(getter))
442 self.assertEqual(getter, pyson.parse(objson, Getter))
443
444 def testGetterIgnoresCase(self):
445 class Getter:
446 def __init__(self, value:int):
447 self._a=value
448 def getValue(self):
449 return self._a
450 def __eq__(self, other):
451 return isinstance(other, self.__class__) and \
452 self._a==other._a
453
454 pyson=ObjectMapper()
455 getter=Getter(17)
456 objson={'value':17}
457 self.assertEqual(objson, pyson.toJson(getter))
458 self.assertEqual(getter, pyson.parse(objson, Getter))
459
460
Note: See TracBrowser for help on using the repository browser.