source: pyson/test/ObjectMapperTest.py@ 164

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

small fixes #73

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