source: pyson/test/ObjectMapperTest.py@ 162

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

#71 uri now available as primitive

File size: 15.6 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
288 pyson=ObjectMapper()
289 obj=Prim([1,2])
290 objson = {'a':[1,2]}
291
292 self.assertEqual(objson, pyson.toJson(obj))
293
294 self.assertRaises(ValueError, lambda:pyson.parse(objson, Prim))
295
296 def testDateTime(self):
297 pyson=ObjectMapper()
298 objson = 1000120 # 1000.12ms since 1970
299 obj=datetime.fromtimestamp(objson/1000.0);
300 self.assertEqual(objson, pyson.toJson(obj))
301 self.assertEqual(obj, pyson.parse(objson, datetime))
302
303
304 def testTypedList(self):
305 '''
306 deserializes typed list contained in another object
307 '''
308 class Prim:
309 def __init__(self, a:List[str]):
310 self._a=a
311 def geta(self)->List[str]:
312 return self._a
313 def __eq__(self, other):
314 return isinstance(other, self.__class__) and \
315 self._a==other._a
316
317 pyson=ObjectMapper()
318 obj=Prim(["x","y"])
319 objson = {'a':["x","y"]}
320
321 self.assertEqual(objson, pyson.toJson(obj))
322 self.assertEqual(obj, pyson.parse(objson, Prim))
323
324 def testTypedListDirect(self):
325 '''
326 deserializes typed list directly
327 '''
328
329 pyson=ObjectMapper()
330 obj=["x","y"]
331 objson = ["x","y"]
332
333 self.assertEqual(objson, pyson.toJson(obj))
334 self.assertEqual(obj, pyson.parse(objson, List[str]))
335
336 def testMixedDict(self):
337 pyson=ObjectMapper()
338 obj={'a':1, 'b':'blabla'}
339
340 # primitive types, should not be changed
341 self.assertEqual(obj, pyson.toJson(obj))
342 self.assertEqual(obj, pyson.parse(obj, Dict[Any,Any]))
343
344 def testTypedListOfObjMissingAnnotation(self):
345 class Prim:
346 def __init__(self, a:int):
347 self._a=a
348 def geta(self)->int:
349 return self._a
350 def __eq__(self, other):
351 return isinstance(other, self.__class__) and \
352 self._a==other._a
353 pyson=ObjectMapper()
354 obj=[Prim(1),Prim(3)]
355 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
356 self.assertRaises(ValueError, lambda:pyson.toJson(obj))
357 # object misses annotation, therefore this will try to parse
358 # Prim objects without header here.
359 self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim]))
360
361 def testTypedListOfObj(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 obj=[Prim(1),Prim(3)]
374 objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
375 self.assertEqual(objson, pyson.toJson(obj))
376 self.assertEqual(obj, pyson.parse(objson, List[Prim]))
377
378 def testTypedSetOfObj(self):
379 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
380 class Prim:
381 def __init__(self, a:int):
382 self._a=a
383 def geta(self)->int:
384 return self._a
385 def __eq__(self, other):
386 return isinstance(other, self.__class__) and \
387 self._a==other._a
388
389 pyson=ObjectMapper()
390 obj=set([SimpleWithHash(1),SimpleWithHash(3)])
391 objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
392 self.assertEqual(objson, pyson.toJson(obj))
393 parsedobj=pyson.parse(objson, Set[SimpleWithHash])
394 self.assertEqual(obj, parsedobj)
395
396
397 def testExpectListButGiveDict(self):
398 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
399 class Prim:
400 def __init__(self, a:int):
401 self._a=a
402 def geta(self)->int:
403 return self._a
404 def __eq__(self, other):
405 return isinstance(other, self.__class__) and \
406 self._a==other._a
407
408 pyson=ObjectMapper()
409 objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
410 # we request List but obj is a dict.
411 self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim]))
412
413 def testSerializeDict(self):
414
415 pyson=ObjectMapper()
416 obj={'a':Simple(1),'c':Simple(3)}
417 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
418 self.assertEqual(objson, pyson.toJson(obj))
419
420
421 def testTypedDictOfObj(self):
422 pyson=ObjectMapper()
423 obj={'a':Simple(1),'c':Simple(3)}
424 objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
425 self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple]))
426 print("deserialized obj"+str(objson)+"="+str(obj))
427
428 def testTypedDictSimpleKey(self):
429 pyson=ObjectMapper()
430
431 key=mydict()
432 key["Simple"]={'a':1}
433 # simple is not hashable
434 objson = { key : 'a' }
435 self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str]))
436
437 def testTypedDictSimpleKeyHashable(self):
438 pyson=ObjectMapper()
439 # key is now not primitive!
440 obj={SimpleWithHash(1):'a'}
441
442 # simple is not hashable
443 key=mydict()
444 key["SimpleWithHash"]={'a':1}
445 # simple is not hashable
446 objson = { key : 'a' }
447 self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str]))
448
449 def testDeserializeBadSubclass(self):
450 pyson=ObjectMapper()
451 objson= { 'BadSubclass':{ 'a':1}}
452 # FIXME the error message is poor in this case.
453 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassMissingTypeInfo))
454
455
456 def testModuleInsteadOfClassAsSubclasses(self):
457 pyson=ObjectMapper()
458 objson= { 'BadSubclass':{ 'a':1}}
459 self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassModuleInstead))
460
461
462
463 def testJsonGetter(self):
464 class Getter:
465 def __init__(self, a:int):
466 self._a=a
467 @JsonGetter("a")
468 def getValue(self):
469 return self._a
470 def __eq__(self, other):
471 return isinstance(other, self.__class__) and \
472 self._a==other._a
473
474 pyson=ObjectMapper()
475 getter=Getter(17)
476 objson={'a':17}
477 self.assertEqual(objson, pyson.toJson(getter))
478 self.assertEqual(getter, pyson.parse(objson, Getter))
479
480 def testGetterIgnoresCase(self):
481 class Getter:
482 def __init__(self, value:int):
483 self._a=value
484 def getValue(self):
485 return self._a
486 def __eq__(self, other):
487 return isinstance(other, self.__class__) and \
488 self._a==other._a
489
490 pyson=ObjectMapper()
491 getter=Getter(17)
492 objson={'value':17}
493 self.assertEqual(objson, pyson.toJson(getter))
494 self.assertEqual(getter, pyson.parse(objson, Getter))
495
496
497 def testJsonValue(self):
498 pyson=ObjectMapper()
499 getter=Snare(17)
500 objson=17
501 print(pyson.toJson(getter))
502 self.assertEqual(objson, pyson.toJson(getter))
503 self.assertEqual(getter, pyson.parse(objson, Snare))
504
505
506 def testJsonValueAsPart(self):
507 pyson=ObjectMapper()
508 csnare=ContainsSnare(Snare(18))
509 objson={"snare":18}
510 print(pyson.toJson(csnare))
511 self.assertEqual(objson, pyson.toJson(csnare))
512 self.assertEqual(csnare, pyson.parse(objson, ContainsSnare))
513
514
515
Note: See TracBrowser for help on using the repository browser.