Serializing objects

Typefit also comes with the ability to serialize objects into a JSON-encodable structure. While there is no strict warranty that serialize() is the strict inverse function of typefit() function, it is engineered so that the delta should be minimal by default and that you have a way to get to the result you want.

Usage

The default behavior tries to be sane and straightforward. It is calibrated so that if you can deserialize things using typefit() then they should look the same when going back through serialize().

from typefit import serialize
from typing import NamedTuple

class Foo(NamedTuple):
    x: int
    y: int

assert serialize(Foo(1, 2)) == {"x": 1, "y": 2}

Choosing your serializer

Of course, it’s not always so simple. That’s why typefit lets you choose your serializer and gives you the opportunity to customize it.

  • Serializer is the basic serializer that will only do 100% safe operations. However, it could be a bit limited for types who have no clear conversion between JSON and Python, typically like dates.

  • SaneSerializer adds some sane defaults to the mix to help you serialize dates and UUIDs without worrying too much.

  • You can inherit from either of those if you want to create your own serializer. In that case you’ll probably want to override the find_serializer method in your subclass to obtain the desired behavior.

The serialize() shortcut will use the SaneSerializer class.

Local behavior

If you want one specific object to serialize in a different way than what the serializer has in mind, you can add a __typefit_serialize__() method to it and this method will be called in stead of the serializer’s method.

By example:

from typing import NamedTuple
from typefit import serialize

class IntString(int):
    def __typefit_serialize__(self):
        return f"{self}"

class Foo(NamedTuple):
    x: IntString

foo = Foo(IntString(42))

assert serialize(foo) == {"x": "42"}

Reference

Narrows are data types that integrate some form of parsing to generate Python objects from more generic types like strings. All those classes accept exactly one argument to their constructor which is the data structure to convert.

class typefit.serialize.SaneSerializer

Opinionated version of what sane default for non-JSON-standard types should be. Comes as an extension of Serializer.

  • Dates are serialized to the ISO format

  • UUIDs are serialized into their default str() representation

find_serializer(obj: Any)

Tries to find special cases and if none of them are matched then resort to the parent method.

serialize_std_date(obj: datetime.date)

Dates are converted to ISO format

serialize_std_datetime(obj: datetime.datetime)

Datetime are converted into ISO format

serialize_uuid(obj: uuid.UUID)

UUIDs are simply converted to strings

class typefit.serialize.Serializer

Base serializer, that has no opinion and will serialize anything that is 100% to serialize without making any assumption.

Supported types are:

  • Numbers (int, float)

  • Booleans

  • Strings

  • Sequences

  • Mappings

  • Named (and typed) tuples

  • Dataclasses (including with Typefit metadata in the field)

  • Any object with a __typefit_serialize__() method

  • Enums

You’ll notice that the behavior of this class is a best effort to make something sane and simple. This means there is no warranty that this works:

>>> base: SomeData = # Some data
>>> serialized = serialize(base)
>>> assert typefit(SomeData, serialized) == base

If you want more types to be recognized by this serializer, you can inherit from it and extend the find_serializer() method.

If you don’t know where to look, check out the following methods:

See also

SaneSerializer

find_serializer(obj: Any)

Trying to be as generic as possible. There is a few tricks there, like strings which are also sequences so the order of tests matters quite a lot.

Please override this if you want to change the behavior. See how it’s done in SaneSerializer for an idea on how to do it.

json(obj: Any)str

Shortcut to transform an object into a JSON string going through serialize().

serialize(obj: Any)

Transforms a given object into a structure of basic types which can easily be serialized to JSON. It is not a strict inverse of typefit() but it should be good enough for most uses.

Please note that this at least assumes that objects are consistent with their type declarations, no additional security is put in place.

This method relies on the find_serializer() method, which means that if you implement a subclass in order to change the mapping of serialization functions you should override find_serializer().

Parameters

obj – Object to be serialized

serialize_dataclass(obj: Any)

Dataclasses are mappings but they merit a special attention due to the fact that their fields are not necessarily the fields that will be used in the output, thanks to the meta(source=X) feature.

Notes

See Source, but basically the conversion to JSON structure generates a series of dictionaries that are then superposed into a single dictionary and returned.

All values of this dictionary are of course recursively serialized.

serialize_enum(obj: enum.Enum)

Enums are serialized into their value.

serialize_generic(obj: Any)Any

By default, leave the object untouched

serialize_mapping(obj: collections.abc.Mapping)

Mappings are just copied into another mapping. While copying, all the values are recursively serialized.

serialize_sequence(obj: collections.abc.Sequence)

Sequences are converted to regular lists, and each item of the list is recursively serialized.

serialize_tuple(obj: tuple)

Named tuples are expected to have typing annotations, we’ll use that as a reference to get the fields list, however types are not enforced.

serialize_typefit(obj: Any)

Serializes an object by calling its __typefit_serialize__() method.

typefit.serialize.serialize(obj: Any)Any

Shortcut to use the SaneSerializer’s serialize() method.

Parameters

obj – Object to be serializer