Skip to main content

Json dataclass mapper

Project description

jsondataclass

Latest version https://travis-ci.org/tushkanin/jsondataclass.svg?branch=develop code style: black

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jsondataclass-0.3.0.tar.gz (19.4 kB view details)

Uploaded Source

Built Distribution

jsondataclass-0.3.0-py2.py3-none-any.whl (12.3 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file jsondataclass-0.3.0.tar.gz.

File metadata

  • Download URL: jsondataclass-0.3.0.tar.gz
  • Upload date:
  • Size: 19.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.14.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.38.0 CPython/3.8.0

File hashes

Hashes for jsondataclass-0.3.0.tar.gz
Algorithm Hash digest
SHA256 1cc234a880ee940b6095bbbe20ee7e096af87746123da33fd9f1466cacf93093
MD5 127db1d0a7c4596ec9d85a437d3a811e
BLAKE2b-256 003299f94ffa7c869ed35668031ee2c77f706df28f57e8ad4ba4feadf6e06e0c

See more details on using hashes here.

File details

Details for the file jsondataclass-0.3.0-py2.py3-none-any.whl.

File metadata

  • Download URL: jsondataclass-0.3.0-py2.py3-none-any.whl
  • Upload date:
  • Size: 12.3 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.14.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.38.0 CPython/3.8.0

File hashes

Hashes for jsondataclass-0.3.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 b714cc4ed08e94e4c8305684b64ab7c54cf0ff88e9c10ffbd2dd8ff37e17aeb8
MD5 1fca6b58b914e2dcb38ec6d0b1b4f0ea
BLAKE2b-256 ed13b18fc5fdec2698f0d084963034979afeb272fb05b5f44b0a8510711640e7

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page