-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #459 from yukinarit/custom-global-serializer
Implement global (de)serializer
- Loading branch information
Showing
10 changed files
with
258 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# Extending pyserde | ||
|
||
pyserde offers three ways to extend pyserde to support non builtin types. | ||
|
||
## Custom field (de)serializer | ||
|
||
See [custom field serializer](./field-attributes.md#serializerdeserializer). | ||
|
||
> 💡 Tip: wrapping `serde.field` with your own field function makes | ||
> | ||
> ```python | ||
> import serde | ||
> | ||
> def field(*args, **kwargs): | ||
> serde.field(*args, **kwargs, serializer=str) | ||
> | ||
> @serde | ||
> class Foo: | ||
> a: int = field(default=0) # Configuring field serializer | ||
> ``` | ||
## Custom class (de)serializer | ||
See [custom class serializer](./class-attributes.md#class_serializer--class_deserializer). | ||
## Custom global (de)serializer | ||
You apply the custom (de)serialization for entire codebase by registering class (de)serializer by `add_serializer` and `add_deserializer`. Registered class (de)serializers are stacked in pyserde's global space and automatically used for all the pyserde classes. | ||
e.g. Implementing custom (de)serialization for `datetime.timedelta` using [isodate](https://pypi.org/project/isodate/) package. | ||
Here is the code of registering class (de)serializer for `datetime.timedelta`. | ||
```python | ||
from datetime import timedelta | ||
from plum import dispatch | ||
from typing import Type, Any | ||
import isodate | ||
import serde | ||
class Serializer: | ||
@dispatch | ||
def serialize(self, value: timedelta) -> Any: | ||
return isodate.duration_isoformat(value) | ||
class Deserializer: | ||
@dispatch | ||
def deserialize(self, cls: Type[timedelta], value: Any) -> timedelta: | ||
return isodate.parse_duration(value) | ||
def init() -> None: | ||
serde.add_serializer(Serializer()) | ||
serde.add_deserializer(Deserializer()) | ||
``` | ||
Users of this package can reuse custom (de)serialization functionality for `datetime.timedelta` just by calling `serde_timedelta.init()`. | ||
```python | ||
import serde_timedelta | ||
from serde import serde | ||
from serde.json import to_json, from_json | ||
from datetime import timedelta | ||
serde_timedelta.init() | ||
@serde | ||
class Foo: | ||
a: timedelta | ||
f = Foo(timedelta(hours=10)) | ||
json = to_json(f) | ||
print(json) | ||
print(from_json(Foo, json)) | ||
``` | ||
and you get `datetime.timedelta` to be serialized in ISO 8601 duration format! | ||
```bash | ||
{"a":"PT10H"} | ||
Foo(a=datetime.timedelta(seconds=36000)) | ||
``` | ||
> 💡 Tip: You can register as many class (de)serializer as you want. This means you can use as many pyserde extensions as you want. | ||
> Registered (de)serializers are stacked in the memory. A (de)serializer can be overridden by another (de)serializer. | ||
> | ||
> e.g. If you register 3 custom serializers in this order, the first serializer will completely overridden by the 3rd one. 2nd one works because it is implemented for a different type. | ||
> 1. Register Serializer for `int` | ||
> 2. Register Serializer for `float` | ||
> 3. Register Serializer for `int` | ||
New in v0.13.0. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from plum import dispatch | ||
from dataclasses import dataclass | ||
from datetime import datetime | ||
from serde import serde, add_serializer, add_deserializer | ||
from serde.json import from_json, to_json | ||
from typing import Type, Any | ||
|
||
|
||
class MySerializer: | ||
@dispatch | ||
def serialize(self, value: datetime) -> str: | ||
return value.strftime("%d/%m/%y") | ||
|
||
|
||
class MyDeserializer: | ||
@dispatch | ||
def deserialize(self, cls: Type[datetime], value: Any) -> datetime: | ||
return datetime.strptime(value, "%d/%m/%y") | ||
|
||
|
||
class MySerializer2: | ||
@dispatch | ||
def serialize(self, value: int) -> str: | ||
return str(value) | ||
|
||
|
||
class MyDeserializer2: | ||
@dispatch | ||
def deserialize(self, cls: Type[int], value: Any) -> int: | ||
return int(value) | ||
|
||
|
||
class MySerializer3: | ||
@dispatch | ||
def serialize(self, value: float) -> str: | ||
return str(value) | ||
|
||
|
||
class MyDeserializer3: | ||
@dispatch | ||
def deserialize(self, cls: Type[float], value: Any) -> float: | ||
return float(value) | ||
|
||
|
||
add_serializer(MySerializer()) | ||
add_serializer(MySerializer2()) | ||
add_deserializer(MyDeserializer()) | ||
add_deserializer(MyDeserializer2()) | ||
|
||
|
||
@serde(class_serializer=MySerializer3(), class_deserializer=MyDeserializer3()) | ||
@dataclass | ||
class Foo: | ||
a: datetime | ||
b: int | ||
c: float | ||
|
||
|
||
def main() -> None: | ||
dt = datetime(2021, 1, 1, 0, 0, 0) | ||
f = Foo(dt, 10, 100.0) | ||
print(f"Into Json: {to_json(f)}") | ||
|
||
s = '{"a": "01/01/21", "b": "10", "c": "100.0"}' | ||
print(f"From Json: {from_json(Foo, s)}") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.