Json dataclass mapper
Project description
jsondataclass
jsondataclass is a library that makes it easy for you to convert of dataclasses (PEP 557) to and from JSON.
Installation
pip3 install jsondataclass
Requirements
python>=3.7
Quick start
from dataclasses import dataclass
from jsondataclass import to_json, from_json
@dataclass
class Movie:
name: str
year: int
county: str
movie = Movie("Terminator: Dark Fate", 2019, "USA")
print(to_json(movie))
# {"name": "Terminator: Dark Fate", "year": 2019, "county": "USA"}
json_str = '{"name": "Terminator: Dark Fate", "year": 2019, "county": "USA"}'
print(from_json(json_str, Movie))
# Movie(name='Terminator: Dark Fate', year=2019, county='USA')
Supported types
str, int, bool, dict, list, tuple
nested dataclasses
typing.List
typing.Tuple
typing.Dict
typing.Optional
typing.Union
typing.Literal (python >= 3.8 required)
datetime.datetime
datetime.date
datetime.time
timestamp
Enum
Decimal
Usage
The primary functions to use are from_json and to_json. There is also a class DataClassMapper with methods from_json and to_json available that can be used to configure conversion operations.
The DataClassMapper instance does not maintain any state while invoking Json operations. So, you are free to reuse the same object for multiple Json serialization and deserialization operations.
Dataclass to JSON
from dataclasses import dataclass
from jsondataclass import to_json, DataClassMapper
@dataclass
class User:
id: int
name: str
user = User(1, "John Doe")
mapper = DataClassMapper()
json_str = mapper.to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe"}
# or
json_str = to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe"}
Dataclass from JSON
from dataclasses import dataclass
from jsondataclass import from_json, DataClassMapper
@dataclass
class User:
id: int
name: str
json_str = '{"id": 1, "name": "John Doe"}'
mapper = DataClassMapper()
user = mapper.from_json(json_str, User)
print(user)
# User(id=1, name='John Doe')
# or
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe')
Dataclass to dict
from dataclasses import dataclass
from jsondataclass import to_dict, DataClassMapper
@dataclass
class User:
id: int
name: str
user = User(1, "John Doe")
mapper = DataClassMapper()
dict_obj = mapper.to_dict(user)
print(dict_obj)
# {'id': 1, 'name': 'John Doe'}
# or
dict_obj = to_dict(user)
print(dict_obj)
# {'id': 1, 'name': 'John Doe'}
Dataclass from dict
from dataclasses import dataclass
from jsondataclass import to_dict, DataClassMapper
@dataclass
class User:
id: int
name: str
user = User(1, "John Doe")
mapper = DataClassMapper()
dict_obj = mapper.to_dict(user)
print(dict_obj)
# {'id': 1, 'name': 'John Doe'}
# or
dict_obj = to_dict(user)
print(dict_obj)
# {'id': 1, 'name': 'John Doe'}
Nested dataclass
from dataclasses import dataclass
from jsondataclass import from_json, to_json
@dataclass
class ContactInfo:
email: str
phone_number: str
@dataclass
class User:
id: int
name: str
info: ContactInfo
user = User(1, "John Doe", ContactInfo("john@doe.com", "+19999999"))
print(to_json(user))
# {"id": 1, "name": "John Doe", "info": {"email": "john@doe.com", "phone_number": "+19999999"}}
json_str = '{"id": 1, "name": "John Doe", "info": {"email": "john@doe.com", "phone_number": "+19999999"}}'
print(from_json(json_str, User))
# User(id=1, name='John Doe', info=ContactInfo(email='john@doe.com', phone_number='+19999999'))
Field serialized name
from dataclasses import dataclass
from jsondataclass import from_json, to_json, jsonfield
@dataclass
class User:
id: int = jsonfield("Id")
name: str = jsonfield("Name")
json_str = '{"Id": 1, "Name": "John Doe"}'
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe')
json_str = to_json(user)
print(json_str)
# {"Id": 1, "Name": "John Doe"}
Optional fields
from dataclasses import dataclass
from typing import Optional
from jsondataclass import from_json
@dataclass
class User:
id: int
name: str
email: Optional[str]
json_str = '{"id": 1, "name": "John Doe"}'
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe', email=None)
Unions
from dataclasses import dataclass
from typing import Union
from jsondataclass import from_json, to_json
@dataclass
class User:
id: Union[int, str]
name: str
json_str = '{"id": 1, "name": "John Doe"}'
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe')
json_str = to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe"}
Generic collections
from dataclasses import dataclass
from typing import List, Tuple, Dict
from jsondataclass import from_json, to_json
@dataclass
class Movie:
genres: List[str]
rating: Tuple[float, int]
name: Dict[str, str]
movie = Movie(["comedy", "crime"], (5.6, 100), {"en": "WALL-E", "de": "WALL-E"})
json_str = to_json(movie)
print(json_str)
# {"genres": ["comedy", "crime"], "rating": [5.6, 100], "name": {"en": "WALL-E", "de": "WALL-E"}}
json_str = '{"genres": ["comedy", "crime"], "rating": [5.6, 100], "name": {"en": "WALL-E", "de": "WALL-E"}}'
movie = from_json(json_str, Movie)
print(movie)
# Movie(genres=['comedy', 'crime'], rating=(5.6, 100), name={'en': 'WALL-E', 'de': 'WALL-E'})
Literals
from dataclasses import dataclass
from typing import Literal
from jsondataclass import from_json, to_json
@dataclass
class Movie:
name: str
year: int
rating: Literal[1, 2, 3, 4, 5]
movie = Movie("Terminator: Dark Fate", 2019, 5)
print(to_json(movie))
# > {"name": "Terminator: Dark Fate", "year": 2019, "rating": 5}
json_str = '{"name": "Terminator: Dark Fate", "year": 2019, "rating": 5}'
print(from_json(json_str, Movie))
# > Movie(name='Terminator: Dark Fate', year=2019, rating=5)
Enums
from dataclasses import dataclass
from enum import Enum
from jsondataclass import from_json, to_json
class Role(Enum):
ADMIN = 1
STAFF = 2
GUEST = 3
@dataclass
class User:
id: int
name: str
role: Role
user = User(1, "John Doe", Role.ADMIN)
json_str = to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe", "role": 1}
json_str = '{"id": 1, "name": "John Doe", "role": 1}'
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe', role=<Role.ADMIN: 1>)
Decimal
Decimal type can be decerialized from integer, float or string, but is serialized always to string.
from dataclasses import dataclass
from decimal import Decimal
from jsondataclass import from_json, to_json
@dataclass
class User:
id: int
name: str
salary: Decimal
user = User(1, "John Doe", Decimal("11.22"))
json_str = to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe", "salary": "11.22"}
json_str = '{"id": 1, "name": "John Doe", "salary": "11.22"}'
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe', salary=Decimal('11.22'))
datetime, date, time
Serialization of datetime, date and time objects are performed using isoformat(), and fromisoformat() are used for deserialization.
from dataclasses import dataclass
from datetime import datetime, date, time
from jsondataclass import from_json, to_json, DataClassMapper, jsonfield
@dataclass
class User:
id: int
name: str
last_login: datetime
birthday: date
local_time: time
user = User(1, "John Doe", datetime.now(), date(2000, 1, 1), time(0, 0, 0, 0))
json_str = to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe", "last_login": "2019-10-31T18:53:47.615534", "birthday": "2000-01-01", "local_time": "00:00:00"}
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe', last_login=datetime.datetime(2019, 10, 31, 18, 54, 35, 688288), birthday=datetime.date(2000, 1, 1), local_time=datetime.time(0, 0))
But you can specify format via DataClassMapper instance.
mapper = DataClassMapper()
mapper.datetime_format = "%m/%d/%y %H:%M:%S"
mapper.date_format = "%m/%d/%y"
mapper.time_format = "%H:%M"
user = User(1, "John Doe", datetime.now(), date(2000, 1, 1), time(0, 0, 0, 0))
json_str = mapper.to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe", "last_login": "10/31/19 18:59:11", "birthday": "01/01/00", "local_time": "00:00"}
user = mapper.from_json(json_str, User)
print(user)
# User(id=1, name='John Doe', last_login=datetime.datetime(2019, 10, 31, 18, 59, 11), birthday=datetime.date(2000, 1, 1), local_time=datetime.time(0, 0))
Or via jsonfield function.
@dataclass
class User:
id: int
name: str
last_login: datetime = jsonfield(serializer_args=("%y/%m/%d %H:%M:%S",))
birthday: date = jsonfield(serializer_args=("%y/%m/%d",))
local_time: time = jsonfield(serializer_args=("%I:%M %p",))
user = User(1, "John Doe", datetime.now(), date(2000, 1, 1), time(0, 0, 0, 0))
json_str = to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe", "last_login": "19/10/31 19:00:58", "birthday": "00/01/01", "local_time": "12:00 AM"}
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe', last_login=datetime.datetime(2019, 10, 31, 19, 0, 58), birthday=datetime.date(2000, 1, 1), local_time=datetime.time(0, 0))
Timestamp
By default, deserialization of timestamp result datetime naive object.
from dataclasses import dataclass
from datetime import datetime, timezone
from jsondataclass import from_json, to_json, jsonfield
@dataclass
class User:
id: int
name: str
last_login: datetime.timestamp
user = User(1, "John Doe", datetime.now())
json_str = to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe", "last_login": 1572541610}
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe', last_login=datetime.datetime(2019, 10, 31, 19, 7, 41))
You can specify timezone using jsonfield function.
@dataclass
class User:
id: int
name: str
last_login: datetime.timestamp = jsonfield(serializer_kwargs={"timezone": timezone.utc})
user = User(1, "John Doe", datetime.now())
json_str = to_json(user)
print(json_str)
# {"id": 1, "name": "John Doe", "last_login": 1572541956}
user = from_json(json_str, User)
print(user)
# User(id=1, name='John Doe', last_login=datetime.datetime(2019, 10, 31, 17, 12, 36, tzinfo=datetime.timezone.utc))
Forward References
from dataclasses import dataclass
from jsondataclass import from_json, to_json
from jsondataclass.utils import set_forward_refs
@dataclass
class User:
id: int
name: str
info: "ContactInfo"
@dataclass
class ContactInfo:
email: str
phone_number: str
set_forward_refs(User, {"ContactInfo": ContactInfo})
user = User(1, "John Doe", ContactInfo("john@doe.com", "+19999999"))
print(to_json(user))
# {"id": 1, "name": "John Doe", "info": {"email": "john@doe.com", "phone_number": "+19999999"}}
json_str = '{"id": 1, "name": "John Doe", "info": {"email": "john@doe.com", "phone_number": "+19999999"}}'
print(from_json(json_str, User))
# User(id=1, name='John Doe', info=ContactInfo(email='john@doe.com', phone_number='+19999999'))
Custom Serialization and Deserialization
Sometimes default representation is not what you want. DataClassMapper allows you to register your own custom serializers.
from dataclasses import dataclass
from typing import Type
from jsondataclass import DataClassMapper, jsonfield, to_json, from_json
from jsondataclass.serializers import Serializer
class Rating:
def __init__(self, rating, vote_count):
self.rating = rating
self.vote_count = vote_count
def __repr__(self):
return f"Rating(rating={self.rating}, vote_count={self.vote_count})"
class RatingSerializer(Serializer[list]):
def serialize(self, data: Rating) -> list:
return [data.rating, data.vote_count]
def deserialize(self, data: list, type_: Type[Rating]) -> Rating:
return Rating(*data)
@dataclass
class Movie:
name: str
year: int
rating: Rating
movie = Movie("Terminator: Dark Fate", 2019, Rating(5, 100))
mapper = DataClassMapper()
mapper.register_serializer(Rating, RatingSerializer)
json_str = mapper.to_json(movie)
print(json_str)
# {"name": "Terminator: Dark Fate", "year": 2019, "rating": [5, 100]}
json_str = '{"name": "Terminator: Dark Fate", "year": 2019, "rating": [5, 100]}'
movie = mapper.from_json(json_str, Movie)
print(movie)
# Movie(name='Terminator: Dark Fate', year=2019, rating=Rating(rating=5, vote_count=100))
Or you can set your serializer on per-field level.
@dataclass
class Movie:
name: str
year: int
rating: Rating = jsonfield(serializer_class=RatingSerializer)
movie = Movie("Terminator: Dark Fate", 2019, Rating(5, 100))
json_str = to_json(movie)
print(json_str)
# {"name": "Terminator: Dark Fate", "year": 2019, "rating": [5, 100]}
json_str = '{"name": "Terminator: Dark Fate", "year": 2019, "rating": [5, 100]}'
movie = from_json(json_str, Movie)
print(movie)
# Movie(name='Terminator: Dark Fate', year=2019, rating=Rating(rating=5, vote_count=100))
Also, you can override builtin serializers.
class UpperStringSerializer(Serializer[str]):
def serialize(self, data: str) -> str:
return data.upper()
def deserialize(self, data: str, type_: Type[str]) -> str:
return data.upper()
@dataclass
class Movie:
name: str
year: int
movie = Movie("Terminator: Dark Fate", 2019)
mapper = DataClassMapper()
mapper.register_serializer(str, UpperStringSerializer)
json_str = mapper.to_json(movie)
print(json_str)
# {"name": "TERMINATOR: DARK FATE", "year": 2019}
json_str = '{"name": "Terminator: Dark Fate", "year": 2019}'
movie = mapper.from_json(json_str, Movie)
print(movie)
# Movie(name='TERMINATOR: DARK FATE', year=2019)
@dataclass
class Movie:
name: str = jsonfield(serializer_class=UpperStringSerializer)
year: int
movie = Movie("Terminator: Dark Fate", 2019)
json_str = mapper.to_json(movie)
print(json_str)
# {"name": "TERMINATOR: DARK FATE", "year": 2019}
json_str = '{"name": "Terminator: Dark Fate", "year": 2019}'
movie = mapper.from_json(json_str, Movie)
print(movie)
# Movie(name='TERMINATOR: DARK FATE', year=2019)
History
0.1.0 (2019-11-01)
First release on PyPI.
0.2.0 (2019-11-01)
Decimal type support.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for jsondataclass-0.3.0-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | b714cc4ed08e94e4c8305684b64ab7c54cf0ff88e9c10ffbd2dd8ff37e17aeb8 |
|
MD5 | 1fca6b58b914e2dcb38ec6d0b1b4f0ea |
|
BLAKE2b-256 | ed13b18fc5fdec2698f0d084963034979afeb272fb05b5f44b0a8510711640e7 |