source: pyson/test/DeserializerTest.py@ 896

Last change on this file since 896 was 837, checked in by wouter, 5 months ago

#298 added test checking that deserializing a list with bad elements (wrong class) is caught

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