Defining serializers for preexisting types#
A key feature of scityping is that it allows not only to make your own types serializable (by subclassing scityping.Serializable), but also to associate serializers to preexisting types. This is especially useful to add support for types defined in external libraries, over which we do not have control.
Below we list different approaches for achieving this. The simplest approach is to subclass the preexisting type, and to reuse its name in for the subclass. Other approaches are more flexible but slightly more verbose.
Standard: By subclassing the type with the same name#
class Complex(Serializable, complex):
@dataclass
class Data:
real: float
imag: float
def encode(z): return z.real, z.imag
Scityping shorthands
You may notice that the encode method above has no self argument.
This is because scityping internally translates it to:
class Complex(Serializable, complex):
@dataclass
class Data:
real: float
imag: float
@classmethod
def encode(datacls, z): return datacls(z.real, z.imag)
Sometimes using the more explicit form with @classmethod is useful.
On the other hand, when the data types are very simple, reducing boilerplate
can improve readability.
Note also that it would make no sense to pass self to encode, since the Data
object is not yet initialized. If it helps, you can think of encode as a
combination of __new__ and __init__ methods.
By subclassing the type with a different name#
Manually update type registries
class NPGenerator(Serializable, np.random.Generator):
@dataclass
class Data:
state: dict
def encode(rng): return rng.bit_generator.state
def decode(data):
bg = getattr(np.random, data.state["bit_generator"])()
bg.state = data.state
return np.random.Generator(bg)
NPGenerator.register(np.random.Generator)
For types which don’t allow subclassing: register against ABC#
Use
@ABCSerializable.registerDefine
decodeso the correct type is returned.
@ABCSerializable.register
class Range(Serializable):
@dataclass
class Data:
start: int
stop: Optional[int]
step: Optional[int]
def encode(r): return start, stop, step
def decode(data): return range(*data)
Registering the same serializer for multiple types#
import numpy as np
class NPType(Serializable, np.generic):
@dataclass
class Data:
nptype: str
value: Union[float, int, str]
def encode(val): return type(val).__name__, val.item()
def decode(data): return getattr(np, data.nptype)(data.value)
for type_name in ('int8', 'int16', 'int32', 'int64',
'float16', 'float32', 'float64', 'float128',
'complex64', 'complex128', 'complex256',
'str_'):
NPType.register(getattr(np, type_name))