source: pyson/test/DeserializerTest.py@ 1394

Last change on this file since 1394 was 1365, checked in by wouter, 4 weeks ago

#422 fixed order of handling @JsonValue and @JsonTypeInfo in parse

File size: 13.0 KB
Line 
1from __future__ import annotations
2from abc import ABC
3from datetime import datetime
4from decimal import Decimal
5import json
6import re
7import sys, traceback
8from typing import Dict, List, Set, Any, Union, Optional, Collection, Type
9import unittest
10from uuid import uuid4, UUID
11
12from pyson.Deserializer import Deserializer
13from pyson.Serializer import Serializer
14from pyson.JsonSerialize import JsonSerialize
15from pyson.JsonDeserialize import JsonDeserialize
16from pyson.JsonGetter import JsonGetter
17from pyson.JsonSubTypes import JsonSubTypes
18from pyson.JsonTypeInfo import Id, As
19from pyson.JsonTypeInfo import JsonTypeInfo
20from pyson.JsonValue import JsonValue
21from pyson.ObjectMapper import ObjectMapper
22from pickle import NONE
23from test.MyDeserializer import ValueDeserializer2
24from cgitb import reset
25
26class SuperSimple:
27 def __init__(self, a:Optional[int]):
28 self.a=a
29 def __eq__(self, other):
30 return isinstance(other, self.__class__) and \
31 self.a==other.a
32 def getA(self):
33 return self.a
34 def __repr__(self):
35 return "SuperSimple::"+str(self.a)
36 def __hash__(self):
37 return hash(self.a)
38
39
40
41# deserializer DROPS FIRST CHAR from string and assumes rest is an int.
42# Then returns Simple(int)
43class ValueDeserializer(Deserializer):
44 def __hash__(self):
45 return hash(self.geta())
46 def deserialize(self, data:object, clas: object)-> object:
47 if type(data)!=str:
48 raise ValueError("Expected str starting with '$', got "+str(data))
49 return Simple(int(data[1:]))
50
51# serializes Simple object, just prefixing its value (as string) with "$"
52class ValueSerializer(Serializer):
53 def serialize(self, obj:object)-> object:
54 if not isinstance(obj, Simple):
55 raise ValueError("Expected Dimple object")
56 return "$" + str(obj.geta())
57
58class Basic:
59 def __init__(self, v:float):
60 self._v=v
61 def __eq__(self, other):
62 return isinstance(other, self.__class__) and \
63 self._v==other._v
64 def getV(self):
65 return self._v
66 def __repr__(self):
67 return "Basic:"+str(self._v)
68 def __hash__(self):
69 return hash(self._v)
70
71@JsonDeserialize(ValueDeserializer)
72@JsonSerialize(ValueSerializer)
73class Simple:
74 def __init__(self, a:int):
75 self._a=a
76 def geta(self)->int:
77 return self._a
78 def __eq__(self, other):
79 return isinstance(other, self.__class__) and \
80 self._a==other._a
81 def __repr__(self):
82 return "Simple:"+str(self._a)
83 def __hash__(self):
84 return hash(self._a)
85
86
87
88@JsonDeserialize(ValueDeserializer2)
89class Simple2:
90 def __init__(self, a:int):
91 self._a=a
92 def geta(self)->int:
93 return self._a
94 def __eq__(self, other):
95 return isinstance(other, self.__class__) and \
96 self._a==other._a
97 def __repr__(self):
98 return self._name+","+str(self._a)
99
100#None cancels out the existing deserializer.
101# parsing with str should now fail.
102@JsonDeserialize(None)
103class Simple3 (Simple):
104 pass
105
106
107class MyList:
108 def __init__(self, data: List[Simple] ):
109 self.data=data
110 def getData(self)->List[Simple]:
111 return self.data
112
113class BasicDict:
114 def __init__(self, data: Dict[Basic,Basic] ):
115 self.data=data
116 def getData(self)->Dict[Basic,Basic]:
117 return self.data
118 def __repr__(self):
119 return "BasicDict:"+str(self.data)
120 def __eq__(self, other):
121 return isinstance(other, self.__class__) and \
122 self.data==other.data
123
124
125class SimpleDict:
126 def __init__(self, data: Dict[Simple,Simple] ):
127 self.data=data
128 def getData(self)->Dict[Simple,Simple]:
129 return self.data
130 def __eq__(self, other):
131 return isinstance(other, self.__class__) and \
132 self.data==other.data
133 def __repr__(self):
134 return "SimpleDict:"+str(self.data)
135
136
137class MyOptionalString:
138 def __init__(self, s:Optional[str]):
139 self.data:Optional[str] = s
140 def getData(self)->Optional[str]:
141 return self.data
142 def __eq__(self, other):
143 return isinstance(other, self.__class__) and \
144 self.data==other.data
145 def __repr__(self):
146 return "MyOptionalString:"+str(self.data)
147
148
149@JsonSubTypes(['test.DeserializerTest.A','test.DeserializerTest.B','test.DeserializerTest.C'])
150@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
151class Root:
152 def __init__(self, data: int ):
153 self.data:int=data
154 def getData(self)->int:
155 return self.data
156 def __eq__(self, other):
157 return isinstance(other, self.__class__) and \
158 self.data==other.data
159 def __repr__(self):
160 return type(self).__name__ + "/" + str(self.data)
161 def __hash__(self):
162 return 0
163
164
165class A(Root):
166 pass
167
168class B(Root):
169 pass
170
171class C(Root):
172 def __init__(self, data:int, name: str):
173 super().__init__(data)
174 self.name = name
175 def __eq__(self, other):
176 return isinstance(other, self.__class__) and \
177 self.data==other.data and self.name==other.name
178 def __repr__(self):
179 return super().__repr__()+","+self.name
180
181
182
183
184class BaseValueDeserializer (Deserializer):
185 def deserialize(self, data:object, clas: object) -> 'BaseValue':
186 if isinstance(data,str):
187 return BaseDiscreteValue(data)
188 raise ValueError("Expected number or double quoted string but found " + str(data)
189 + " of type " + str(type(data)))
190
191@JsonDeserialize(using=BaseValueDeserializer)
192class BaseValue(ABC):
193 def __init__(self, value):
194 self._value = value;
195
196 @JsonValue()
197 def getValue(self) -> str:
198 return self._value;
199
200#disable parent deserializer. This is essential #331
201#@JsonDeserialize(using=None)
202class BaseDiscreteValue(BaseValue):
203 def __init__(self, value:str):
204 self.__value=value
205 @JsonValue()
206 def getValue(self)->str:
207 return self.__value
208
209
210
211@JsonSubTypes(['test.DeserializerTest.SubClass'])
212@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
213class RootClass(SuperSimple):
214 pass
215
216@JsonSubTypes(['test.DeserializerTest.SubSubClass'])
217class SubClass(RootClass):
218 pass
219
220# INDIRECTLY inherits from RootClass.
221class SubSubClass(SubClass):
222 pass
223
224
225@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
226class BothTypeInfoAndValue:
227 def __init__(self, val:str):
228 self.__value=val
229 @JsonValue()
230 def getVal(self)->str:
231 return self.__value
232 def __eq__(self, other):
233 return isinstance(other, self.__class__) and \
234 self.__value==other.__value
235 def __hash__(self):
236 return hash(self.__value)
237
238
239class DeserializerTest(unittest.TestCase):
240 '''
241 Test a lot of back-and-forth cases.
242 FIXME Can we make this a parameterized test?
243 '''
244 pyson=ObjectMapper()
245 mixedList:List[Root] = [A(1), B(1)]
246 mixedlistjson:List = [{'A': {'data': 1}}, {'B': {'data': 1}}]
247
248 mixedDict:Dict[Root,int] = {A(1):1, B(1):2}
249 mixedDictJson:dict = {'{"A": {"data": 1}}': 1, '{"B": {"data": 1}}': 2}
250 MyC:C = C(3, "MyC")
251
252 def testDeserialize(self):
253 objson= "$12"
254 self.assertEqual(Simple(12), self.pyson.parse(objson, Simple))
255
256 def testExternalDeserialize2(self):
257 objson= "$13"
258 self.assertEqual(13, self.pyson.parse(objson, Simple2))
259
260
261 def testExternalDeserialize3(self):
262 objson= "$13"
263 self.assertRaises(ValueError, lambda:self.pyson.parse(objson, Simple3))
264
265 def testDeserializeMyList(self):
266 print(self.pyson.toJson(MyList([12,13])))
267
268 # the json we provide is NOT the proper json but a STRING.
269 # This triggers a fallback mechanism that tries to parse the string as json.
270 objson={"data": ["$12", "$13"]}
271 res = self.pyson.parse(objson, MyList)
272 print(res)
273 self.assertEqual([Simple(12),Simple(13)], res.data)
274
275 def testDeserializeCollection(self):
276 objson=[1,2,3]
277 # this checks that pyson can parse a list as Collection.
278 # The result is a simple list like in java/jackson.
279 # Collection without typing info means Collectino[Any]
280 res = self.pyson.parse(objson, Collection)
281 print(res)
282 self.assertEqual([1,2,3], res)
283
284
285 def testSerializeBasicDict(self):
286 '''
287 Basic object keys. Special (de)serializer should kick in #190
288 '''
289 d= BasicDict( { Basic(1.):Basic(2.), Basic(3.): Basic(4.) } )
290 objson={"data": {"{\"v\": 1.0}": {"v": 2.0}, "{\"v\": 3.0}": {"v": 4.0}}}
291 dump=json.dumps(self.pyson.toJson(d));
292 print(dump)
293 # self.assertEqual(objson, dump);
294 res=self.pyson.parse(objson, BasicDict)
295 print("res="+str(res))
296 self.assertEqual(d, res)
297
298
299 def testSerializeSimpleDictCustomSerializer(self):
300 d= SimpleDict( { Simple(1):Simple(2), Simple(3): Simple(4) } )
301
302 # The keys need extra quotes, they are deserialied by
303 # our special deserializer that parses the string as json,
304 # and json requires double quotes around its strings.
305 objson = {"data": {'"$1"': "$2", '"$3"': "$4"}}
306
307 obj=self.pyson.toJson(d)
308 print(json.dumps(obj))
309 self.assertEqual(objson, obj)
310
311 res = self.pyson.parse(objson, SimpleDict)
312 print("res="+str(res))
313 self.assertEqual(d, res)
314
315 def testDeserializeOptString(self):
316 objson:dict={'s':None}
317 res = self.pyson.parse(objson, MyOptionalString)
318 print(res)
319 self.assertEqual(MyOptionalString(None), res)
320
321 objson={'s':"something"}
322 res = self.pyson.parse(objson, MyOptionalString)
323 print(res)
324 self.assertEqual(MyOptionalString("something"), res)
325
326 def testSerializeMixedList(self):
327 # see #296
328 res=self.pyson.toJson(self.mixedList)
329 print(res)
330 self.assertEqual(self.mixedlistjson, res)
331
332 def testDeserializeMixedList(self):
333 # see #296
334 res=self.pyson.parse(self.mixedlistjson, List[Root])
335 print(res)
336 self.assertEqual(self.mixedList,res)
337
338 def testDeserializeBadList(self):
339 # see #298. This SHOULD fail because B is not subtype of A
340 self.assertRaises(ValueError, lambda:self.pyson.parse(self.mixedlistjson, List[A]))
341
342
343 def testSerializeMixedDict(self):
344 # see #296
345 res=self.pyson.toJson(self.mixedDict)
346 print(res)
347 self.assertEqual(self.mixedDictJson, res)
348
349 def testDeserializeMixedDict(self):
350 # see #296
351 print("testDeserializeMixedDict")
352 res=self.pyson.parse(self.mixedDictJson, Dict[Root,int])
353 print(res)
354 self.assertEqual(self.mixedDict,res)
355
356 def testDeserializeC(self):
357 # see #298. Sub-class should not expect
358 res=self.pyson.parse({'C':{'data':3, 'name':'MyC' }}, Root)
359 print(reset)
360 self.assertEqual(self.MyC, res)
361 # even if you serialize as C, you still need the 'C' wrapper
362 res=self.pyson.parse({'C':{'data':3, 'name':'MyC' }}, C)
363 self.assertEqual(self.MyC, res)
364
365 def testDeserializeBaseValue(self):
366 # see #331
367 res=self.pyson.parse({'yes': 1, 'no': 0}, Dict[BaseDiscreteValue, int])
368 print(res)
369
370
371 def testDeserializeSuperSimple(self):
372 res=self.pyson.parse({'a':1 } , SuperSimple)
373 print(res)
374 self.assertEqual(SuperSimple(1), res)
375
376
377 def testDeserializeMissingValue(self):
378 res=self.pyson.parse({ } , SuperSimple) # missing value for a
379 print(res)
380 self.assertEqual(SuperSimple(None), res)
381
382 def testDeserializeStandardDict(self):
383 val:Dict = self.pyson.parse(json.loads("{\"a\":0.3,\"b\":{\"x\":3},\"c\":[1,2,3]}"),Dict)
384
385
386 def testDeserializeClass(self):
387 clazz:Type[Root] = self.pyson.parse("test.DeserializerTest$C", Type[Root])
388 self.assertEquals(C, clazz)
389
390 def testSerializeClass(self):
391 js = self.pyson.toJson(C)
392 print("serialized Class<C>:"+json.dumps(js))
393 self.assertEquals("test.DeserializerTest$C", js)
394
395
396 def testDeserializeListOfClass(self):
397 clazzlist:List[Type[Root]] = self.pyson.parse(["test.DeserializerTest$C","test.DeserializerTest$A"], List[Type[Root]])
398 self.assertEquals([C,A], clazzlist)
399
400
401 def testSerializeSubSubClass(self):
402 js = self.pyson.toJson(SubSubClass(22))
403 print("serialized SuperSimple:"+json.dumps(js))
404 self.assertEquals({'SubSubClass': {'a': 22}}, js)
405
406
407 def testDeserializeSubSubClass(self):
408 js=self.pyson.parse({'SubSubClass': {'a': 22}}, RootClass)
409 self.assertEquals(SubSubClass(22), js)
410
411
412 def testSerializeBothTypeInfoAndValue(self):
413 js = self.pyson.toJson(BothTypeInfoAndValue("ok"))
414 print("serialized BothTypeInfoAndValue:"+json.dumps(js))
415 self.assertEquals({'BothTypeInfoAndValue': "ok"}, js)
416
417 def testDeserializeBothTypeInfoAndValue(self):
418 js = self.pyson.parse({'BothTypeInfoAndValue': "ok"}, BothTypeInfoAndValue)
419 self.assertEquals(BothTypeInfoAndValue("ok"), js)
420
421
Note: See TracBrowser for help on using the repository browser.