source: pyson/test/ObjectMapperTest.py@ 143

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

#62 fix issue if referred class is actually not a class but a module

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