docs for muutils v0.6.17
View Source on GitHub

# muutils.json_serialize.serializable_field

extends `dataclasses.Field` for use with `SerializableDataclass`

In particular, instead of using `dataclasses.field`, use `serializable_field` to define fields in a `SerializableDataclass`. You provide information on how the field should be serialized and loaded (as well as anything that goes into `dataclasses.field`) when you define the field, and the `SerializableDataclass` will automatically use those functions.

View Source
View on GitHub

      1"""extends `dataclasses.Field` for use with `SerializableDataclass`
      2
      3In particular, instead of using `dataclasses.field`, use `serializable_field` to define fields in a `SerializableDataclass`.
      4You provide information on how the field should be serialized and loaded (as well as anything that goes into `dataclasses.field`)
      5when you define the field, and the `SerializableDataclass` will automatically use those functions.
      6
      7"""
      8
      9from __future__ import annotations
     10
     11import dataclasses
     12import sys
     13import types
     14from typing import Any, Callable, Optional, Union, overload, TypeVar
     15
     16
     17# pylint: disable=bad-mcs-classmethod-argument, too-many-arguments, protected-access
     18
     19
     20class SerializableField(dataclasses.Field):
     21    """extension of `dataclasses.Field` with additional serialization properties"""
     22
     23    __slots__ = (
     24        # from dataclasses.Field.__slots__
     25        "name",
     26        "type",
     27        "default",
     28        "default_factory",
     29        "repr",
     30        "hash",
     31        "init",
     32        "compare",
     33        "metadata",
     34        "kw_only",
     35        "_field_type",  # Private: not to be used by user code.
     36        # new ones
     37        "serialize",
     38        "serialization_fn",
     39        "loading_fn",
     40        "deserialize_fn",  # new alternative to loading_fn
     41        "assert_type",
     42        "custom_typecheck_fn",
     43    )
     44
     45    def __init__(
     46        self,
     47        default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
     48        default_factory: Union[
     49            Callable[[], Any], dataclasses._MISSING_TYPE
     50        ] = dataclasses.MISSING,
     51        init: bool = True,
     52        repr: bool = True,
     53        hash: Optional[bool] = None,
     54        compare: bool = True,
     55        # TODO: add field for custom comparator (such as serializing)
     56        metadata: Optional[types.MappingProxyType] = None,
     57        kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
     58        serialize: bool = True,
     59        serialization_fn: Optional[Callable[[Any], Any]] = None,
     60        loading_fn: Optional[Callable[[Any], Any]] = None,
     61        deserialize_fn: Optional[Callable[[Any], Any]] = None,
     62        assert_type: bool = True,
     63        custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
     64    ):
     65        # TODO: should we do this check, or assume the user knows what they are doing?
     66        if init and not serialize:
     67            raise ValueError("Cannot have init=True and serialize=False")
     68
     69        # need to assemble kwargs in this hacky way so as not to upset type checking
     70        super_kwargs: dict[str, Any] = dict(
     71            default=default,
     72            default_factory=default_factory,
     73            init=init,
     74            repr=repr,
     75            hash=hash,
     76            compare=compare,
     77            kw_only=kw_only,
     78        )
     79
     80        if metadata is not None:
     81            super_kwargs["metadata"] = metadata
     82        else:
     83            super_kwargs["metadata"] = types.MappingProxyType({})
     84
     85        # special check, kw_only is not supported in python <3.9 and `dataclasses.MISSING` is truthy
     86        if sys.version_info < (3, 10):
     87            if super_kwargs["kw_only"] == True:  # noqa: E712
     88                raise ValueError("kw_only is not supported in python >=3.9")
     89            else:
     90                del super_kwargs["kw_only"]
     91
     92        # actually init the super class
     93        super().__init__(**super_kwargs)  # type: ignore[call-arg]
     94
     95        # now init the new fields
     96        self.serialize: bool = serialize
     97        self.serialization_fn: Optional[Callable[[Any], Any]] = serialization_fn
     98
     99        if loading_fn is not None and deserialize_fn is not None:
    100            raise ValueError(
    101                "Cannot pass both loading_fn and deserialize_fn, pass only one. ",
    102                "`loading_fn` is the older interface and takes the dict of the class, ",
    103                "`deserialize_fn` is the new interface and takes only the field's value.",
    104            )
    105        self.loading_fn: Optional[Callable[[Any], Any]] = loading_fn
    106        self.deserialize_fn: Optional[Callable[[Any], Any]] = deserialize_fn
    107
    108        self.assert_type: bool = assert_type
    109        self.custom_typecheck_fn: Optional[Callable[[type], bool]] = custom_typecheck_fn
    110
    111    @classmethod
    112    def from_Field(cls, field: dataclasses.Field) -> "SerializableField":
    113        """copy all values from a `dataclasses.Field` to new `SerializableField`"""
    114        return cls(
    115            default=field.default,
    116            default_factory=field.default_factory,
    117            init=field.init,
    118            repr=field.repr,
    119            hash=field.hash,
    120            compare=field.compare,
    121            metadata=field.metadata,
    122            kw_only=getattr(field, "kw_only", dataclasses.MISSING),  # for python <3.9
    123            serialize=field.repr,  # serialize if it's going to be repr'd
    124            serialization_fn=None,
    125            loading_fn=None,
    126            deserialize_fn=None,
    127        )
    128
    129
    130Sfield_T = TypeVar("Sfield_T")
    131
    132
    133@overload
    134def serializable_field(
    135    *_args,
    136    default_factory: Callable[[], Sfield_T],
    137    default: dataclasses._MISSING_TYPE = dataclasses.MISSING,
    138    init: bool = True,
    139    repr: bool = True,
    140    hash: Optional[bool] = None,
    141    compare: bool = True,
    142    metadata: Optional[types.MappingProxyType] = None,
    143    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    144    serialize: bool = True,
    145    serialization_fn: Optional[Callable[[Any], Any]] = None,
    146    deserialize_fn: Optional[Callable[[Any], Any]] = None,
    147    assert_type: bool = True,
    148    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
    149    **kwargs: Any,
    150) -> Sfield_T: ...
    151@overload
    152def serializable_field(
    153    *_args,
    154    default: Sfield_T,
    155    default_factory: dataclasses._MISSING_TYPE = dataclasses.MISSING,
    156    init: bool = True,
    157    repr: bool = True,
    158    hash: Optional[bool] = None,
    159    compare: bool = True,
    160    metadata: Optional[types.MappingProxyType] = None,
    161    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    162    serialize: bool = True,
    163    serialization_fn: Optional[Callable[[Any], Any]] = None,
    164    deserialize_fn: Optional[Callable[[Any], Any]] = None,
    165    assert_type: bool = True,
    166    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
    167    **kwargs: Any,
    168) -> Sfield_T: ...
    169@overload
    170def serializable_field(
    171    *_args,
    172    default: dataclasses._MISSING_TYPE = dataclasses.MISSING,
    173    default_factory: dataclasses._MISSING_TYPE = dataclasses.MISSING,
    174    init: bool = True,
    175    repr: bool = True,
    176    hash: Optional[bool] = None,
    177    compare: bool = True,
    178    metadata: Optional[types.MappingProxyType] = None,
    179    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    180    serialize: bool = True,
    181    serialization_fn: Optional[Callable[[Any], Any]] = None,
    182    deserialize_fn: Optional[Callable[[Any], Any]] = None,
    183    assert_type: bool = True,
    184    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
    185    **kwargs: Any,
    186) -> Any: ...
    187def serializable_field(
    188    *_args,
    189    default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    190    default_factory: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    191    init: bool = True,
    192    repr: bool = True,
    193    hash: Optional[bool] = None,
    194    compare: bool = True,
    195    metadata: Optional[types.MappingProxyType] = None,
    196    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    197    serialize: bool = True,
    198    serialization_fn: Optional[Callable[[Any], Any]] = None,
    199    deserialize_fn: Optional[Callable[[Any], Any]] = None,
    200    assert_type: bool = True,
    201    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
    202    **kwargs: Any,
    203) -> Any:
    204    """Create a new `SerializableField`
    205
    206    ```
    207    default: Sfield_T | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    208    default_factory: Callable[[], Sfield_T]
    209    | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    210    init: bool = True,
    211    repr: bool = True,
    212    hash: Optional[bool] = None,
    213    compare: bool = True,
    214    metadata: types.MappingProxyType | None = None,
    215    kw_only: bool | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    216    # ----------------------------------------------------------------------
    217    # new in `SerializableField`, not in `dataclasses.Field`
    218    serialize: bool = True,
    219    serialization_fn: Optional[Callable[[Any], Any]] = None,
    220    loading_fn: Optional[Callable[[Any], Any]] = None,
    221    deserialize_fn: Optional[Callable[[Any], Any]] = None,
    222    assert_type: bool = True,
    223    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
    224    ```
    225
    226    # new Parameters:
    227    - `serialize`: whether to serialize this field when serializing the class'
    228    - `serialization_fn`: function taking the instance of the field and returning a serializable object. If not provided, will iterate through the `SerializerHandler`s defined in `muutils.json_serialize.json_serialize`
    229    - `loading_fn`: function taking the serialized object and returning the instance of the field. If not provided, will take object as-is.
    230    - `deserialize_fn`: new alternative to `loading_fn`. takes only the field's value, not the whole class. if both `loading_fn` and `deserialize_fn` are provided, an error will be raised.
    231    - `assert_type`: whether to assert the type of the field when loading. if `False`, will not check the type of the field.
    232    - `custom_typecheck_fn`: function taking the type of the field and returning whether the type itself is valid. if not provided, will use the default type checking.
    233
    234    # Gotchas:
    235    - `loading_fn` takes the dict of the **class**, not the field. if you wanted a `loading_fn` that does nothing, you'd write:
    236
    237    ```python
    238    class MyClass:
    239        my_field: int = serializable_field(
    240            serialization_fn=lambda x: str(x),
    241            loading_fn=lambda x["my_field"]: int(x)
    242        )
    243    ```
    244
    245    using `deserialize_fn` instead:
    246
    247    ```python
    248    class MyClass:
    249        my_field: int = serializable_field(
    250            serialization_fn=lambda x: str(x),
    251            deserialize_fn=lambda x: int(x)
    252        )
    253    ```
    254
    255    In the above code, `my_field` is an int but will be serialized as a string.
    256
    257    note that if not using ZANJ, and you have a class inside a container, you MUST provide
    258    `serialization_fn` and `loading_fn` to serialize and load the container.
    259    ZANJ will automatically do this for you.
    260
    261    # TODO: `custom_value_check_fn`: function taking the value of the field and returning whether the value itself is valid. if not provided, any value is valid as long as it passes the type test
    262    """
    263    assert len(_args) == 0, f"unexpected positional arguments: {_args}"
    264    return SerializableField(
    265        default=default,
    266        default_factory=default_factory,
    267        init=init,
    268        repr=repr,
    269        hash=hash,
    270        compare=compare,
    271        metadata=metadata,
    272        kw_only=kw_only,
    273        serialize=serialize,
    274        serialization_fn=serialization_fn,
    275        deserialize_fn=deserialize_fn,
    276        assert_type=assert_type,
    277        custom_typecheck_fn=custom_typecheck_fn,
    278        **kwargs,
    279    )

