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
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
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
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
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)