Skip to content

Commit

Permalink
require explicit serializable type registration by default for Object…
Browse files Browse the repository at this point in the history
…Mapper
  • Loading branch information
keotl committed Jun 11, 2021
1 parent a9136ac commit d5dd7be
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def deserialize(self, obj, declared_type: Type[NamedTuple]) -> NamedTuple:
attributes = declared_type.__annotations__
parameters = Stream(attributes.items()) \
.map(lambda name, attribute_type:
(name, self.deserializer.deserialize(obj.get(name), attribute_type))) \
self.deserializer.deserialize(obj.get(name), attribute_type)) \
.toList()
elif isinstance(obj, list):
parameters = obj
Expand Down
5 changes: 3 additions & 2 deletions jivago/serialization/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
TypedDictionaryDeserializationStrategy
from jivago.serialization.deserialization.typed_list_deserialization_strategy import TypedListDeserializationStrategy
from jivago.serialization.deserialization.typed_tuple_deserialization_strategy import TypedTupleDeserializationStrategy
from jivago.serialization.serialization_exception import SerializationException
from jivago.serialization.serialization_exception import SerializationException, \
NoMatchingDeserializationStrategyException
from jivago.wsgi.invocation.incorrect_attribute_type_exception import IncorrectAttributeTypeException

T = TypeVar('T')
Expand Down Expand Up @@ -48,7 +49,7 @@ def deserialize(self, obj: dict, object_clazz: Type[T]) -> T:
try:
return Stream(self.deserialization_strategies). \
firstMatch(lambda s: s.can_handle_deserialization(object_clazz)) \
.orElseThrow(SerializationException) \
.orElseThrow(lambda: NoMatchingDeserializationStrategyException(object_clazz)) \
.deserialize(obj, object_clazz)
except (AttributeError, TypeError):
raise IncorrectAttributeTypeException()
7 changes: 4 additions & 3 deletions jivago/serialization/object_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@

class ObjectMapper(object):

def __init__(self):
self.deserializer = Deserializer(ObjectMapper.__RegistryStub())
def __init__(self, allow_unregistered_types: bool = False):
self.deserializer = Deserializer(
ObjectMapper.__RegistryStub() if allow_unregistered_types else Registry.INSTANCE)
self.serializer = Serializer()

def deserialize(self, json_str: str, clazz: Type[T]) -> T:
Expand All @@ -29,4 +30,4 @@ class __RegistryStub(Registry):

@Override
def is_annotated(self, object: object, annotation: "Annotation"):
return annotation == Serializable and object not in BUILTIN_TYPES + (dict,)
return annotation == Serializable and object not in BUILTIN_TYPES + (dict, list, tuple)
6 changes: 6 additions & 0 deletions jivago/serialization/serialization_exception.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
class SerializationException(Exception):
pass


class NoMatchingDeserializationStrategyException(SerializationException):
def __init__(self, clazz: type):
super().__init__(self,
f"Could not find an appropriate deserialization strategy for type {clazz}. Are you missing the '@Serializable' annotation?")
34 changes: 33 additions & 1 deletion test/serialization/test_dto_serialization_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from jivago.lang.annotations import Serializable
from jivago.lang.registry import Registry
from jivago.serialization.deserializer import Deserializer
from jivago.serialization.serialization_exception import SerializationException
from jivago.serialization.serialization_exception import SerializationException, \
NoMatchingDeserializationStrategyException
from jivago.serialization.serializer import Serializer
from jivago.wsgi.invocation.incorrect_attribute_type_exception import IncorrectAttributeTypeException

Expand Down Expand Up @@ -181,6 +182,8 @@ def test_givenNamedTuple_whenDeserializing_thenDeserializeFromDictionary(self):
result = self.serialization_handler.deserialize(serialized, ANamedTuple)

self.assertIsInstance(result, ANamedTuple)
self.assertEqual("paul atreides", result.name)
self.assertEqual(17, result.age)

def test_givenJsonList_whenDeserializingAsNamedTuple_thenDeserializeFromList(self):
serialized = ["paul atreides", 17]
Expand Down Expand Up @@ -227,6 +230,31 @@ def test_givenATypeDerivedFromABuiltInType_whenDeserializing_thenDeserializesAsB
self.assertEqual("my-name", result.name)
self.assertIsInstance(result.name, DerivedString)

def test_givenUntypedList_whenDeserializing_thenInstantiateBasicList(self):
given = [1, 2, 3]

result = self.serialization_handler.deserialize(given, list)

self.assertEqual(given, result)

def test_givenUntypedTuple_whenDeserializing_thenInstantiateBasicTuple(self):
given = [1, 2, 3]

result = self.serialization_handler.deserialize(given, tuple)

self.assertEqual((1, 2, 3), result)

def test_givenUntypedDictionary_whenDeserializing_thenInstantiateBasicDict(self):
given = {"key": "value"}

result = self.serialization_handler.deserialize(given, dict)

self.assertEqual(given, result)

def test_givenUnknownDeserializationStrategy_whenDeserializing_thenThrowExceptionWithHelpfulMessage(self):
with self.assertRaisesRegexp(NoMatchingDeserializationStrategyException, "UnknownType.*@Serializable"):
self.serialization_handler.deserialize({}, UnknownType)


@Serializable
class ADto(object):
Expand Down Expand Up @@ -292,6 +320,10 @@ class DtoWithDerivedStringMember(object):
name: DerivedString


class UnknownType(object):
pass


A_NESTED_DTO = ANestedDto()
A_NESTED_DTO.child_dto = ChildDto()
A_NESTED_DTO.child_dto.name = "a name"
23 changes: 21 additions & 2 deletions test/serialization/test_object_mapper.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import unittest

from jivago.lang.annotations import Serializable
from jivago.serialization.object_mapper import ObjectMapper
from jivago.serialization.serialization_exception import SerializationException


class ObjectMapperTest(unittest.TestCase):

def setUp(self):
self.object_mapper = ObjectMapper()

def test_givenUnregisteredDtoClass_whenDeserializing_thenIgnoreClassRegistrationStatus(self):
def test_givenUnregisteredType_whenDeserializing_thenThrowException(self):
with self.assertRaises(SerializationException):
self.object_mapper.deserialize('{ "name": "foo" }', A_Dto)

def test_givenRegisteredType_whenSerializing_thenDeserializeCorrectly(self):
dto = self.object_mapper.deserialize('{ "name": "foo" }', A_RegisteredDto)

self.assertEqual("foo", dto.name)

def test_givenAllowUnregisteredTypes_whenDeserializing_thenIgnoreClassRegistrationStatus(self):
self.object_mapper = ObjectMapper(allow_unregistered_types=True)

dto = self.object_mapper.deserialize('{ "name": "foo" }', A_Dto)

self.assertEqual("foo", dto.name)

def test_givenUnregisteredDtoClass_whenSerializing_thenIgnoreClassRegistrationStatus(self):
def test_givenAllowUnregisteredTypes_whenSerializing_thenIgnoreClassRegistrationStatus(self):
self.object_mapper = ObjectMapper(allow_unregistered_types=True)
dto = A_Dto()
dto.name = "foo"

Expand All @@ -24,3 +38,8 @@ def test_givenUnregisteredDtoClass_whenSerializing_thenIgnoreClassRegistrationSt

class A_Dto(object):
name: str


@Serializable
class A_RegisteredDto(object):
name: str

0 comments on commit d5dd7be

Please sign in to comment.