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
RevLine 
[837]1from __future__ import annotations
[489]2from abc import ABC
3from datetime import datetime
4from decimal import Decimal
5import json
6import re
7import sys, traceback
[749]8from typing import Dict, List, Set, Any, Union, Optional, Collection
[489]9import unittest
10from uuid import uuid4, UUID
11
12from pyson.Deserializer import Deserializer
[570]13from pyson.Serializer import Serializer
14from pyson.JsonSerialize import JsonSerialize
[489]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
[568]22from pickle import NONE
[571]23from test.MyDeserializer import ValueDeserializer2
[938]24from cgitb import reset
[489]25
[568]26# deserializer DROPS FIRST CHAR from string and assumes rest is an int.
27# Then returns Simple(int)
[490]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:]))
[489]35
[570]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())
[490]42
[570]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
[571]56@JsonDeserialize(ValueDeserializer)
[572]57@JsonSerialize(ValueSerializer)
[489]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
[570]66 def __repr__(self):
67 return "Simple:"+str(self._a)
68 def __hash__(self):
69 return hash(self._a)
[489]70
71
72
[571]73@JsonDeserialize(ValueDeserializer2)
[490]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
[570]82 def __repr__(self):
[490]83 return self._name+","+str(self._a)
[568]84
85#None cancels out the existing deserializer.
86# parsing with str should now fail.
87@JsonDeserialize(None)
88class Simple3 (Simple):
89 pass
[967]90
[489]91
[570]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)
[568]105 def __eq__(self, other):
106 return isinstance(other, self.__class__) and \
[570]107 self.data==other.data
[490]108
[570]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)
[490]120
[570]121
[827]122class MyOptionalString:
123 def __init__(self, s:Optional[str]):
[829]124 self.data:Optional[str] = s
125 def getData(self)->Optional[str]:
[827]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):
[829]131 return "MyOptionalString:"+str(self.data)
[835]132
[836]133
[938]134@JsonSubTypes(['test.DeserializerTest.A','test.DeserializerTest.B','test.DeserializerTest.C'])
[836]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):
[835]151 pass
[827]152
[836]153class B(Root):
[835]154 pass
155
[938]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
[836]165
166
[938]167
168
[967]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
[968]185#disable parent deserializer. This is essential #331
186#@JsonDeserialize(using=None)
[967]187class BaseDiscreteValue(BaseValue):
188 def __init__(self, value:str):
189 self.__value=value
[968]190 @JsonValue()
191 def getValue(self)->str:
192 return self.__value
[967]193
194
[489]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()
[836]201 mixedList:List[Root] = [A(1), B(1)]
202 mixedlistjson:List = [{'A': {'data': 1}}, {'B': {'data': 1}}]
[489]203
[836]204 mixedDict:Dict[Root,int] = {A(1):1, B(1):2}
205 mixedDictJson:dict = {'{"A": {"data": 1}}': 1, '{"B": {"data": 1}}': 2}
[938]206 MyC:C = C(3, "MyC")
[836]207
[489]208 def testDeserialize(self):
209 objson= "$12"
[490]210 self.assertEqual(Simple(12), self.pyson.parse(objson, Simple))
[489]211
[490]212 def testExternalDeserialize2(self):
213 objson= "$13"
214 self.assertEqual(13, self.pyson.parse(objson, Simple2))
[568]215
216
217 def testExternalDeserialize3(self):
218 objson= "$13"
[569]219 self.assertRaises(ValueError, lambda:self.pyson.parse(objson, Simple3))
[568]220
[570]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
[748]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.
[749]235 # Collection without typing info means Collectino[Any]
[748]236 res = self.pyson.parse(objson, Collection)
237 print(res)
238 self.assertEqual([1,2,3], res)
[570]239
[748]240
[570]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)
[827]270
271 def testDeserializeOptString(self):
[828]272 objson:dict={'s':None}
[827]273 res = self.pyson.parse(objson, MyOptionalString)
274 print(res)
[829]275 self.assertEqual(MyOptionalString(None), res)
[570]276
[828]277 objson={'s':"something"}
[827]278 res = self.pyson.parse(objson, MyOptionalString)
279 print(res)
[829]280 self.assertEqual(MyOptionalString("something"), res)
[827]281
[835]282 def testSerializeMixedList(self):
283 # see #296
[836]284 res=self.pyson.toJson(self.mixedList)
285 print(res)
286 self.assertEqual(self.mixedlistjson, res)
[835]287
[836]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
[837]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
[836]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
[938]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
[967]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.