import re import unittest import sys, traceback from pyson.ObjectMapper import ObjectMapper from pyson.JsonSubTypes import JsonSubTypes from pyson.JsonTypeInfo import JsonTypeInfo from pyson.JsonTypeInfo import Id,As from typing import Dict,List,Set import json class Props: ''' compound class with properties, used for testing ''' def __init__(self, age:int, name:str): if age<0: raise ValueError("age must be >0, got "+str(age)) self._age=age self._name=name; def __str__(self): return self._name+","+str(self._age) def getage(self): return self._age def getname(self): return self._name def __eq__(self, other): return isinstance(other, self.__class__) and \ self._name==other._name and self._age==other._age @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) class Simple: def __init__(self, a:int): self._a=a def geta(self)->int: return self._a def __eq__(self, other): return isinstance(other, self.__class__) and \ self._a==other._a def __str__(self): return self._name+","+str(self._a) class SimpleWithHash(Simple): def __hash__(self): return hash(self.geta()) # define abstract root class # These need to be reachable globally for reference @JsonSubTypes(["test.ObjectMapperTest.Bear"]) @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) class Animal: pass class Bear(Animal): def __init__(self, props:Props): self._props=props def __str__(self): return "Bear["+str(self._props)+"]" def getprops(self): return self._props def __eq__(self, other): return isinstance(other, self.__class__) and \ self._props==other._props # A wrongly configured type, you must add jsonsubtypes here. @JsonSubTypes(["test.ObjectMapperTest.BadSubclass"]) class BadSuperclassMissingTypeInfo: pass class BadSubclass(BadSuperclassMissingTypeInfo): def __init__(self, a:int): self._a=a def geta(self)->int: return self._a def __eq__(self, other): return isinstance(other, self.__class__) and \ self._a==other._a def __str__(self): return self._name+","+str(self._a) #module instead of class. @JsonSubTypes(["test.ObjectMapperTest"]) @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) class BadSuperclassModuleInstead: pass # our parser supports non-primitive keys but python dict dont. # therefore the following fails even before we can start testing our code... # we need to create a hashable dict to get around ths class mydict(dict): def __hash__(self, *args, **kwargs): return 1 class ObjectMapperTest(unittest.TestCase): def testPrimitives(self): pyson=ObjectMapper() res=pyson.parse(3, int) self.assertEquals(3, res) # this throws correct, self.assertRaises(ValueError, lambda:pyson.parse(3, str)) # this throws correct, self.assertRaises(ValueError, lambda:pyson.parse("ja", int)) #DEMO with nested classes of different types. res=pyson.parse('three', str) print(res, type(res)) pyson.parse(3.0, float) pyson.parse(3.1, float) pyson.parse(3j, complex) pyson.parse(range(6), range) pyson.parse(True, bool) pyson.parse(False, bool) pyson.parse(b"Hello", bytes) pyson.parse(bytearray(b'\x00\x00\x00\x01'), bytearray) def testProps(self): pyson=ObjectMapper() propsjson={'age': 10, 'name': 'pietje'} props=Props(10, "pietje") self.assertEquals(propsjson,pyson.toJson(props)) self.assertEquals(props, pyson.parse(propsjson, Props)) def testParseDeepError(self): pyson=ObjectMapper() propsjson={'age': 10, 'name': 12} try: pyson.parse(propsjson, Props) raise AssertionError("parser did not throw") except ValueError as e: # we catch this to assure the exception contains # both top error and details. print("received error "+str(e)) self.assertTrue(str(e).find("Error parsing")) self.assertTrue(str(e).find("ValueError")) self.assertTrue(str(e).find("expected")) def testEmpty(self): pyson=ObjectMapper() class EmptyClass: def __init__(self): pass def __eq__(self, other): return isinstance(other, self.__class__) obj=EmptyClass() print(pyson.toJson(obj)) res=pyson.parse({}, EmptyClass) self.assertEqual(obj, res) def testSubType(self): pyson=ObjectMapper() class Cat(): def __init__(self, props:Props): self._props=props def __str__(self): return "Cat["+str(self._props)+"]" def getprops(self): return self._props obj=Cat(Props(1,'bruno')) print(pyson.toJson(obj)) bson={'props':{'age':1, 'name':'bruno'}} res=pyson.parse(bson, Cat) print(res, type(res)) self.assertEquals(type(res.getprops()), Props) def testInheritance(self): pyson=ObjectMapper() obj=Bear(Props(1,'bruno')) res=pyson.toJson(obj) print("result:"+str(res)) bson={'Bear': {'props': {'age': 1, 'name': 'bruno'}}} self.assertEquals(bson, res) res=pyson.parse(bson, Animal) print("Deserialized an Animal! -->"+str(res)) self. assertEqual(obj, res) def testUntypedList(self): class Prim: def __init__(self, a:list): self._a=a def geta(self)->list: return self._a pyson=ObjectMapper() obj=Prim([1,2]) objson = {'a':[1,2]} self.assertEqual(objson, pyson.toJson(obj)) self.assertRaises(ValueError, lambda:pyson.parse(objson, Prim)) def testTypedList(self): ''' deserializes typed list contained in another object ''' class Prim: def __init__(self, a:List[str]): self._a=a def geta(self)->List[str]: return self._a def __eq__(self, other): return isinstance(other, self.__class__) and \ self._a==other._a pyson=ObjectMapper() obj=Prim(["x","y"]) objson = {'a':["x","y"]} self.assertEqual(objson, pyson.toJson(obj)) self.assertEqual(obj, pyson.parse(objson, Prim)) def testTypedListDirect(self): ''' deserializes typed list directly ''' pyson=ObjectMapper() obj=["x","y"] objson = ["x","y"] self.assertEqual(objson, pyson.toJson(obj)) self.assertEqual(obj, pyson.parse(objson, List[str])) def testTypedListOfObjMissingAnnotation(self): class Prim: def __init__(self, a:int): self._a=a def geta(self)->int: return self._a def __eq__(self, other): return isinstance(other, self.__class__) and \ self._a==other._a pyson=ObjectMapper() obj=[Prim(1),Prim(3)] objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}] self.assertRaises(ValueError, lambda:pyson.toJson(obj)) # object misses annotation, therefore this will try to parse # Prim objects without header here. self.assertRaises(ValueError, lambda:pyson.parse(objson, List[Prim])) def testTypedListOfObj(self): @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) class Prim: def __init__(self, a:int): self._a=a def geta(self)->int: return self._a def __eq__(self, other): return isinstance(other, self.__class__) and \ self._a==other._a pyson=ObjectMapper() obj=[Prim(1),Prim(3)] objson = [{"Prim":{'a':1}},{"Prim":{'a':3}}] self.assertEqual(objson, pyson.toJson(obj)) self.assertEqual(obj, pyson.parse(objson, List[Prim])) def testTypedSetOfObj(self): @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) class Prim: def __init__(self, a:int): self._a=a def geta(self)->int: return self._a def __eq__(self, other): return isinstance(other, self.__class__) and \ self._a==other._a pyson=ObjectMapper() obj=set([SimpleWithHash(1),SimpleWithHash(3)]) objson = [{"SimpleWithHash":{'a':1}},{"SimpleWithHash":{'a':3}}] self.assertEqual(objson, pyson.toJson(obj)) parsedobj=pyson.parse(objson, Set[SimpleWithHash]) self.assertEqual(obj, parsedobj) def testExpectListButGiveDict(self): @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) class Prim: def __init__(self, a:int): self._a=a def geta(self)->int: return self._a def __eq__(self, other): return isinstance(other, self.__class__) and \ self._a==other._a pyson=ObjectMapper() objson = { 'a':{"Prim":{'a':1}},'c':{"Prim":{'a':3}}} # we request List but obj is a dict. self.assertRaises(ValueError,lambda:pyson.parse(objson, List[Prim])) def testSerializeDict(self): pyson=ObjectMapper() obj={'a':Simple(1),'c':Simple(3)} objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}} self.assertEqual(objson, pyson.toJson(obj)) def testTypedDictOfObj(self): pyson=ObjectMapper() obj={'a':Simple(1),'c':Simple(3)} objson = { 'a':{"Simple":{'a':1}},'c':{"Simple":{'a':3}}} self.assertEqual(obj, pyson.parse(objson, Dict[str,Simple])) print("deserialized obj"+str(objson)+"="+str(obj)) def testTypedDictSimpleKey(self): pyson=ObjectMapper() key=mydict() key["Simple"]={'a':1} # simple is not hashable objson = { key : 'a' } self.assertRaises(ValueError,lambda:pyson.parse(objson, Dict[Simple,str])) def testTypedDictSimpleKeyHashable(self): pyson=ObjectMapper() # key is now not primitive! obj={SimpleWithHash(1):'a'} # simple is not hashable key=mydict() key["SimpleWithHash"]={'a':1} # simple is not hashable objson = { key : 'a' } self.assertEqual(obj, pyson.parse(objson, Dict[SimpleWithHash,str])) def testDeserializeBadSubclass(self): pyson=ObjectMapper() objson= { 'BadSubclass':{ 'a':1}} # FIXME the error message is poor in this case. self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassMissingTypeInfo)) def testModuleInsteadOfClassAsSubclasses(self): pyson=ObjectMapper() objson= { 'BadSubclass':{ 'a':1}} self.assertRaises(ValueError,lambda:pyson.parse(objson, BadSuperclassModuleInstead))