source: pyson/test/DeserializerTest.py@ 968

Last change on this file since 968 was 968, checked in by wouter, 4 months ago

#331 check for key class being @JsonValue

File size: 10.1 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
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
26# deserializer DROPS FIRST CHAR from string and assumes rest is an int.
27# Then returns Simple(int)
28class ValueDeserializer(Deserializer):
29 def __hash__(self):
30 return hash(self.geta())
31 def deserialize(self, data:object, clas: object)-> object:
32 if type(data)!=str:
33 raise ValueError("Expected str starting with '$', got "+str(data))
34 return Simple(int(data[1:]))
35
36# serializes Simple object, just prefixing its value (as string) with "$"
37class ValueSerializer(Serializer):
38 def serialize(self, obj:object)-> object:
39 if not isinstance(obj, Simple):
40 raise ValueError("Expected Dimple object")
41 return "$" + str(obj.geta())
42
43class Basic:
44 def __init__(self, v:float):
45 self._v=v
46 def __eq__(self, other):
47 return isinstance(other, self.__class__) and \
48 self._v==other._v
49 def getV(self):
50 return self._v
51 def __repr__(self):
52 return "Basic:"+str(self._v)
53 def __hash__(self):
54 return hash(self._v)
55
56@JsonDeserialize(ValueDeserializer)
57@JsonSerialize(ValueSerializer)
58class Simple:
59 def __init__(self, a:int):
60 self._a=a
61 def geta(self)->int:
62 return self._a
63 def __eq__(self, other):
64 return isinstance(other, self.__class__) and \
65 self._a==other._a
66 def __repr__(self):
67 return "Simple:"+str(self._a)
68 def __hash__(self):
69 return hash(self._a)
70
71
72
73@JsonDeserialize(ValueDeserializer2)
74class Simple2:
75 def __init__(self, a:int):
76 self._a=a
77 def geta(self)->int:
78 return self._a
79 def __eq__(self, other):
80 return isinstance(other, self.__class__) and \
81 self._a==other._a
82 def __repr__(self):
83 return self._name+","+str(self._a)
84
85#None cancels out the existing deserializer.
86# parsing with str should now fail.
87@JsonDeserialize(None)
88class Simple3 (Simple):
89 pass
90
91
92class MyList:
93 def __init__(self, data: List[Simple] ):
94 self.data=data
95 def getData(self)->List[Simple]:
96 return self.data
97
98class BasicDict:
99 def __init__(self, data: Dict[Basic,Basic] ):
100 self.data=data
101 def getData(self)->Dict[Basic,Basic]:
102 return self.data
103 def __repr__(self):
104 return "BasicDict:"+str(self.data)
105 def __eq__(self, other):
106 return isinstance(other, self.__class__) and \
107 self.data==other.data
108
109
110class SimpleDict:
111 def __init__(self, data: Dict[Simple,Simple] ):
112 self.data=data
113 def getData(self)->Dict[Simple,Simple]:
114 return self.data
115 def __eq__(self, other):
116 return isinstance(other, self.__class__) and \
117 self.data==other.data
118 def __repr__(self):
119 return "SimpleDict:"+str(self.data)
120
121
122class MyOptionalString:
123 def __init__(self, s:Optional[str]):
124 self.data:Optional[str] = s
125 def getData(self)->Optional[str]:
126 return self.data
127 def __eq__(self, other):
128 return isinstance(other, self.__class__) and \
129 self.data==other.data
130 def __repr__(self):
131 return "MyOptionalString:"+str(self.data)
132
133
134@JsonSubTypes(['test.DeserializerTest.A','test.DeserializerTest.B','test.DeserializerTest.C'])
135@JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
136class Root:
137 def __init__(self, data: int ):
138 self.data:int=data
139 def getData(self)->int:
140 return self.data
141 def __eq__(self, other):
142 return isinstance(other, self.__class__) and \
143 self.data==other.data
144 def __repr__(self):
145 return type(self).__name__ + "/" + str(self.data)
146 def __hash__(self):
147 return 0
148
149
150class A(Root):
151 pass
152
153class B(Root):
154 pass
155
156class C(Root):
157 def __init__(self, data:int, name: str):
158 super().__init__(data)
159 self.name = name
160 def __eq__(self, other):
161 return isinstance(other, self.__class__) and \
162 self.data==other.data and self.name==other.name
163 def __repr__(self):
164 return super().__repr__()+","+self.name
165
166
167
168
169class BaseValueDeserializer (Deserializer):
170 def deserialize(self, data:object, clas: object) -> 'BaseValue':
171 if isinstance(data,str):
172 return BaseDiscreteValue(data)
173 raise ValueError("Expected number or double quoted string but found " + str(data)
174 + " of type " + str(type(data)))
175
176@JsonDeserialize(using=BaseValueDeserializer)
177class BaseValue(ABC):
178 def __init__(self, value):
179 self._value = value;
180
181 @JsonValue()
182 def getValue(self) -> str:
183 return self._value;
184
185#disable parent deserializer. This is essential #331
186#@JsonDeserialize(using=None)
187class BaseDiscreteValue(BaseValue):
188 def __init__(self, value:str):
189 self.__value=value
190 @JsonValue()
191 def getValue(self)->str:
192 return self.__value
193
194
195class DeserializerTest(unittest.TestCase):
196 '''
197 Test a lot of back-and-forth cases.
198 FIXME Can we make this a parameterized test?
199 '''
200 pyson=ObjectMapper()
201 mixedList:List[Root] = [A(1), B(1)]
202 mixedlistjson:List = [{'A': {'data': 1}}, {'B': {'data': 1}}]
203
204 mixedDict:Dict[Root,int] = {A(1):1, B(1):2}
205 mixedDictJson:dict = {'{"A": {"data": 1}}': 1, '{"B": {"data": 1}}': 2}
206 MyC:C = C(3, "MyC")
207
208 def testDeserialize(self):
209 objson= "$12"
210 self.assertEqual(Simple(12), self.pyson.parse(objson, Simple))
211
212 def testExternalDeserialize2(self):
213 objson= "$13"
214 self.assertEqual(13, self.pyson.parse(objson, Simple2))
215
216
217 def testExternalDeserialize3(self):
218 objson= "$13"
219 self.assertRaises(ValueError, lambda:self.pyson.parse(objson, Simple3))
220
221 def testDeserializeMyList(self):
222 print(self.pyson.toJson(MyList([12,13])))
223
224 # the json we provide is NOT the proper json but a STRING.
225 # This triggers a fallback mechanism that tries to parse the string as json.
226 objson={"data": ["$12", "$13"]}
227 res = self.pyson.parse(objson, MyList)
228 print(res)
229 self.assertEqual([Simple(12),Simple(13)], res.data)
230
231 def testDeserializeCollection(self):
232 objson=[1,2,3]
233 # this checks that pyson can parse a list as Collection.
234 # The result is a simple list like in java/jackson.
235 # Collection without typing info means Collectino[Any]
236 res = self.pyson.parse(objson, Collection)
237 print(res)
238 self.assertEqual([1,2,3], res)
239
240
241 def testSerializeBasicDict(self):
242 '''
243 Basic object keys. Special (de)serializer should kick in #190
244 '''
245 d= BasicDict( { Basic(1.):Basic(2.), Basic(3.): Basic(4.) } )
246 objson={"data": {"{\"v\": 1.0}": {"v": 2.0}, "{\"v\": 3.0}": {"v": 4.0}}}
247 dump=json.dumps(self.pyson.toJson(d));
248 print(dump)
249 # self.assertEqual(objson, dump);
250 res=self.pyson.parse(objson, BasicDict)
251 print("res="+str(res))
252 self.assertEqual(d, res)
253
254
255 def testSerializeSimpleDictCustomSerializer(self):
256 d= SimpleDict( { Simple(1):Simple(2), Simple(3): Simple(4) } )
257
258 # The keys need extra quotes, they are deserialied by
259 # our special deserializer that parses the string as json,
260 # and json requires double quotes around its strings.
261 objson = {"data": {'"$1"': "$2", '"$3"': "$4"}}
262
263 obj=self.pyson.toJson(d)
264 print(json.dumps(obj))
265 self.assertEqual(objson, obj)
266
267 res = self.pyson.parse(objson, SimpleDict)
268 print("res="+str(res))
269 self.assertEqual(d, res)
270
271 def testDeserializeOptString(self):
272 objson:dict={'s':None}
273 res = self.pyson.parse(objson, MyOptionalString)
274 print(res)
275 self.assertEqual(MyOptionalString(None), res)
276
277 objson={'s':"something"}
278 res = self.pyson.parse(objson, MyOptionalString)
279 print(res)
280 self.assertEqual(MyOptionalString("something"), res)
281
282 def testSerializeMixedList(self):
283 # see #296
284 res=self.pyson.toJson(self.mixedList)
285 print(res)
286 self.assertEqual(self.mixedlistjson, res)
287
288 def testDeserializeMixedList(self):
289 # see #296
290 res=self.pyson.parse(self.mixedlistjson, List[Root])
291 print(res)
292 self.assertEqual(self.mixedList,res)
293
294 def testDeserializeBadList(self):
295 # see #298. This SHOULD fail because B is not subtype of A
296 self.assertRaises(ValueError, lambda:self.pyson.parse(self.mixedlistjson, List[A]))
297
298
299 def testSerializeMixedDict(self):
300 # see #296
301 res=self.pyson.toJson(self.mixedDict)
302 print(res)
303 self.assertEqual(self.mixedDictJson, res)
304
305 def testDeserializeMixedDict(self):
306 # see #296
307 print("testDeserializeMixedDict")
308 res=self.pyson.parse(self.mixedDictJson, Dict[Root,int])
309 print(res)
310 self.assertEqual(self.mixedDict,res)
311
312 def testDeserializeC(self):
313 # see #298. Sub-class should not expect
314 res=self.pyson.parse({'C':{'data':3, 'name':'MyC' }}, Root)
315 print(reset)
316 self.assertEqual(self.MyC, res)
317 # even if you serialize as C, you still need the 'C' wrapper
318 res=self.pyson.parse({'C':{'data':3, 'name':'MyC' }}, C)
319 self.assertEqual(self.MyC, res)
320
321 def testDeserializeBaseValue(self):
322 # see #331
323 res=self.pyson.parse({'yes': 1, 'no': 0}, Dict[BaseDiscreteValue, int])
324 print(res)
325
Note: See TracBrowser for help on using the repository browser.