Example #1
0
def _load_hashed_keys(obj: dict, cls: type, cls_args: tuple,
                      **kwargs) -> Tuple[dict, bool]:
    # Load any hashed keys and return a copy of the given obj if any hashed
    # keys are unpacked.
    result = obj

    stored_keys = set(obj.get('-keys', set()))
    if stored_keys:
        # Apparently, there are stored hashed keys, we need to unpack them.
        if len(cls_args) != 2:
            raise DeserializationError(
                'A detailed type is needed for cls of '
                'the form Dict[<type>, <type>] to '
                'deserialize a dict with hashed keys.', obj, cls)
        result = {**obj}
        key_type = cls_args[0]
        for key in stored_keys:
            # Get the original (unhashed) key and load it.
            original_key = result['-keys'][key]
            loaded_key = load(original_key, cls=key_type, **kwargs)

            # Replace the hashed key by the loaded key entirely.
            result[loaded_key] = result[key]
            del result['-keys'][key]
            del result[key]

        del result['-keys']
    return result, len(stored_keys) > 0
Example #2
0
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 loadb(bytes_: bytes,
          cls: Optional[type] = None,
          encoding: str = 'utf-8',
          jdkwargs: Optional[Dict[str, object]] = None,
          *args,
          **kwargs) -> object:
    """
    Extend ``json.loads``, allowing bytes to be loaded into a dict or a Python
    instance of type ``cls``. Any extra (keyword) arguments are passed on to
    ``json.loads``.

    :param bytes_: the bytes that are to be loaded.
    :param cls: a matching class of which an instance should be returned.
    :param encoding: the encoding that is used to transform from bytes.
    :param jdkwargs: extra keyword arguments for ``json.loads`` (not
    ``jsons.loads``!)
    :param args: extra arguments for ``jsons.loads``.
    :param kwargs: extra keyword arguments for ``jsons.loads``.
    :return: a JSON-type object (dict, str, list, etc.) or an instance of type
    ``cls`` if given.
    """
    if not isinstance(bytes_, bytes):
        raise DeserializationError('loadb accepts bytes only, "{}" was given'
                                   .format(type(bytes_)), bytes_, cls)
    jdkwargs = jdkwargs or {}
    str_ = bytes_.decode(encoding=encoding)
    return loads(str_, cls, jdkwargs=jdkwargs, *args, **kwargs)
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 _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
Example #6
0
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_nonetype_deserializer(obj: object,
                                  cls: Optional[type] = None,
                                  **kwargs) -> object:
    """
    Deserialize a ``NoneType``.
    :param obj: the value that is to be deserialized.
    :param cls: not used.
    :param kwargs: not used.
    :return: ``obj``.
    """
    if obj is not None:
        raise DeserializationError('Cannot deserialize {} as NoneType'
                                   .format(obj), source=obj, target=cls)
    return obj
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),
                                      fork_inst=fork_inst,
                                      fully_qualified=True)
        valid_types = [
            get_class_name(typ, fork_inst=fork_inst, 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)
    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 {}
    return determine_precedence(cls, cls_from_meta, type(json_obj),
                                inferred_cls), meta_hints
Example #9
0
def _do_load(json_obj: object, deserializer: callable, cls: type,
             initial: bool, **kwargs):
    try:
        result = deserializer(json_obj, cls, **kwargs)
        validate(result, cls, kwargs['fork_inst'])
    except Exception as err:
        clear()
        if isinstance(err, JsonsError):
            raise
        raise DeserializationError(str(err), json_obj, cls)
    else:
        if initial:
            # Clear all lru caches right before returning the initial call.
            clear()
        return result
Example #10
0
def _do_load(obj: list, cls: type, warn_on_fail: bool,
             fork_inst: Type[StateHolder], kwargs) -> list:
    result = []
    for index, elem in enumerate(obj):
        try:
            result.append(load(elem, cls=cls, tasks=1, **kwargs))
        except DeserializationError as err:
            new_msg = ('Could not deserialize element at index %s. %s' %
                       (index, err.message))
            if warn_on_fail:
                fork_inst._warn(new_msg)
            else:
                new_err = DeserializationError(new_msg, err.source, err.target)
                raise new_err from err

    return result
def default_primitive_deserializer(obj: object,
                                   cls: Optional[type] = None,
                                   **kwargs) -> object:
    """
    Deserialize a primitive: it simply returns the given primitive.
    :param obj: the value that is to be deserialized.
    :param cls: not used.
    :param kwargs: not used.
    :return: ``obj``.
    """
    result = obj
    if obj is not None and not isinstance(obj, cls):
        try:
            result = cls(obj)
        except ValueError:
            raise DeserializationError('Could not cast "{}" into "{}"'
                                       .format(obj, cls.__name__), obj, cls)
    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
    cls, meta_hints = _check_and_get_cls_and_meta_hints(json_obj, cls,
                                                        fork_inst)
    deserializer = _get_deserializer(cls, fork_inst)
    kwargs_ = {
        'strict': strict,
        'fork_inst': fork_inst,
        'attr_getters': attr_getters,
        'meta_hints': meta_hints,
        **kwargs
    }
    try:
        return deserializer(json_obj, cls, **kwargs_)
    except Exception as err:
        if isinstance(err, JsonsError):
            raise
        raise DeserializationError(str(err), json_obj, cls)