class SerializableField(dataclasses.Field):
View Source
View on GitHub

     21class SerializableField(dataclasses.Field):
     22    """extension of `dataclasses.Field` with additional serialization properties"""
     23
     24    __slots__ = (
     25        # from dataclasses.Field.__slots__
     26        "name",
     27        "type",
     28        "default",
     29        "default_factory",
     30        "repr",
     31        "hash",
     32        "init",
     33        "compare",
     34        "metadata",
     35        "kw_only",
     36        "_field_type",  # Private: not to be used by user code.
     37        # new ones
     38        "serialize",
     39        "serialization_fn",
     40        "loading_fn",
     41        "deserialize_fn",  # new alternative to loading_fn
     42        "assert_type",
     43        "custom_typecheck_fn",
     44    )
     45
     46    def __init__(
     47        self,
     48        default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
     49        default_factory: Union[
     50            Callable[[], Any], dataclasses._MISSING_TYPE
     51        ] = dataclasses.MISSING,
     52        init: bool = True,
     53        repr: bool = True,
     54        hash: Optional[bool] = None,
     55        compare: bool = True,
     56        # TODO: add field for custom comparator (such as serializing)
     57        metadata: Optional[types.MappingProxyType] = None,
     58        kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
     59        serialize: bool = True,
     60        serialization_fn: Optional[Callable[[Any], Any]] = None,
     61        loading_fn: Optional[Callable[[Any], Any]] = None,
     62        deserialize_fn: Optional[Callable[[Any], Any]] = None,
     63        assert_type: bool = True,
     64        custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
     65    ):
     66        # TODO: should we do this check, or assume the user knows what they are doing?
     67        if init and not serialize:
     68            raise ValueError("Cannot have init=True and serialize=False")
     69
     70        # need to assemble kwargs in this hacky way so as not to upset type checking
     71        super_kwargs: dict[str, Any] = dict(
     72            default=default,
     73            default_factory=default_factory,
     74            init=init,
     75            repr=repr,
     76            hash=hash,
     77            compare=compare,
     78            kw_only=kw_only,
     79        )
     80
     81        if metadata is not None:
     82            super_kwargs["metadata"] = metadata
     83        else:
     84            super_kwargs["metadata"] = types.MappingProxyType({})
     85
     86        # special check, kw_only is not supported in python <3.9 and `dataclasses.MISSING` is truthy
     87        if sys.version_info < (3, 10):
     88            if super_kwargs["kw_only"] == True:  # noqa: E712
     89                raise ValueError("kw_only is not supported in python >=3.9")
     90            else:
     91                del super_kwargs["kw_only"]
     92
     93        # actually init the super class
     94        super().__init__(**super_kwargs)  # type: ignore[call-arg]
     95
     96        # now init the new fields
     97        self.serialize: bool = serialize
     98        self.serialization_fn: Optional[Callable[[Any], Any]] = serialization_fn
     99
    100        if loading_fn is not None and deserialize_fn is not None:
    101            raise ValueError(
    102                "Cannot pass both loading_fn and deserialize_fn, pass only one. ",
    103                "`loading_fn` is the older interface and takes the dict of the class, ",
    104                "`deserialize_fn` is the new interface and takes only the field's value.",
    105            )
    106        self.loading_fn: Optional[Callable[[Any], Any]] = loading_fn
    107        self.deserialize_fn: Optional[Callable[[Any], Any]] = deserialize_fn
    108
    109        self.assert_type: bool = assert_type
    110        self.custom_typecheck_fn: Optional[Callable[[type], bool]] = custom_typecheck_fn
    111
    112    @classmethod
    113    def from_Field(cls, field: dataclasses.Field) -> "SerializableField":
    114        """copy all values from a `dataclasses.Field` to new `SerializableField`"""
    115        return cls(
    116            default=field.default,
    117            default_factory=field.default_factory,
    118            init=field.init,
    119            repr=field.repr,
    120            hash=field.hash,
    121            compare=field.compare,
    122            metadata=field.metadata,
    123            kw_only=getattr(field, "kw_only", dataclasses.MISSING),  # for python <3.9
    124            serialize=field.repr,  # serialize if it's going to be repr'd
    125            serialization_fn=None,
    126            loading_fn=None,
    127            deserialize_fn=None,
    128        )

