Skip to main content

typed_json_dataclass

Codacy Badge Actions Status codecov PyPI status PyPI version PyPI pyversions PyPI - Downloads MIT license

typed_json_dataclass is a library that augments the Python3.7 dataclass feature in two major ways:

  1. Add a way to recursively grab class dictionary definitions, thus making your dataclass JSON serializable
  2. Add a light amount of type validation to your dataclasses, so that you can validate that the JSON you're being given matches the data types that you're expecting.

By expressing your data as dataclasses, and by having your incoming data validated as it is received, you can easily implement the Data Transfer Object (DTO) pattern in your Python code.

This library can be thought of as a combination of attrs, cattrs, and marshamllow

Getting Started

Install the library from PyPI:

pip install typed_json_dataclass

Use the dataclass decorator just like normal, but add the TypedJsonMixin from this library, to your class definition. This will add 4 new methods to all of your dataclasses:

  1. from_dict()
@classmethod
def from_dict(cls, raw_dict, *, mapping_mode=MappingMode.NoMap):
    """Given a python dict, create an instance of the implementing class.

    :raw_dict: A dictionary that represents the DTO to create
    :mapping_mode: Format for properties
    :returns: Returns an instance of the DTO, instantiated via the dict
    """
  1. from_json()
@classmethod
def from_json(cls, raw_json, *, mapping_mode=MappingMode.NoMap):
    """Given a raw json string, create an instance of the implementing class.

    :raw_json: A json string that represents the DTO to create
    :mapping_mode: Format for properties
    :returns: Returns an instance of the DTO, instantiated via the json
    """
  1. to_dict()
def to_dict(self, *, keep_none=False, mapping_mode=MappingMode.NoMap, warn_on_initvar=True):
    """Express the DTO as a dictionary.

    :keep_none: Filter keys that are None
    :mapping_mode: Format for properties
    :warn_on_initvar: Emit a warning if the instance contains non-default
                      init-only variables.
    :returns: Returns the instantiated DTO as a dictionary
    """
  1. to_json()
def to_json(self, *, keep_none=False, mapping_mode=MappingMode.NoMap, warn_on_initvar=True):
    """Express the DTO as a json string.

    :keep_none: Filter keys that are None
    :mapping_mode: Format for properties
    :warn_on_initvar: Emit a warning if the instance contains non-default
                      init-only variables.
    :returns: Returns the instantiated DTO as a json string
    """

Examples

Converting your dataclass to a JSON serializable format

from typing import List
from dataclasses import dataclass
from typed_json_dataclass import TypedJsonMixin

@dataclass
class Person(TypedJsonMixin):
    name: str
    age: int

@dataclass
class Family(TypedJsonMixin):
    people: List[Person]

bob = Person(name='Bob', age=24)
alice = Person(name='Alice', age=32)
family = Family(people=[bob, alice])

print(family.to_json())
# => {"people": [{"name": "Bob", "age": 24}, {"name": "Alice", "age": 32}]}

If your data doesn't match the type definitions, you'll get a helpful error:

from dataclasses import dataclass
from typed_json_dataclass import TypedJsonMixin

@dataclass
class Person(TypedJsonMixin):
    name: str
    age: int

request_data = '{"name":"Bob","age":"24"}'

bob = Person.from_json(request_data)
# => TypeError: Person.age is expected to be <class 'int'>, but value 24 with type <class 'str'> was found instead

And you can parse data from a Python dict as well. Just use the .from_dict() function instead:

from dataclasses import dataclass
from typed_json_dataclass import TypedJsonMixin

@dataclass
class Person(TypedJsonMixin):
    name: str
    age: int

request_data_as_dict = {
    'name': 'Alice',
    'age': '32'
}

alice = Person.from_dict(request_data_as_dict)
# => TypeError: Person.age is expected to be <class 'int'>, but value 32 with type <class 'str'> was found instead

Setting a mapping_mode for auto mapping

from dataclasses import dataclass
from typed_json_dataclass import TypedJsonMixin, MappingMode

@dataclass
class Person(TypedJsonMixin):
    person_name: str
    person_age: int

request_data_as_dict = {
    'personName': 'Alice',
    'personAge': 32
}

alice = Person.from_dict(request_data_as_dict, mapping_mode=MappingMode.SnakeCase)
# => Person(person_name='Alice', person_age=32)

This mapping mode is useful for when you get requests that have the JSON in a camel case format, but you want your objects to be snake case and stay PEP8 compliant.

Limitations and Caveats

Dataclasses with init-only variables

Support for dataclasses with init-only variables is limited. Although to_dict and to_json will convert the dataclass, the resulting dict or JSON string will not contain the init-only variables, since their values are not available after initialization. This also means that such dataclasses cannot later be instantiated from a dict or JSON string, since the init-only variables are a required parameter in the dataclass' __init__ method. TypedJsonMixin detects the usage of dataclasses with init-only variables, emits a warning when it is converted to a dict or JSON string, and refuses to instantiate a dataclass with init-only variables.

A first workaround consists of providing a default value to the init-only variables:

@dataclass
class Person(TypedJsonMixin):
    person_name: InitVar[str] = ''
    person_first_name: str = ''
    person_last_name: str = ''

    def __post_init__(self, person_name):
        if person_name:
            # Instantiated directly
            self.person_first_name, self.person_last_name = person_name.split()
        # Call TypedJsonMixin __post_init__ method
        super().__post_init__()

Note: Instantiations without arguments, such as Person(), are now possible, although the created instance would then be invalid.

The second workaround is to remove init-only variables from the dataclass, and perform the __post_init__ instantiation using a class method instead:

@dataclass
class Person(TypedJsonMixin):
    person_first_name: str
    person_last_name: str

    @classmethod
    def create(cls, person_name):
        first_name, last_name = person_name.split()
        cls(first_name, last_name)

Finally, if the dataclass is not meant to ever be instantiated from a dict or JSON string, and only the to_dict or to_json methods are called, the warnings can be suppressed by passing warn_on_initvar=False as a keyword argument in the method call.

Metadata

Release files for typed-json-dataclass 1.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for typed-json-dataclass 1.2.1
File Size Uploaded
typed-json-dataclass-1.2.1.tar.gz 10.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for typed-json-dataclass 1.2.1
File Interpreter ABI Platform
typed_json_dataclass-1.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 19.7 kB

Release files / typed-json-dataclass-1.2.1.tar.gz

Download URL typed-json-dataclass-1.2.1.tar.gz
Size 10.8 kB
Tags Source
SHA-256 checksum
How to use checksums
fe95767a967766d1c5124392a5fccac7a52a9359c97332ad7e63ed0586de3a85
BLAKE2b-256 checksum
How to use checksums
576ace7c66af3c6a3459622768d948157d4952ef6233430cfee511dc36d2c7da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/0.12.16 CPython/3.7.3 Linux/4.15.0-1036-gcp

Release files / typed_json_dataclass-1.2.1-py3-none-any.whl

Download URL typed_json_dataclass-1.2.1-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
28fb50b7c4ead7c3a20eb8a448984d5b072bdaa20c4db2fa56a93fc62e395bfd
BLAKE2b-256 checksum
How to use checksums
f6f0b84b17224f114bca74cf0286f5e624a90f342b6b7ca6deb1a539bc6a9f6d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/0.12.16 CPython/3.7.3 Linux/4.15.0-1036-gcp

Release history Release notifications | RSS feed

This release

1.2.1 This release

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page