def test_get_class_name_without__name__(self): class Meta(type): __name__ = None class C(metaclass=Meta): pass self.assertEqual('C', get_class_name(C)) self.assertEqual('tests.test_common_impl.C', get_class_name(C, fully_qualified=True))
def test_get_class_name_without__name__(self): class Meta(type): __name__ = None class C(metaclass=Meta): pass self.assertEqual('C', get_class_name(C)) self.assertEqual('{}.C'.format(__name__), get_class_name(C, fully_qualified=True))
def _get_lizer(cls: type, lizers: Dict[str, callable], classes_lizers: list) -> callable: cls_name = get_class_name(cls, str.lower) lizer = lizers.get(cls_name, None) if not lizer: parents = get_parents(cls, classes_lizers) if parents: pname = get_class_name(parents[0], str.lower) lizer = lizers[pname] return lizer
def default_dict_serializer(obj: dict, cls: Optional[type] = None, *, strict: bool = False, strip_nulls: bool = False, key_transformer: Optional[Callable[[str], str]] = None, **kwargs) -> dict: """ Serialize the given ``obj`` to a dict of serialized objects. :param obj: the dict that is to be serialized. :param cls: the type of ``obj``; ``obj`` is dumped as if of that type. :param strict: if ``True`` the serialization will raise upon any the failure of any attribute. Otherwise it continues with a warning. :param strip_nulls: if ``True`` the resulting dict will not contain null values. :param key_transformer: a function that will be applied to all keys in the resulting dict. :param kwargs: any keyword arguments that may be given to the serialization process. :return: a dict of which all elements are serialized. """ result = dict() fork_inst = kwargs['fork_inst'] for key in obj: dumped_elem = None try: dumped_elem = dump(obj[key], key_transformer=key_transformer, strip_nulls=strip_nulls, **kwargs) _store_cls_info(dumped_elem, key, obj, **kwargs) except RecursionDetectedError: fork_inst._warn( 'Recursive structure detected in attribute "{}" ' 'of object of type "{}", ignoring the attribute.'.format( key, get_class_name(cls))) except SerializationError as err: if strict: raise else: fork_inst._warn('Failed to dump attribute "{}" of object of ' 'type "{}". Reason: {}. Ignoring the ' 'attribute.'.format(key, get_class_name(cls), err.message)) break if not (strip_nulls and dumped_elem is None): if key_transformer: key = key_transformer(key) result[key] = dumped_elem return result
def set_serializer(func: callable, cls: type, high_prio: bool = True, fork_inst: type = StateHolder) -> None: """ Set a serializer function for the given type. You may override the default behavior of ``jsons.load`` by setting a custom serializer. The ``func`` argument must take one argument (i.e. the object that is to be serialized) and also a ``kwargs`` parameter. For example: >>> def func(obj, **kwargs): ... return dict() You may ask additional arguments between ``cls`` and ``kwargs``. :param func: the serializer function. :param cls: the type this serializer can handle. :param high_prio: determines the order in which is looked for the callable. :param fork_inst: if given, it uses this fork of ``JsonSerializable``. :return: None. """ if cls: index = 0 if high_prio else len(fork_inst._classes_serializers) fork_inst._classes_serializers.insert(index, cls) cls_name = get_class_name(cls, fork_inst=fork_inst) fork_inst._serializers[cls_name.lower()] = func else: fork_inst._serializers['nonetype'] = func
def default_union_deserializer(obj: object, cls: Union, **kwargs) -> object: """ Deserialize an object to any matching type of the given union. The first successful deserialization is returned. :param obj: The object that needs deserializing. :param cls: The Union type with a generic (e.g. Union[str, int]). :param kwargs: Any keyword arguments that are passed through the deserialization process. :return: An object of the first type of the Union that could be deserialized successfully. """ for sub_type in get_union_params(cls): try: return load(obj, sub_type, **kwargs) except JsonsError: pass # Try the next one. else: fork_inst = kwargs['fork_inst'] args_msg = ', '.join([ get_class_name(cls_, fork_inst=fork_inst) for cls_ in get_union_params(cls) ]) err_msg = ('Could not match the object of type "{}" to any type of ' 'the Union: {}'.format(str(cls), args_msg)) raise DeserializationError(err_msg, obj, cls)
def fork( fork_inst: Type[T] = StateHolder, name: Optional[str] = None) -> Type[T]: """ Fork from the given ``StateHolder`` to create a separate "branch" of serializers and deserializers. :param fork_inst: The ``StateHolder`` on which the new fork is based. :param name: The ``__name__`` of the new ``type``. :return: A "fork inst" that can be used to separately store (de)serializers from the regular ``StateHolder``. """ fork_inst._fork_counter += 1 if name: class_name = name else: class_name = '{}_fork{}'.format( get_class_name(fork_inst), fork_inst._fork_counter ) result = type(class_name, (fork_inst,), {}) result._classes_serializers = fork_inst._classes_serializers.copy() result._classes_deserializers = fork_inst._classes_deserializers.copy() result._serializers = fork_inst._serializers.copy() result._deserializers = fork_inst._deserializers.copy() result._fork_counter = 0 return result
def set_deserializer( func: callable, cls: Union[type, Sequence[type]], high_prio: bool = True, fork_inst: type = StateHolder) -> None: """ Set a deserializer function for the given type. You may override the default behavior of ``jsons.dump`` by setting a custom deserializer. The ``func`` argument must take two arguments (i.e. the dict containing the serialized values and the type that the values should be deserialized into) and also a ``kwargs`` parameter. For example: >>> def func(dict_, cls, **kwargs): ... return cls() You may ask additional arguments between ``cls`` and ``kwargs``. :param func: the deserializer function. :param cls: the type or sequence of types this serializer can handle. :param high_prio: determines the order in which is looked for the callable. :param fork_inst: if given, it uses this fork of ``JsonSerializable``. :return: None. """ if isinstance(cls, Sequence): for cls_ in cls: set_deserializer(func, cls_, high_prio, fork_inst) elif cls: index = 0 if high_prio else len(fork_inst._classes_deserializers) fork_inst._classes_deserializers.insert(index, cls) cls_name = get_class_name(cls, fully_qualified=True) fork_inst._deserializers[cls_name.lower()] = func else: fork_inst._deserializers['nonetype'] = func
def default_union_serializer(obj: object, cls: Union, **kwargs) -> object: """ Serialize an object to any matching type of the given union. The first successful serialization is returned. :param obj: The object that is to be serialized. :param cls: The Union type with a generic (e.g. Union[str, int]). :param kwargs: Any keyword arguments that are passed through the serialization process. :return: An object of the first type of the Union that could be serialized successfully. """ sub_types = get_union_params(cls) # Cater for Optional[...]/Union[None, ...] first to avoid blindly # string-ifying None in later serializers. if obj is None and NoneType in sub_types: return obj for sub_type in sub_types: try: return dump(obj, sub_type, **kwargs) except JsonsError: pass # Try the next one. else: args_msg = ', '.join( [get_class_name(cls_) for cls_ in get_union_params(cls)]) err_msg = ('Could not match the object of type "{}" to any type of ' 'the Union: {}'.format(type(obj), args_msg)) raise SerializationError(err_msg)
def dump(obj: object, cls: Optional[type] = None, fork_inst: Optional[type] = _StateHolder, **kwargs) -> object: """ Serialize the given ``obj`` to a JSON equivalent type (e.g. dict, list, int, ...). The way objects are serialized can be finetuned by setting serializer functions for the specific type using ``set_serializer``. You can also provide ``cls`` to specify that ``obj`` needs to be serialized as if it was of type ``cls`` (meaning to only take into account attributes from ``cls``). The type ``cls`` must have a ``__slots__`` defined. Any type will do, but in most cases you may want ``cls`` to be a base class of ``obj``. :param obj: a Python instance of any sort. :param cls: if given, ``obj`` will be dumped as if it is of type ``type``. :param fork_inst: if given, it uses this fork of ``JsonSerializable``. :param kwargs: the keyword args are passed on to the serializer function. :return: the serialized obj as a JSON type. """ if cls and not hasattr(cls, '__slots__'): raise SerializationError('Invalid type: "{}". Only types that have a ' '__slots__ defined are allowed when ' 'providing "cls".'.format( get_class_name(cls))) cls_ = cls or obj.__class__ serializer = _get_serializer(cls_, fork_inst) kwargs_ = {'fork_inst': fork_inst, **kwargs} try: return serializer(obj, cls=cls, **kwargs_) except Exception as err: raise SerializationError(str(err))
def _check_and_get_cls_and_meta_hints( json_obj: object, cls: type, fork_inst: type, inferred_cls: bool) -> Tuple[type, Optional[dict]]: # Check if json_obj is of a valid type and return the cls. if type(json_obj) not in VALID_TYPES: invalid_type = get_class_name(type(json_obj), fully_qualified=True) valid_types = [ get_class_name(typ, fully_qualified=True) for typ in VALID_TYPES ] msg = ('Invalid type: "{}", only arguments of the following types are ' 'allowed: {}'.format(invalid_type, ", ".join(valid_types))) raise DeserializationError(msg, json_obj, cls) cls_from_meta, meta = get_cls_and_meta(json_obj, fork_inst) meta_hints = meta.get('classes', {}) if meta else {} return determine_precedence(cls, cls_from_meta, type(json_obj), inferred_cls), meta_hints
def _store_cls_info(result: object, original_obj: dict, kwargs): if kwargs.get('_store_cls', None) and isinstance(result, dict): cls = get_type(original_obj) if cls.__module__ == 'typing': cls_name = repr(cls) else: cls_name = get_class_name(cls, fully_qualified=True) result['-cls'] = cls_name
def _get_lizer_by_parents(cls: type, lizers: Dict[str, callable], classes_lizers: list, fork_inst: type) -> callable: result = None parents = _get_parents(cls, classes_lizers) if parents: pname = get_class_name(parents[0], str.lower, fully_qualified=True) result = lizers[pname] return result
def _check_for_none(json_obj: object, cls: type): # Check if the json_obj is None and whether or not that is fine. if json_obj is None and not can_match_with_none(cls): cls_name = get_class_name(cls).lower() raise DeserializationError( message='NoneType cannot be deserialized into {}'.format(cls_name), source=json_obj, target=cls)
def _do_serialize( obj: object, cls: type, attributes: Dict[str, Optional[type]], kwargs: dict, key_transformer: Optional[Callable[[str], str]] = None, strip_nulls: bool = False, strip_privates: bool = False, strip_properties: bool = False, strip_class_variables: bool = False, strip_attr: Union[str, MutableSequence[str], Tuple[str]] = None, strict: bool = False, fork_inst: Optional[type] = StateHolder) -> Dict[str, object]: result = dict() for attr_name, cls_ in attributes.items(): attr = obj.__getattribute__(attr_name) dumped_elem = None try: dumped_elem = dump(attr, cls=cls_, key_transformer=key_transformer, strip_nulls=strip_nulls, strip_privates=strip_privates, strip_properties=strip_properties, strip_class_variables=strip_class_variables, strip_attr=strip_attr, **kwargs) _store_cls_info(dumped_elem, attr, kwargs) except RecursionDetectedError: fork_inst._warn( 'Recursive structure detected in attribute "{}" ' 'of object of type "{}", ignoring the attribute.'.format( attr_name, get_class_name(cls))) except SerializationError as err: if strict: raise else: fork_inst._warn('Failed to dump attribute "{}" of object of ' 'type "{}". Reason: {}. Ignoring the ' 'attribute.'.format(attr, get_class_name(cls), err.message)) break _add_dumped_elem(result, attr_name, dumped_elem, strip_nulls, key_transformer) return result
def _store_cls_info(result: object, attr: str, original_obj: dict, **kwargs): if isinstance(result, dict) and kwargs.get('_store_cls'): cls = get_type(original_obj[attr]) if cls.__module__ == 'typing': cls_name = repr(cls) else: cls_name = get_class_name(cls, fully_qualified=True, fork_inst=kwargs['fork_inst']) result['-cls'] = cls_name
def default_object_serializer( obj: object, key_transformer: Optional[Callable[[str], str]] = None, strip_nulls: bool = False, strip_privates: bool = False, strip_properties: bool = False, strip_class_variables: bool = False, verbose: Union[Verbosity, bool] = False, **kwargs) -> dict: """ Serialize the given ``obj`` to a dict. All values within ``obj`` are also serialized. If ``key_transformer`` is given, it will be used to transform the casing (e.g. snake_case) to a different format (e.g. camelCase). :param obj: the object that is to be serialized. :param key_transformer: a function that will be applied to all keys in the resulting dict. :param strip_nulls: if ``True`` the resulting dict will not contain null values. :param strip_privates: if ``True`` the resulting dict will not contain private attributes (i.e. attributes that start with an underscore). :param strip_properties: if ``True`` the resulting dict will not contain values from @properties. :param strip_class_variables: if ``True`` the resulting dict will not contain values from class variables. :param verbose: if ``True`` the resulting dict will contain meta information (e.g. on how to deserialize). :param kwargs: any keyword arguments that are to be passed to the serializer functions. :return: a Python dict holding the values of ``obj``. """ if obj is None: return obj cls = kwargs['cls'] or obj.__class__ obj_dict = _get_dict_from_obj(obj, strip_privates, strip_properties, strip_class_variables, **kwargs) kwargs_ = {**kwargs, 'verbose': verbose} verbose = Verbosity.from_value(verbose) if Verbosity.WITH_CLASS_INFO in verbose: kwargs_['_store_cls'] = True result = default_dict_serializer( obj_dict, key_transformer=key_transformer, strip_nulls=strip_nulls, strip_privates=strip_privates, strip_properties=strip_properties, strip_class_variables=strip_class_variables, **kwargs_) cls_name = get_class_name(cls, fully_qualified=True, fork_inst=kwargs['fork_inst']) if kwargs.get('_store_cls'): result['-cls'] = cls_name else: result = _get_dict_with_meta(result, cls_name, verbose, kwargs['fork_inst']) return result
def _check_and_get_cls_and_meta_hints( json_obj: object, cls: type, fork_inst: type) -> Tuple[type, Optional[dict]]: # Check if json_obj is of a valid type and return the cls. if type(json_obj) not in VALID_TYPES: invalid_type = get_class_name(type(json_obj), fork_inst=fork_inst) valid_types = [get_class_name(typ, fork_inst=fork_inst) for typ in VALID_TYPES] msg = ('Invalid type: "{}", only arguments of the following types are ' 'allowed: {}'.format(invalid_type, ", ".join(valid_types))) raise DeserializationError(msg, json_obj, cls) if json_obj is None: raise DeserializationError('Cannot load None with strict=True', json_obj, cls) cls_from_meta, meta = _get_cls_and_meta(json_obj, fork_inst) meta_hints = meta.get('classes', {}) if meta else {} # Providing cls takes precedence over cls_from_meta. return (cls or cls_from_meta or type(json_obj)), meta_hints
def _get_lizer(cls: type, lizers: Dict[str, callable], classes_lizers: list, fork_inst: type, recursive: bool = False) -> callable: cls_name = get_class_name(cls, str.lower, fully_qualified=True) lizer = (lizers.get(cls_name, None) or _get_lizer_by_parents(cls, lizers, classes_lizers, fork_inst)) if not lizer and not recursive and hasattr(cls, '__supertype__'): return _get_lizer(cls.__supertype__, lizers, classes_lizers, fork_inst, True) return lizer
def _do_serialize( obj: object, cls: type, attributes: Dict[str, Optional[type]], kwargs: dict, key_transformer: Optional[Callable[[str], str]] = None, strip_nulls: bool = False, strip_privates: bool = False, strip_properties: bool = False, strip_class_variables: bool = False, strip_attr: Union[str, MutableSequence[str], Tuple[str]] = None, strict: bool = False, fork_inst: Optional[type] = StateHolder) -> Dict[str, object]: result = dict() is_attrs_cls = getattr(cls, '__attrs_attrs__', None) is not None make_attributes_public = is_attrs_cls and not strip_privates for attr_name, cls_ in attributes.items(): attr = getattr(obj, attr_name) attr_type = cls_ or type(attr) announce_class(attr_type, fork_inst=fork_inst) serializer = get_serializer(attr_type, fork_inst) try: dumped_elem = serializer( attr, cls=cls_, key_transformer=key_transformer, strip_nulls=strip_nulls, strip_privates=strip_privates, strip_properties=strip_properties, strip_class_variables=strip_class_variables, strip_attr=strip_attr, **kwargs) _store_cls_info(dumped_elem, attr, kwargs) except Exception as err: if strict: raise SerializationError(message=err.args[0]) from err else: fork_inst._warn( 'Failed to dump attribute "{}" of object of ' 'type "{}". Reason: {}. Ignoring the ' 'attribute.'.format(attr, get_class_name(cls), err.args[0]), 'attribute-not-serialized') break if make_attributes_public: attr_name = attr_name.lstrip('_') _add_dumped_elem(result, attr_name, dumped_elem, strip_nulls, key_transformer) return result
def announce_class(cls: type, cls_name: Optional[str] = None, fork_inst: type = StateHolder): """ Announce the given cls to jsons to allow jsons to deserialize a verbose dump into that class. :param cls: the class that is to be announced. :param cls_name: a custom name for that class. :param fork_inst: if given, it uses this fork of ``JsonSerializable``. :return: None. """ cls_name = cls_name or get_class_name(cls, fully_qualified=True) fork_inst._announced_classes[cls] = cls_name fork_inst._announced_classes[cls_name] = cls
def _get_remaining_args(obj: dict, cls: type, constructor_args: dict, strict: bool, fork_inst: type) -> dict: # Get the remaining args or raise if strict and the signature is unmatched. remaining_attrs = { attr_name: obj[attr_name] for attr_name in obj if attr_name not in constructor_args and attr_name != META_ATTR } if strict and remaining_attrs: unexpected_arg = list(remaining_attrs.keys())[0] err_msg = ('Type "{}" does not expect "{}"'.format( get_class_name(cls, fork_inst=fork_inst), unexpected_arg)) raise SignatureMismatchError(err_msg, unexpected_arg, obj, cls) return remaining_attrs
def fork(cls, name: Optional[str] = None) -> type: """ Create a 'fork' of ``JsonSerializable``: a new ``type`` with a separate configuration of serializers and deserializers. :param name: the ``__name__`` of the new ``type``. :return: a new ``type`` based on ``JsonSerializable``. """ cls._fork_counter += 1 class_name = name or '{}_fork{}'.format(get_class_name(cls), cls._fork_counter) result = type(class_name, (cls, ), {}) result._classes_serializers = cls._classes_serializers.copy() result._classes_deserializers = cls._classes_deserializers.copy() result._serializers = cls._serializers.copy() result._deserializers = cls._deserializers.copy() result._fork_counter = 0 return result
def set_validator(func: Callable[[type], bool], cls: Union[type, Sequence[type]], fork_inst: type = StateHolder) -> None: """ Set a validator function for the given ``cls``. The function should accept an instance of the type it should validate and must return ``False`` or raise any exception in case of a validation failure. :param func: the function that takes an instance of type ``cls`` and returns a bool (``True`` if the validation was successful). :param cls: the type or types that ``func`` is able to validate. :param fork_inst: if given, it uses this fork of ``JsonSerializable``. :return: None. """ if isinstance(cls, Sequence): for cls_ in cls: set_validator(func, cls_, fork_inst) cls_name = get_class_name(cls, fork_inst=fork_inst, fully_qualified=True) fork_inst._validators[cls_name.lower()] = func fork_inst._classes_validators.append(cls)
def _do_load(json_obj: object, deserializer: callable, cls: type, initial: bool, **kwargs): cls_name = get_class_name(cls, fully_qualified=True) if deserializer is None: raise DeserializationError( 'No deserializer for type "{}"'.format(cls_name), json_obj, cls) try: result = deserializer(json_obj, cls, **kwargs) validate(result, cls, kwargs['fork_inst']) except Exception as err: clear() if isinstance(err, JsonsError): raise message = 'Could not deserialize value "{}" into "{}". {}'.format( json_obj, cls_name, err) raise DeserializationError(message, json_obj, cls) else: if initial: # Clear all lru caches right before returning the initial call. clear() return result
def default_object_deserializer(obj: dict, cls: type, key_transformer: Optional[Callable[ [str], str]] = None, strict: bool = False, **kwargs) -> object: """ Deserialize ``obj`` into an instance of type ``cls``. If ``obj`` contains keys with a certain case style (e.g. camelCase) that do not match the style of ``cls`` (e.g. snake_case), a key_transformer should be used (e.g. KEY_TRANSFORMER_SNAKECASE). :param obj: a serialized instance of ``cls``. :param cls: the type to which ``obj`` should be deserialized. :param key_transformer: a function that transforms the keys in order to match the attribute names of ``cls``. :param strict: deserialize in strict mode. :param kwargs: any keyword arguments that may be passed to the deserializers. :return: an instance of type ``cls``. """ concat_kwargs = kwargs if key_transformer: obj = {key_transformer(key): obj[key] for key in obj} concat_kwargs = {**kwargs, 'key_transformer': key_transformer} concat_kwargs['strict'] = strict constructor_args = _get_constructor_args(obj, cls, **concat_kwargs) remaining_attrs = { attr_name: obj[attr_name] for attr_name in obj if attr_name not in constructor_args } if strict and remaining_attrs: unexpected_arg = list(remaining_attrs.keys())[0] err_msg = 'Type "{}" does not expect "{}"'.format( get_class_name(cls), unexpected_arg) raise SignatureMismatchError(err_msg, unexpected_arg, obj, cls) instance = cls(**constructor_args) _set_remaining_attrs(instance, remaining_attrs, **kwargs) return instance
def default_object_serializer(obj: object, cls: Optional[type] = None, *, key_transformer: Optional[Callable[[str], str]] = None, strip_nulls: bool = False, strip_privates: bool = False, strip_properties: bool = False, strip_class_variables: bool = False, strip_attr: Union[str, MutableSequence[str], Tuple[str]] = None, verbose: Union[Verbosity, bool] = False, strict: bool = False, fork_inst: Optional[type] = StateHolder, **kwargs) -> Optional[dict]: """ Serialize the given ``obj`` to a dict. All values within ``obj`` are also serialized. If ``key_transformer`` is given, it will be used to transform the casing (e.g. snake_case) to a different format (e.g. camelCase). :param obj: the object that is to be serialized. :param cls: the type of the object that is to be dumped. :param key_transformer: a function that will be applied to all keys in the resulting dict. :param strip_nulls: if ``True`` the resulting dict will not contain null values. :param strip_privates: if ``True`` the resulting dict will not contain private attributes (i.e. attributes that start with an underscore). :param strip_properties: if ``True`` the resulting dict will not contain values from @properties. :param strip_class_variables: if ``True`` the resulting dict will not contain values from class variables. :param strip_attr: can be a name or a collection of names of attributes that are not to be included in the dump. :param verbose: if ``True`` the resulting dict will contain meta information (e.g. on how to deserialize). :param strict: a bool to determine if the serializer should be strict (i.e. only dumping stuff that is known to ``cls``). :param fork_inst: if given, it uses this fork of ``JsonSerializable``. :param kwargs: any keyword arguments that are to be passed to the serializer functions. :return: a Python dict holding the values of ``obj``. """ strip_attr = _normalize_strip_attr(strip_attr) if cls and strict: attributes = _get_attributes_from_class(cls, strip_privates, strip_properties, strip_class_variables, strip_attr, strict) else: attributes = _get_attributes_from_object(obj, strip_privates, strip_properties, strip_class_variables, strip_attr, strict) cls = obj.__class__ verbose = Verbosity.from_value(verbose) kwargs_ = { **kwargs, 'fork_inst': fork_inst, 'verbose': verbose, 'strict': strict, # Set a flag in kwargs to temporarily store -cls: '_store_cls': Verbosity.WITH_CLASS_INFO in verbose } result = _do_serialize(obj=obj, cls=cls, attributes=attributes, kwargs=kwargs_, key_transformer=key_transformer, strip_nulls=strip_nulls, strip_privates=strip_privates, strip_properties=strip_properties, strip_class_variables=strip_class_variables, strip_attr=strip_attr, strict=strict, fork_inst=fork_inst) cls_name = get_class_name(cls, fully_qualified=True) if not kwargs.get('_store_cls'): result = _get_dict_with_meta(result, cls_name, verbose, fork_inst) return result
def load(json_obj: object, cls: Optional[type] = None, strict: bool = False, fork_inst: Optional[type] = _StateHolder, attr_getters: Optional[Dict[str, Callable[[], object]]] = None, **kwargs) -> object: """ Deserialize the given ``json_obj`` to an object of type ``cls``. If the contents of ``json_obj`` do not match the interface of ``cls``, a DeserializationError is raised. If ``json_obj`` contains a value that belongs to a custom class, there must be a type hint present for that value in ``cls`` to let this function know what type it should deserialize that value to. **Example**: >>> from typing import List >>> import jsons >>> class Person: ... # No type hint required for name ... def __init__(self, name): ... self.name = name >>> class Family: ... # Person is a custom class, use a type hint ... def __init__(self, persons: List[Person]): ... self.persons = persons >>> loaded = jsons.load({'persons': [{'name': 'John'}]}, Family) >>> loaded.persons[0].name 'John' If no ``cls`` is given, a dict is simply returned, but contained values (e.g. serialized ``datetime`` values) are still deserialized. If `strict` mode is off and the type of `json_obj` exactly matches `cls` then `json_obj` is simply returned. :param json_obj: the dict that is to be deserialized. :param cls: a matching class of which an instance should be returned. :param strict: a bool to determine if the deserializer should be strict (i.e. fail on a partially deserialized `json_obj` or on `None`). :param fork_inst: if given, it uses this fork of ``JsonSerializable``. :param attr_getters: a ``dict`` that may hold callables that return values for certain attributes. :param kwargs: the keyword args are passed on to the deserializer function. :return: an instance of ``cls`` if given, a dict otherwise. """ if not strict and (json_obj is None or type(json_obj) == cls): return json_obj if type(json_obj) not in VALID_TYPES: raise DeserializationError( 'Invalid type: "{}", only arguments of the following types are ' 'allowed: {}'.format( get_class_name(type(json_obj)), ", ".join(get_class_name(typ) for typ in VALID_TYPES)), json_obj, cls) if json_obj is None: raise DeserializationError('Cannot load None with strict=True', json_obj, cls) cls = cls or type(json_obj) deserializer = _get_deserializer(cls, fork_inst) kwargs_ = { 'strict': strict, 'fork_inst': fork_inst, 'attr_getters': attr_getters, **kwargs } try: return deserializer(json_obj, cls, **kwargs_) except Exception as err: if isinstance(err, JsonsError): raise raise DeserializationError(str(err), json_obj, cls)
def test_get_class_name_of_none(self): self.assertEqual('NoneType', get_class_name(None))
def default_object_serializer(obj: object, cls: Optional[type] = None, *, key_transformer: Optional[Callable[[str], str]] = None, strip_nulls: bool = False, strip_privates: bool = False, strip_properties: bool = False, strip_class_variables: bool = False, strip_attr: Union[str, MutableSequence[str], Tuple[str]] = None, verbose: Union[Verbosity, bool] = False, **kwargs) -> dict: """ Serialize the given ``obj`` to a dict. All values within ``obj`` are also serialized. If ``key_transformer`` is given, it will be used to transform the casing (e.g. snake_case) to a different format (e.g. camelCase). :param obj: the object that is to be serialized. :param cls: the type of the object that is to be dumped. :param key_transformer: a function that will be applied to all keys in the resulting dict. :param strip_nulls: if ``True`` the resulting dict will not contain null values. :param strip_privates: if ``True`` the resulting dict will not contain private attributes (i.e. attributes that start with an underscore). :param strip_properties: if ``True`` the resulting dict will not contain values from @properties. :param strip_class_variables: if ``True`` the resulting dict will not contain values from class variables. :param strip_attr: can be a name or a collection of names of attributes that are not to be included in the dump. dict will not contain attributes with :param verbose: if ``True`` the resulting dict will contain meta information (e.g. on how to deserialize). :param kwargs: any keyword arguments that are to be passed to the serializer functions. :return: a Python dict holding the values of ``obj``. """ if obj is None: return obj strip_attr = strip_attr or [] if (not isinstance(strip_attr, MutableSequence) and not isinstance(strip_attr, tuple)): strip_attr = (strip_attr, ) cls = cls or obj.__class__ obj_dict = _get_dict_from_obj(obj, cls, strip_privates, strip_properties, strip_class_variables, strip_attr, **kwargs) kwargs_ = {**kwargs, 'verbose': verbose} verbose = Verbosity.from_value(verbose) if Verbosity.WITH_CLASS_INFO in verbose: # Set a flag in kwargs to temporarily store -cls. kwargs_['_store_cls'] = True result = default_dict_serializer( obj_dict, cls, key_transformer=key_transformer, strip_nulls=strip_nulls, strip_privates=strip_privates, strip_properties=strip_properties, strip_class_variables=strip_class_variables, strip_attr=strip_attr, **kwargs_) cls_name = get_class_name(cls, fully_qualified=True, fork_inst=kwargs['fork_inst']) if not kwargs.get('_store_cls'): result = _get_dict_with_meta(result, cls_name, verbose, kwargs['fork_inst']) return result