extension of `dataclasses.Field` with additional serialization properties

SerializableField( default: Union[Any, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, default_factory: Union[Callable[[], Any], dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, init: bool = True, repr: bool = True, hash: Optional[bool] = None, compare: bool = True, metadata: Optional[mappingproxy] = None, kw_only: Union[bool, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, serialize: bool = True, serialization_fn: Optional[Callable[[Any], Any]] = None, loading_fn: Optional[Callable[[Any], Any]] = None, deserialize_fn: Optional[Callable[[Any], Any]] = None, assert_type: bool = True, custom_typecheck_fn: Optional[Callable[[<member 'type' of 'SerializableField' objects>], bool]] = None)
View Source
View on GitHub

     46    def __init__(
     47        self,
     48        default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
     49        default_factory: Union[
     50            Callable[[], Any], dataclasses._MISSING_TYPE
     51        ] = dataclasses.MISSING,
     52        init: bool = True,
     53        repr: bool = True,
     54        hash: Optional[bool] = None,
     55        compare: bool = True,
     56        # TODO: add field for custom comparator (such as serializing)
     57        metadata: Optional[types.MappingProxyType] = None,
     58        kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
     59        serialize: bool = True,
     60        serialization_fn: Optional[Callable[[Any], Any]] = None,
     61        loading_fn: Optional[Callable[[Any], Any]] = None,
     62        deserialize_fn: Optional[Callable[[Any], Any]] = None,
     63        assert_type: bool = True,
     64        custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
     65    ):
     66        # TODO: should we do this check, or assume the user knows what they are doing?
     67        if init and not serialize:
     68            raise ValueError("Cannot have init=True and serialize=False")
     69
     70        # need to assemble kwargs in this hacky way so as not to upset type checking
     71        super_kwargs: dict[str, Any] = dict(
     72            default=default,
     73            default_factory=default_factory,
     74            init=init,
     75            repr=repr,
     76            hash=hash,
     77            compare=compare,
     78            kw_only=kw_only,
     79        )
     80
     81        if metadata is not None:
     82            super_kwargs["metadata"] = metadata
     83        else:
     84            super_kwargs["metadata"] = types.MappingProxyType({})
     85
     86        # special check, kw_only is not supported in python <3.9 and `dataclasses.MISSING` is truthy
     87        if sys.version_info < (3, 10):
     88            if super_kwargs["kw_only"] == True:  # noqa: E712
     89                raise ValueError("kw_only is not supported in python >=3.9")
     90            else:
     91                del super_kwargs["kw_only"]
     92
     93        # actually init the super class
     94        super().__init__(**super_kwargs)  # type: ignore[call-arg]
     95
     96        # now init the new fields
     97        self.serialize: bool = serialize
     98        self.serialization_fn: Optional[Callable[[Any], Any]] = serialization_fn
     99
    100        if loading_fn is not None and deserialize_fn is not None:
    101            raise ValueError(
    102                "Cannot pass both loading_fn and deserialize_fn, pass only one. ",
    103                "`loading_fn` is the older interface and takes the dict of the class, ",
    104                "`deserialize_fn` is the new interface and takes only the field's value.",
    105            )
    106        self.loading_fn: Optional[Callable[[Any], Any]] = loading_fn
    107        self.deserialize_fn: Optional[Callable[[Any], Any]] = deserialize_fn
    108
    109        self.assert_type: bool = assert_type
    110        self.custom_typecheck_fn: Optional[Callable[[type], bool]] = custom_typecheck_fn

serialize: bool
serialization_fn: Optional[Callable[[Any], Any]]
loading_fn: Optional[Callable[[Any], Any]]
deserialize_fn: Optional[Callable[[Any], Any]]
assert_type: bool
custom_typecheck_fn: Optional[Callable[[<member 'type' of 'SerializableField' objects>], bool]]
@classmethod
def from_Field( cls, field: dataclasses.Field) -> SerializableField:
View Source
View on GitHub

    112    @classmethod
    113    def from_Field(cls, field: dataclasses.Field) -> "SerializableField":
    114        """copy all values from a `dataclasses.Field` to new `SerializableField`"""
    115        return cls(
    116            default=field.default,
    117            default_factory=field.default_factory,
    118            init=field.init,
    119            repr=field.repr,
    120            hash=field.hash,
    121            compare=field.compare,
    122            metadata=field.metadata,
    123            kw_only=getattr(field, "kw_only", dataclasses.MISSING),  # for python <3.9
    124            serialize=field.repr,  # serialize if it's going to be repr'd
    125            serialization_fn=None,
    126            loading_fn=None,
    127            deserialize_fn=None,
    128        )

copy all values from a `dataclasses.Field` to new `SerializableField`

name
type
default
default_factory
init
repr
hash
compare
metadata
kw_only
def serializable_field( *_args, default: Union[Any, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, default_factory: Union[Any, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, init: bool = True, repr: bool = True, hash: Optional[bool] = None, compare: bool = True, metadata: Optional[mappingproxy] = None, kw_only: Union[bool, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, serialize: bool = True, serialization_fn: Optional[Callable[[Any], Any]] = None, deserialize_fn: Optional[Callable[[Any], Any]] = None, assert_type: bool = True, custom_typecheck_fn: Optional[Callable[[type], bool]] = None, **kwargs: Any) -> Any:
View Source
View on GitHub

    188def serializable_field(
    189    *_args,
    190    default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    191    default_factory: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    192    init: bool = True,
    193    repr: bool = True,
    194    hash: Optional[bool] = None,
    195    compare: bool = True,
    196    metadata: Optional[types.MappingProxyType] = None,
    197    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
    198    serialize: bool = True,
    199    serialization_fn: Optional[Callable[[Any], Any]] = None,
    200    deserialize_fn: Optional[Callable[[Any], Any]] = None,
    201    assert_type: bool = True,
    202    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
    203    **kwargs: Any,
    204) -> Any:
    205    """Create a new `SerializableField`
    206
    207    ```
    208    default: Sfield_T | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    209    default_factory: Callable[[], Sfield_T]
    210    | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    211    init: bool = True,
    212    repr: bool = True,
    213    hash: Optional[bool] = None,
    214    compare: bool = True,
    215    metadata: types.MappingProxyType | None = None,
    216    kw_only: bool | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    217    # ----------------------------------------------------------------------
    218    # new in `SerializableField`, not in `dataclasses.Field`
    219    serialize: bool = True,
    220    serialization_fn: Optional[Callable[[Any], Any]] = None,
    221    loading_fn: Optional[Callable[[Any], Any]] = None,
    222    deserialize_fn: Optional[Callable[[Any], Any]] = None,
    223    assert_type: bool = True,
    224    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
    225    ```
    226
    227    # new Parameters:
    228    - `serialize`: whether to serialize this field when serializing the class'
    229    - `serialization_fn`: function taking the instance of the field and returning a serializable object. If not provided, will iterate through the `SerializerHandler`s defined in `muutils.json_serialize.json_serialize`
    230    - `loading_fn`: function taking the serialized object and returning the instance of the field. If not provided, will take object as-is.
    231    - `deserialize_fn`: new alternative to `loading_fn`. takes only the field's value, not the whole class. if both `loading_fn` and `deserialize_fn` are provided, an error will be raised.
    232    - `assert_type`: whether to assert the type of the field when loading. if `False`, will not check the type of the field.
    233    - `custom_typecheck_fn`: function taking the type of the field and returning whether the type itself is valid. if not provided, will use the default type checking.
    234
    235    # Gotchas:
    236    - `loading_fn` takes the dict of the **class**, not the field. if you wanted a `loading_fn` that does nothing, you'd write:
    237
    238    ```python
    239    class MyClass:
    240        my_field: int = serializable_field(
    241            serialization_fn=lambda x: str(x),
    242            loading_fn=lambda x["my_field"]: int(x)
    243        )
    244    ```
    245
    246    using `deserialize_fn` instead:
    247
    248    ```python
    249    class MyClass:
    250        my_field: int = serializable_field(
    251            serialization_fn=lambda x: str(x),
    252            deserialize_fn=lambda x: int(x)
    253        )
    254    ```
    255
    256    In the above code, `my_field` is an int but will be serialized as a string.
    257
    258    note that if not using ZANJ, and you have a class inside a container, you MUST provide
    259    `serialization_fn` and `loading_fn` to serialize and load the container.
    260    ZANJ will automatically do this for you.
    261
    262    # TODO: `custom_value_check_fn`: function taking the value of the field and returning whether the value itself is valid. if not provided, any value is valid as long as it passes the type test
    263    """
    264    assert len(_args) == 0, f"unexpected positional arguments: {_args}"
    265    return SerializableField(
    266        default=default,
    267        default_factory=default_factory,
    268        init=init,
    269        repr=repr,
    270        hash=hash,
    271        compare=compare,
    272        metadata=metadata,
    273        kw_only=kw_only,
    274        serialize=serialize,
    275        serialization_fn=serialization_fn,
    276        deserialize_fn=deserialize_fn,
    277        assert_type=assert_type,
    278        custom_typecheck_fn=custom_typecheck_fn,
    279        **kwargs,
    280    )

Create a new `SerializableField`

    default: Sfield_T | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    default_factory: Callable[[], Sfield_T]
    | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    init: bool = True,
    repr: bool = True,
    hash: Optional[bool] = None,
    compare: bool = True,
    metadata: types.MappingProxyType | None = None,
    kw_only: bool | dataclasses._MISSING_TYPE = dataclasses.MISSING,
    # ----------------------------------------------------------------------
    # new in `SerializableField`, not in `dataclasses.Field`
    serialize: bool = True,
    serialization_fn: Optional[Callable[[Any], Any]] = None,
    loading_fn: Optional[Callable[[Any], Any]] = None,
    deserialize_fn: Optional[Callable[[Any], Any]] = None,
    assert_type: bool = True,
    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,

# new Parameters:

- `serialize`: whether to serialize this field when serializing the class'
- `serialization_fn`: function taking the instance of the field and returning a serializable object. If not provided, will iterate through the `SerializerHandler`s defined in `muutils.json_serialize.json_serialize`
- `loading_fn`: function taking the serialized object and returning the instance of the field. If not provided, will take object as-is.
- `deserialize_fn`: new alternative to `loading_fn`. takes only the field's value, not the whole class. if both `loading_fn` and `deserialize_fn` are provided, an error will be raised.
- `assert_type`: whether to assert the type of the field when loading. if `False`, will not check the type of the field.
- `custom_typecheck_fn`: function taking the type of the field and returning whether the type itself is valid. if not provided, will use the default type checking.

# Gotchas:

- `loading_fn` takes the dict of the class, not the field. if you wanted a `loading_fn` that does nothing, you'd write:

    class MyClass:
        my_field: int = serializable_field(
            serialization_fn=lambda x: str(x),
            loading_fn=lambda x["my_field"]: int(x)
        )

using `deserialize_fn` instead:

    class MyClass:
        my_field: int = serializable_field(
            serialization_fn=lambda x: str(x),
            deserialize_fn=lambda x: int(x)
        )

In the above code, `my_field` is an int but will be serialized as a string.

note that if not using ZANJ, and you have a class inside a container, you MUST provide `serialization_fn` and `loading_fn` to serialize and load the container. ZANJ will automatically do this for you.

# TODO: `custom_value_check_fn`: function taking the value of the field and returning whether the value itself is valid. if not provided, any value is valid as long as it passes the type test