[134] | 1 |
|
---|
| 2 | import re
|
---|
| 3 | import unittest
|
---|
| 4 | import sys, traceback
|
---|
| 5 | from packson.ObjectMapper import ObjectMapper
|
---|
| 6 | from packson.JsonSubTypes import JsonSubTypes
|
---|
| 7 | from packson.JsonTypeInfo import JsonTypeInfo
|
---|
| 8 | from packson.JsonTypeInfo import Id,As
|
---|
| 9 | from typing import Dict,List,Set
|
---|
| 10 | import json
|
---|
| 11 |
|
---|
| 12 | class 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)
|
---|
| 32 | class 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 |
|
---|
| 44 | class 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)
|
---|
| 52 | class Animal:
|
---|
| 53 | pass
|
---|
| 54 |
|
---|
| 55 |
|
---|
| 56 | class 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 | # our parser supports non-primitive keys but python dict dont.
|
---|
| 71 | # therefore the following fails even before we can start testing our code...
|
---|
| 72 | # we need to create a hashable dict to get around ths
|
---|
| 73 | class mydict(dict):
|
---|
| 74 | def __hash__(self, *args, **kwargs):
|
---|
| 75 | return 1
|
---|
| 76 |
|
---|
| 77 |
|
---|
| 78 | class ObjectMapperTest(unittest.TestCase):
|
---|
| 79 | def testPrimitives(self):
|
---|
| 80 | packson=ObjectMapper()
|
---|
| 81 |
|
---|
| 82 | res=packson.parse(3, int)
|
---|
| 83 | self.assertEquals(3, res)
|
---|
| 84 |
|
---|
| 85 |
|
---|
| 86 | # this throws correct,
|
---|
| 87 | self.assertRaises(ValueError, lambda:packson.parse(3, str))
|
---|
| 88 |
|
---|
| 89 | # this throws correct,
|
---|
| 90 | self.assertRaises(ValueError, lambda:packson.parse("ja", int))
|
---|
| 91 |
|
---|
| 92 | #DEMO with nested classes of different types.
|
---|
| 93 | res=packson.parse('three', str)
|
---|
| 94 | print(res, type(res))
|
---|
| 95 |
|
---|
| 96 | packson.parse(3.0, float)
|
---|
| 97 | packson.parse(3.1, float)
|
---|
| 98 | packson.parse(3j, complex)
|
---|
| 99 | packson.parse(range(6), range)
|
---|
| 100 | packson.parse(True, bool)
|
---|
| 101 | packson.parse(False, bool)
|
---|
| 102 | packson.parse(b"Hello", bytes)
|
---|
| 103 | packson.parse(bytearray(b'\x00\x00\x00\x01'), bytearray)
|
---|
| 104 |
|
---|
| 105 |
|
---|
| 106 | def testProps(self):
|
---|
| 107 | packson=ObjectMapper()
|
---|
| 108 | propsjson={'age': 10, 'name': 'pietje'}
|
---|
| 109 | props=Props(10, "pietje")
|
---|
| 110 | self.assertEquals(propsjson,packson.toJson(props))
|
---|
| 111 | self.assertEquals(props, packson.parse(propsjson, Props))
|
---|
| 112 |
|
---|
| 113 | def testParseDeepError(self):
|
---|
| 114 | packson=ObjectMapper()
|
---|
| 115 | propsjson={'age': 10, 'name': 12}
|
---|
| 116 | try:
|
---|
| 117 | packson.parse(propsjson, Props)
|
---|
| 118 | raise AssertionError("parser did not throw")
|
---|
| 119 | except ValueError as e:
|
---|
| 120 | # we catch this to assure the exception contains
|
---|
| 121 | # both top error and details.
|
---|
| 122 | print("received error "+str(e))
|
---|
| 123 | self.assertTrue(str(e).find("Error parsing"))
|
---|
| 124 | self.assertTrue(str(e).find("ValueError"))
|
---|
| 125 | self.assertTrue(str(e).find("expected"))
|
---|
| 126 |
|
---|
| 127 |
|
---|
| 128 | def testEmpty(self):
|
---|
| 129 | packson=ObjectMapper()
|
---|
| 130 |
|
---|
| 131 | class EmptyClass:
|
---|
| 132 | def __init__(self):
|
---|
| 133 | pass
|
---|
| 134 | def __eq__(self, other):
|
---|
| 135 | return isinstance(other, self.__class__)
|
---|
| 136 |
|
---|
| 137 | obj=EmptyClass()
|
---|
| 138 | print(packson.toJson(obj))
|
---|
| 139 | res=packson.parse({}, EmptyClass)
|
---|
| 140 | self.assertEqual(obj, res)
|
---|
| 141 |
|
---|
| 142 | def testSubType(self):
|
---|
| 143 | packson=ObjectMapper()
|
---|
| 144 |
|
---|
| 145 | class Cat():
|
---|
| 146 | def __init__(self, props:Props):
|
---|
| 147 | self._props=props
|
---|
| 148 |
|
---|
| 149 | def __str__(self):
|
---|
| 150 | return "Cat["+str(self._props)+"]"
|
---|
| 151 |
|
---|
| 152 | def getprops(self):
|
---|
| 153 | return self._props
|
---|
| 154 |
|
---|
| 155 | obj=Cat(Props(1,'bruno'))
|
---|
| 156 | print(packson.toJson(obj))
|
---|
| 157 |
|
---|
| 158 |
|
---|
| 159 | bson={'props':{'age':1, 'name':'bruno'}}
|
---|
| 160 | res=packson.parse(bson, Cat)
|
---|
| 161 | print(res, type(res))
|
---|
| 162 | self.assertEquals(type(res.getprops()), Props)
|
---|
| 163 |
|
---|
| 164 |
|
---|
| 165 |
|
---|
| 166 | def testInheritance(self):
|
---|
| 167 | packson=ObjectMapper()
|
---|
| 168 |
|
---|
| 169 |
|
---|
| 170 | obj=Bear(Props(1,'bruno'))
|
---|
| 171 | res=packson.toJson(obj)
|
---|
| 172 | print("result:"+str(res))
|
---|
| 173 | bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}}
|
---|
| 174 | self.assertEquals(bson, res)
|
---|
| 175 |
|
---|
| 176 | res=packson.parse(bson, Animal)
|
---|
| 177 | print("Deserialized an Animal! -->"+str(res))
|
---|
| 178 | self. assertEqual(obj, res)
|
---|
| 179 |
|
---|
| 180 | def testUntypedList(self):
|
---|
| 181 | class Prim:
|
---|
| 182 | def __init__(self, a:list):
|
---|
| 183 | self._a=a
|
---|
| 184 | def geta(self)->list:
|
---|
| 185 | return self._a
|
---|
| 186 |
|
---|
| 187 | packson=ObjectMapper()
|
---|
| 188 | obj=Prim([1,2])
|
---|
| 189 | objson = {'a':[1,2]}
|
---|
| 190 |
|
---|
| 191 | self.assertEqual(objson, packson.toJson(obj))
|
---|
| 192 |
|
---|
| 193 | self.assertRaises(ValueError, lambda:packson.parse(objson, Prim))
|
---|
| 194 |
|
---|
| 195 | def testTypedList(self):
|
---|
| 196 | '''
|
---|
| 197 | deserializes typed list contained in another object
|
---|
| 198 | '''
|
---|
| 199 | class Prim:
|
---|
| 200 | def __init__(self, a:List[str]):
|
---|
| 201 | self._a=a
|
---|
| 202 | def geta(self)->List[str]:
|
---|
| 203 | return self._a
|
---|
| 204 | def __eq__(self, other):
|
---|
| 205 | return isinstance(other, self.__class__) and \
|
---|
| 206 | self._a==other._a
|
---|
| 207 |
|
---|
| 208 | packson=ObjectMapper()
|
---|
| 209 | obj=Prim(["x","y"])
|
---|
| 210 | objson = {'a':["x","y"]}
|
---|
| 211 |
|
---|
| 212 | self.assertEqual(objson, packson.toJson(obj))
|
---|
| 213 | self.assertEqual(obj, packson.parse(objson, Prim))
|
---|
| 214 |
|
---|
| 215 | def testTypedListDirect(self):
|
---|
| 216 | '''
|
---|
| 217 | deserializes typed list directly
|
---|
| 218 | '''
|
---|
| 219 |
|
---|
| 220 | packson=ObjectMapper()
|
---|
| 221 | obj=["x","y"]
|
---|
| 222 | objson = ["x","y"]
|
---|
| 223 |
|
---|
| 224 | self.assertEqual(objson, packson.toJson(obj))
|
---|
| 225 | self.assertEqual(obj, packson.parse(objson, List[str]))
|
---|
| 226 |
|
---|
| 227 | def testTypedListOfObjMissingAnnotation(self):
|
---|
| 228 | class Prim:
|
---|
| 229 | def __init__(self, a:int):
|
---|
| 230 | self._a=a
|
---|
| 231 | def geta(self)->int:
|
---|
| 232 | return self._a
|
---|
| 233 | def __eq__(self, other):
|
---|
| 234 | return isinstance(other, self.__class__) and \
|
---|
| 235 | self._a==other._a
|
---|
| 236 | packson=ObjectMapper()
|
---|
| 237 | obj=[Prim(1),Prim(3)]
|
---|
| 238 | objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
|
---|
| 239 | self.assertRaises(ValueError, lambda:packson.toJson(obj))
|
---|
| 240 | # object misses annotation, therefore this will try to parse
|
---|
| 241 | # Prim objects without header here.
|
---|
| 242 | self.assertRaises(ValueError, lambda:packson.parse(objson, List[Prim]))
|
---|
| 243 |
|
---|
| 244 | def testTypedListOfObj(self):
|
---|
| 245 | @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
|
---|
| 246 | class Prim:
|
---|
| 247 | def __init__(self, a:int):
|
---|
| 248 | self._a=a
|
---|
| 249 | def geta(self)->int:
|
---|
| 250 | return self._a
|
---|
| 251 | def __eq__(self, other):
|
---|
| 252 | return isinstance(other, self.__class__) and \
|
---|
| 253 | self._a==other._a
|
---|
| 254 |
|
---|
| 255 | packson=ObjectMapper()
|
---|
| 256 | obj=[Prim(1),Prim(3)]
|
---|
| 257 | objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}]
|
---|
| 258 | self.assertEqual(objson, packson.toJson(obj))
|
---|
| 259 | self.assertEqual(obj, packson.parse(objson, List[Prim]))
|
---|
| 260 |
|
---|
| 261 | def testTypedSetOfObj(self):
|
---|
| 262 | @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
|
---|
| 263 | class Prim:
|
---|
| 264 | def __init__(self, a:int):
|
---|
| 265 | self._a=a
|
---|
| 266 | def geta(self)->int:
|
---|
| 267 | return self._a
|
---|
| 268 | def __eq__(self, other):
|
---|
| 269 | return isinstance(other, self.__class__) and \
|
---|
| 270 | self._a==other._a
|
---|
| 271 |
|
---|
| 272 | packson=ObjectMapper()
|
---|
| 273 | obj=set([SimpleWithHash(1),SimpleWithHash(3)])
|
---|
| 274 | objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}]
|
---|
| 275 | self.assertEqual(objson, packson.toJson(obj))
|
---|
| 276 | parsedobj=packson.parse(objson, Set[SimpleWithHash])
|
---|
| 277 | self.assertEqual(obj, parsedobj)
|
---|
| 278 |
|
---|
| 279 |
|
---|
| 280 | def testExpectListButGiveDict(self):
|
---|
| 281 | @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT)
|
---|
| 282 | class Prim:
|
---|
| 283 | def __init__(self, a:int):
|
---|
| 284 | self._a=a
|
---|
| 285 | def geta(self)->int:
|
---|
| 286 | return self._a
|
---|
| 287 | def __eq__(self, other):
|
---|
| 288 | return isinstance(other, self.__class__) and \
|
---|
| 289 | self._a==other._a
|
---|
| 290 |
|
---|
| 291 | packson=ObjectMapper()
|
---|
| 292 | objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}}
|
---|
| 293 | # we request List but obj is a dict.
|
---|
| 294 | self.assertRaises(ValueError,lambda:packson.parse(objson, List[Prim]))
|
---|
| 295 |
|
---|
| 296 | def testSerializeDict(self):
|
---|
| 297 |
|
---|
| 298 | packson=ObjectMapper()
|
---|
| 299 | obj={'a':Simple(1),'c':Simple(3)}
|
---|
| 300 | objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
|
---|
| 301 | self.assertEqual(objson, packson.toJson(obj))
|
---|
| 302 |
|
---|
| 303 |
|
---|
| 304 | def testTypedDictOfObj(self):
|
---|
| 305 | packson=ObjectMapper()
|
---|
| 306 | obj={'a':Simple(1),'c':Simple(3)}
|
---|
| 307 | objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}}
|
---|
| 308 | self.assertEqual(obj, packson.parse(objson, Dict[str,Simple]))
|
---|
| 309 | print("deserialized obj"+str(objson)+"="+str(obj))
|
---|
| 310 |
|
---|
| 311 | def testTypedDictSimpleKey(self):
|
---|
| 312 | packson=ObjectMapper()
|
---|
| 313 |
|
---|
| 314 | key=mydict()
|
---|
| 315 | key["Simple"]={'a':1}
|
---|
| 316 | # simple is not hashable
|
---|
| 317 | objson = { key : 'a' }
|
---|
| 318 | self.assertRaises(ValueError,lambda:packson.parse(objson, Dict[Simple,str]))
|
---|
| 319 |
|
---|
| 320 | def testTypedDictSimpleKeyHashable(self):
|
---|
| 321 | packson=ObjectMapper()
|
---|
| 322 | # key is now not primitive!
|
---|
| 323 | obj={SimpleWithHash(1):'a'}
|
---|
| 324 |
|
---|
| 325 | # simple is not hashable
|
---|
| 326 | key=mydict()
|
---|
| 327 | key["SimpleWithHash"]={'a':1}
|
---|
| 328 | # simple is not hashable
|
---|
| 329 | objson = { key : 'a' }
|
---|
| 330 | self.assertEqual(obj, packson.parse(objson, Dict[SimpleWithHash,str]))
|
---|
| 331 |
|
---|
| 332 | |
---|