diff --git a/serializable/__init__.py b/serializable/__init__.py index 79ef88b..93741e9 100644 --- a/serializable/__init__.py +++ b/serializable/__init__.py @@ -195,7 +195,8 @@ def default(self, o: Any) -> Any: if prop_info.custom_type: if prop_info.is_helper_type(): - v = prop_info.custom_type.json_serialize(v) + v = prop_info.custom_type.json_normalize( + v, view=self._view, prop_info=prop_info, ctx=o.__class__) else: v = prop_info.custom_type(v) elif prop_info.is_array: @@ -298,17 +299,15 @@ def from_json(cls: Type[_T], data: Dict[str, Any]) -> Optional[_T]: try: if prop_info.custom_type: if prop_info.is_helper_type(): - _data[k] = prop_info.custom_type.json_deserialize(v) + _data[k] = prop_info.custom_type.json_denormalize( + v, prop_info=prop_info, ctx=klass) else: _data[k] = prop_info.custom_type(v) elif prop_info.is_array: items = [] for j in v: if not prop_info.is_primitive_type() and not prop_info.is_enum: - try: - items.append(prop_info.concrete_type.from_json(data=j)) - except AttributeError as e: - raise e + items.append(prop_info.concrete_type.from_json(data=j)) else: items.append(prop_info.concrete_type(j)) _data[k] = items # type: ignore @@ -438,7 +437,14 @@ def as_xml(self: Any, view_: Optional[Type[ViewType]] = None, SubElement(nested_e, nested_key).text = str(j) elif prop_info.custom_type: if prop_info.is_helper_type(): - SubElement(this_e, new_key).text = str(prop_info.custom_type.xml_serialize(v)) + v_ser = prop_info.custom_type.xml_normalize( + v, view=view_, element_name=new_key, xmlns=xmlns, prop_info=prop_info, ctx=self.__class__) + if v_ser is None: + pass # skip the element + elif isinstance(v_ser, Element): + this_e.append(v_ser) + else: + SubElement(this_e, new_key).text = str(v_ser) else: SubElement(this_e, new_key).text = str(prop_info.custom_type(v)) elif prop_info.is_enum: @@ -583,14 +589,16 @@ def strip_default_namespace(s: str) -> str: ) elif prop_info.custom_type: if prop_info.is_helper_type(): - _data[decoded_k] = prop_info.custom_type.xml_deserialize(child_e) + _data[decoded_k] = prop_info.custom_type.xml_denormalize( + child_e, default_ns=default_namespace, prop_info=prop_info, ctx=klass) else: _data[decoded_k] = prop_info.custom_type(child_e.text) else: _data[decoded_k].append(prop_info.concrete_type(child_e.text)) elif prop_info.custom_type: if prop_info.is_helper_type(): - _data[decoded_k] = prop_info.custom_type.xml_deserialize(child_e.text) + _data[decoded_k] = prop_info.custom_type.xml_denormalize( + child_e, default_ns=default_namespace, prop_info=prop_info, ctx=klass) else: _data[decoded_k] = prop_info.custom_type(child_e.text) elif prop_info.is_enum: diff --git a/serializable/helpers.py b/serializable/helpers.py index fdd5c9f..4560587 100644 --- a/serializable/helpers.py +++ b/serializable/helpers.py @@ -16,16 +16,24 @@ # # SPDX-License-Identifier: Apache-2.0 # Copyright (c) Paul Horton. All Rights Reserved. + import re import warnings from datetime import date, datetime -from typing import Any +from typing import TYPE_CHECKING, Any, Optional, Type, TypeVar, Union + +if TYPE_CHECKING: # pragma: no cover + from xml.etree.ElementTree import Element + + from . import ObjectMetadataLibrary, ViewType + +_T = TypeVar('_T') class BaseHelper: """Base Helper. - Inherit from this class and implement the needed functions! + Inherit from this class and implement/override the needed functions! This class does not provide any functionality, it is more like a Protocol with some fallback implementations. @@ -34,7 +42,7 @@ class BaseHelper: # region general/fallback @classmethod - def serialize(cls, o: Any) -> Any: + def serialize(cls, o: Any) -> Union[Any, str]: """general purpose serializer""" raise NotImplementedError() @@ -48,10 +56,31 @@ def deserialize(cls, o: Any) -> Any: # region json specific @classmethod - def json_serialize(cls, o: Any) -> Any: + def json_normalize(cls, o: Any, *, + view: Optional[Type['ViewType']], + prop_info: 'ObjectMetadataLibrary.SerializableProperty', + ctx: Type[Any], + **kwargs: Any) -> Optional[Any]: + """json specific normalizer""" + return cls.json_serialize(o) + + @classmethod + def json_serialize(cls, o: Any) -> Union[str, Any]: """json specific serializer""" return cls.serialize(o) + @classmethod + def json_denormalize(cls, o: Any, *, + prop_info: 'ObjectMetadataLibrary.SerializableProperty', + ctx: Type[Any], + **kwargs: Any) -> Any: + """json specific denormalizer + + :param tCls: the class that was desired to denormalize to + :param pCls: tha prent class - as context + """ + return cls.json_deserialize(o) + @classmethod def json_deserialize(cls, o: Any) -> Any: """json specific deserializer""" @@ -62,12 +91,32 @@ def json_deserialize(cls, o: Any) -> Any: # endregion xml specific @classmethod - def xml_serialize(cls, o: Any) -> Any: + def xml_normalize(cls, o: Any, *, + element_name: str, + view: Optional[Type['ViewType']], + xmlns: Optional[str], + prop_info: 'ObjectMetadataLibrary.SerializableProperty', + ctx: Type[Any], + **kwargs: Any) -> Optional[Union['Element', Any]]: + """xml specific normalizer""" + return cls.xml_serialize(o) + + @classmethod + def xml_serialize(cls, o: Any) -> Union[str, Any]: """xml specific serializer""" return cls.serialize(o) @classmethod - def xml_deserialize(cls, o: Any) -> Any: + def xml_denormalize(cls, o: 'Element', *, + default_ns: Optional[str], + prop_info: 'ObjectMetadataLibrary.SerializableProperty', + ctx: Type[Any], + **kwargs: Any) -> Any: + """xml specific denormalizer""" + return cls.xml_deserialize(o.text) + + @classmethod + def xml_deserialize(cls, o: Union[str, Any]) -> Any: """xml specific deserializer""" return cls.deserialize(o)