fastclasses-json


Namefastclasses-json JSON
Version 0.7.0 PyPI version JSON
download
home_pagehttps://github.com/cakemanny/fastclasses-json
SummaryQuickly serialize dataclasses to and from JSON
upload_time2024-02-04 10:35:07
maintainer
docs_urlNone
authorDaniel Golding
requires_python>=3.7
licenseMIT
keywords fast dataclasses json fastclasses
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Fastclasses JSON
================

[![CI](https://github.com/cakemanny/fastclasses-json/actions/workflows/pythonpackage.yml/badge.svg)](https://github.com/cakemanny/fastclasses-json/actions/workflows/pythonpackage.yml?query=branch%3Amaster)
[![PyPI](https://img.shields.io/pypi/v/fastclasses-json)](https://pypi.org/project/fastclasses-json/)

Inspired by [Dataclasses JSON](https://github.com/lidatong/dataclasses-json/).
This library attempts provide some basic functionality for encoding and
decoding [dataclasses](https://docs.python.org/3/library/dataclasses.html)
with close to hand-written performance characteristics for large datasets.

```python

from dataclasses import dataclass
from fastclasses_json import dataclass_json

@dataclass_json
@dataclass
class SimpleExample:
    str_field: str

SimpleExample.from_dict({'str_field': 'howdy!'})
SimpleExample.from_json('{"str_field": "howdy!"}')
# SimpleExample(str_field='howdy!')
SimpleExample('hi!').to_dict()
# {'str_field': 'hi!'}
SimpleExample('hi!').to_json()
# '{"str_field":"hi!"}'

```

Installation
------------
```bash
$ pip install fastclasses-json
```

Supported Types
---------------
* `typing.List[T]` where `T` is also decorated with `@dataclass_json`
* `typing.Optional[T]`
* `typing.Optional[typing.List[T]]`
* `typing.List[typing.Optional[T]]`
* `typing.List[typing.List[typing.List[T]]]` etc
* `typing.Dict[str, T]`
* `enum.Enum` subclasses
* `datetime.date` and `datetime.datetime` as ISO8601 format strings
  - NB: if `python-dateutil` is installed, it will be used instead of the
    standard library for parsing
* `decimal.Decimal` as strings
* `uuid.UUID` as strings
* Mutually recursive dataclasses.

any other types will just be left as is

```python
from __future__ import annotations
from typing import Optional, List

@dataclass_json
@dataclass
class Russian:
    doll: Optional[Doll]

@dataclass_json
@dataclass
class Doll:
    russian: Optional[Russian]

Russian.from_dict({'doll': {'russian': {'doll': None}}})
# Russian(doll=Doll(russian=Russian(doll=None)))
Russian(Doll(Russian(None))).to_dict()
# {'doll': {'russian': {}}}

from enum import Enum

class Mood(Enum):
    HAPPY = 'json'
    SAD = 'xml'

@dataclass_json
@dataclass
class ILikeEnums:
    maybe_moods: Optional[List[Mood]]


ILikeEnums.from_dict({})  # ILikeEnums(maybe_moods=None)
ILikeEnums.from_dict({'maybe_moods': ['json']})  # ILikeEnums(maybe_moods=[Mood.HAPPY])
ILikeEnums(maybe_moods=[Mood.HAPPY]).to_dict()  # {'maybe_moods': ['json']}

from datetime import date

@dataclass_json
@dataclass
class Enitnelav:
    romantic: date

Enitnelav.from_dict({'romantic': '2021-06-17'})  # Enitnelav(romantic=datetime.date(2021, 6, 17))
Enitnelav(romantic=date(2021, 6, 17)).to_dict()  # {'romantic': '2021-06-17'}

from decimal import Decimal
from uuid import UUID

@dataclass_json
@dataclass
class TaxReturn:
    number: UUID
    to_pay: Decimal  # 😱

TaxReturn.from_dict({'number': 'e10be89e-938f-4b49-b4cf-9765f2f15298', 'to_pay': '0.01'})
# TaxReturn(number=UUID('e10be89e-938f-4b49-b4cf-9765f2f15298'), to_pay=Decimal('0.01'))
TaxReturn(UUID('e10be89e-938f-4b49-b4cf-9765f2f15298'), Decimal('0.01')).to_dict()
# {'number': 'e10be89e-938f-4b49-b4cf-9765f2f15298', 'to_pay': '0.01'}

```

we are not a drop-in replacement for Dataclasses JSON. There are plenty of
cases to use this in spite.

Configuration
-------------

Per-field configuration is done by including a `"fastclasses_json"` dict
in the field metadata dict.

* `encoder`: a function to convert a given field value when converting from
  a `dataclass` to a `dict` or to JSON. Can be any callable.
* `decoder`: a function to convert a given field value when converting from
  JSON or a dict into the python `dataclass`. Can be any callable.
* `field_name`: the name the field should be called in the JSON output.

#### example

```python
@dataclass_json
@dataclass
class Coach:
    from_: str = field(metadata={
        "fastclasses_json": {
            "field_name": "from",
            "encoder": lambda v: v[:5].upper(),
        }
    })
    to_: str = field(metadata={
        "fastclasses_json": {
            "field_name": "to",
            "encoder": lambda v: v[:5].upper(),
        }
    })


Coach("London Victoria", "Amsterdam Sloterdijk").to_dict()
# {'from': 'LONDO', 'to': 'AMSTE'}
```

### Whole tree configuration options

#### How to use other field naming conventions

The `field_name_transform` option allows tranforming field names of all
dataclasses that are serialized / deserialized.

```python
from __future__ import annotations
from fastclasses_json import dataclass_json
from dataclasses import dataclass

@dataclass_json(field_name_transform=str.upper)
@dataclass
class Box:
    dimensions: Dimensions
    weight_in_g: int

@dataclass
class Dimensions:
    height_in_mm: int
    width_in_mm: int
    depth_in_mm: int

Box(Dimensions(12, 24, 35), 944).to_dict()
# {'DIMENSIONS': {'HEIGHT_IN_MM': 12, 'WIDTH_IN_MM': 24, 'DEPTH_IN_MM': 35}, 'WEIGHT_IN_G': 944}
```


Type checking (i.e. using mypy)
-------------------------------

If using type annotations in your code, you may notice type errors when type
checking classes that use the `@dataclass_json` decorator.

```
% mypy tests/for_type_checking.py
tests/for_type_checking.py:27: error: "A" has no attribute "to_json"
tests/for_type_checking.py:28: error: "Type[A]" has no attribute "from_dict"
```

There are two techniques for overcoming this, one which is simpler but likely
to break or be unstable between versions of python and mypy; and one which
is a bit more work on your part.

### Mypy plugin

Changes in python and mypy are likely to lead to a game of cat and mouse, but
for the moment, we have a plugin that you can configure in your `setup.cfg`

```
% cat setup.cfg
[mypy]
plugins = fastclasses_json.mypy_plugin
```

### Mixin with stub methods

There is a mixin containing stub methods for converting to and from dicts and
JSON. This can be useful if the mypy plugin breaks or if you are using a
different type checker.

```python
from dataclasses import dataclass
from fastclasses_json import dataclass_json, JSONMixin

@dataclass_json
@dataclass
class SimpleTypedExample(JSONMixin):
    what_a_lot_of_hassle_these_types_eh: str

print(SimpleTypedExample.from_dict({'what_a_lot_of_hassle_these_types_eh': 'yes'}))
```
```
% mypy that_listing_above.py
Success: no issues found in 1 source file
```

Notice that you have to use both the `@dataclass_json` decorator and the
`JSONMixin` mixin. How very annoying!


Migration & Caveats
-------------------

### `None`
Fields with the value `None` are not included in the produced JSON. This helps
keep the JSON nice and compact

```python
from dataclasses import dataclass
from fastclasses_json import dataclass_json
from typing import Optional

@dataclass_json
@dataclass
class Farm:
    sheep: Optional[int]
    cows: Optional[int]

Farm(sheep=None, cows=1).to_json()
# '{"cows":1}'
```


### `infer_missing`
Fastclasses JSON does not get annoyed if fields are missing when deserializing.
Missing fields are initialized as `None`. This differs from the defaults in
[Dataclasses JSON][dataclasses-json].

```python
from dataclasses import dataclass
from fastclasses_json import dataclass_json

@dataclass_json
@dataclass
class Cupboard:
    num_hats: int
    num_coats: int

Cupboard.from_dict({'num_hats': 2})
# Cupboard(num_hats=2, num_coats=None)
```

In [Dataclasses JSON][dataclasses-json], there is the `infer_missing`
parameter that gives this behaviour.
To make migration easier, `from_dict` and `from_json` takes the dummy parameter
`infer_missing`, so that the following code works the same and does
not cause errors:

```python
Cupboard.from_dict({'num_hats': 2}, infer_missing=True)
# Cupboard(num_hats=2, num_coats=None)
```

[dataclasses-json]: https://github.com/lidatong/dataclasses-json/

### `letter_case`
Fastclasses JSON does not have `letter_case`, instead see
`field_name_transform` under [Configuration](#configuration)
which can achieve the same goals.


<!-- TODO: write about nested Any? -->

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/cakemanny/fastclasses-json",
    "name": "fastclasses-json",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "fast dataclasses json fastclasses",
    "author": "Daniel Golding",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/ab/30/f3d841c50395bbebbea2cf755877205f1174c5b5230eacc00aa5c50486bf/fastclasses-json-0.7.0.tar.gz",
    "platform": null,
    "description": "Fastclasses JSON\n================\n\n[![CI](https://github.com/cakemanny/fastclasses-json/actions/workflows/pythonpackage.yml/badge.svg)](https://github.com/cakemanny/fastclasses-json/actions/workflows/pythonpackage.yml?query=branch%3Amaster)\n[![PyPI](https://img.shields.io/pypi/v/fastclasses-json)](https://pypi.org/project/fastclasses-json/)\n\nInspired by [Dataclasses JSON](https://github.com/lidatong/dataclasses-json/).\nThis library attempts provide some basic functionality for encoding and\ndecoding [dataclasses](https://docs.python.org/3/library/dataclasses.html)\nwith close to hand-written performance characteristics for large datasets.\n\n```python\n\nfrom dataclasses import dataclass\nfrom fastclasses_json import dataclass_json\n\n@dataclass_json\n@dataclass\nclass SimpleExample:\n    str_field: str\n\nSimpleExample.from_dict({'str_field': 'howdy!'})\nSimpleExample.from_json('{\"str_field\": \"howdy!\"}')\n# SimpleExample(str_field='howdy!')\nSimpleExample('hi!').to_dict()\n# {'str_field': 'hi!'}\nSimpleExample('hi!').to_json()\n# '{\"str_field\":\"hi!\"}'\n\n```\n\nInstallation\n------------\n```bash\n$ pip install fastclasses-json\n```\n\nSupported Types\n---------------\n* `typing.List[T]` where `T` is also decorated with `@dataclass_json`\n* `typing.Optional[T]`\n* `typing.Optional[typing.List[T]]`\n* `typing.List[typing.Optional[T]]`\n* `typing.List[typing.List[typing.List[T]]]` etc\n* `typing.Dict[str, T]`\n* `enum.Enum` subclasses\n* `datetime.date` and `datetime.datetime` as ISO8601 format strings\n  - NB: if `python-dateutil` is installed, it will be used instead of the\n    standard library for parsing\n* `decimal.Decimal` as strings\n* `uuid.UUID` as strings\n* Mutually recursive dataclasses.\n\nany other types will just be left as is\n\n```python\nfrom __future__ import annotations\nfrom typing import Optional, List\n\n@dataclass_json\n@dataclass\nclass Russian:\n    doll: Optional[Doll]\n\n@dataclass_json\n@dataclass\nclass Doll:\n    russian: Optional[Russian]\n\nRussian.from_dict({'doll': {'russian': {'doll': None}}})\n# Russian(doll=Doll(russian=Russian(doll=None)))\nRussian(Doll(Russian(None))).to_dict()\n# {'doll': {'russian': {}}}\n\nfrom enum import Enum\n\nclass Mood(Enum):\n    HAPPY = 'json'\n    SAD = 'xml'\n\n@dataclass_json\n@dataclass\nclass ILikeEnums:\n    maybe_moods: Optional[List[Mood]]\n\n\nILikeEnums.from_dict({})  # ILikeEnums(maybe_moods=None)\nILikeEnums.from_dict({'maybe_moods': ['json']})  # ILikeEnums(maybe_moods=[Mood.HAPPY])\nILikeEnums(maybe_moods=[Mood.HAPPY]).to_dict()  # {'maybe_moods': ['json']}\n\nfrom datetime import date\n\n@dataclass_json\n@dataclass\nclass Enitnelav:\n    romantic: date\n\nEnitnelav.from_dict({'romantic': '2021-06-17'})  # Enitnelav(romantic=datetime.date(2021, 6, 17))\nEnitnelav(romantic=date(2021, 6, 17)).to_dict()  # {'romantic': '2021-06-17'}\n\nfrom decimal import Decimal\nfrom uuid import UUID\n\n@dataclass_json\n@dataclass\nclass TaxReturn:\n    number: UUID\n    to_pay: Decimal  # \ud83d\ude31\n\nTaxReturn.from_dict({'number': 'e10be89e-938f-4b49-b4cf-9765f2f15298', 'to_pay': '0.01'})\n# TaxReturn(number=UUID('e10be89e-938f-4b49-b4cf-9765f2f15298'), to_pay=Decimal('0.01'))\nTaxReturn(UUID('e10be89e-938f-4b49-b4cf-9765f2f15298'), Decimal('0.01')).to_dict()\n# {'number': 'e10be89e-938f-4b49-b4cf-9765f2f15298', 'to_pay': '0.01'}\n\n```\n\nwe are not a drop-in replacement for Dataclasses JSON. There are plenty of\ncases to use this in spite.\n\nConfiguration\n-------------\n\nPer-field configuration is done by including a `\"fastclasses_json\"` dict\nin the field metadata dict.\n\n* `encoder`: a function to convert a given field value when converting from\n  a `dataclass` to a `dict` or to JSON. Can be any callable.\n* `decoder`: a function to convert a given field value when converting from\n  JSON or a dict into the python `dataclass`. Can be any callable.\n* `field_name`: the name the field should be called in the JSON output.\n\n#### example\n\n```python\n@dataclass_json\n@dataclass\nclass Coach:\n    from_: str = field(metadata={\n        \"fastclasses_json\": {\n            \"field_name\": \"from\",\n            \"encoder\": lambda v: v[:5].upper(),\n        }\n    })\n    to_: str = field(metadata={\n        \"fastclasses_json\": {\n            \"field_name\": \"to\",\n            \"encoder\": lambda v: v[:5].upper(),\n        }\n    })\n\n\nCoach(\"London Victoria\", \"Amsterdam Sloterdijk\").to_dict()\n# {'from': 'LONDO', 'to': 'AMSTE'}\n```\n\n### Whole tree configuration options\n\n#### How to use other field naming conventions\n\nThe `field_name_transform` option allows tranforming field names of all\ndataclasses that are serialized / deserialized.\n\n```python\nfrom __future__ import annotations\nfrom fastclasses_json import dataclass_json\nfrom dataclasses import dataclass\n\n@dataclass_json(field_name_transform=str.upper)\n@dataclass\nclass Box:\n    dimensions: Dimensions\n    weight_in_g: int\n\n@dataclass\nclass Dimensions:\n    height_in_mm: int\n    width_in_mm: int\n    depth_in_mm: int\n\nBox(Dimensions(12, 24, 35), 944).to_dict()\n# {'DIMENSIONS': {'HEIGHT_IN_MM': 12, 'WIDTH_IN_MM': 24, 'DEPTH_IN_MM': 35}, 'WEIGHT_IN_G': 944}\n```\n\n\nType checking (i.e. using mypy)\n-------------------------------\n\nIf using type annotations in your code, you may notice type errors when type\nchecking classes that use the `@dataclass_json` decorator.\n\n```\n% mypy tests/for_type_checking.py\ntests/for_type_checking.py:27: error: \"A\" has no attribute \"to_json\"\ntests/for_type_checking.py:28: error: \"Type[A]\" has no attribute \"from_dict\"\n```\n\nThere are two techniques for overcoming this, one which is simpler but likely\nto break or be unstable between versions of python and mypy; and one which\nis a bit more work on your part.\n\n### Mypy plugin\n\nChanges in python and mypy are likely to lead to a game of cat and mouse, but\nfor the moment, we have a plugin that you can configure in your `setup.cfg`\n\n```\n% cat setup.cfg\n[mypy]\nplugins = fastclasses_json.mypy_plugin\n```\n\n### Mixin with stub methods\n\nThere is a mixin containing stub methods for converting to and from dicts and\nJSON. This can be useful if the mypy plugin breaks or if you are using a\ndifferent type checker.\n\n```python\nfrom dataclasses import dataclass\nfrom fastclasses_json import dataclass_json, JSONMixin\n\n@dataclass_json\n@dataclass\nclass SimpleTypedExample(JSONMixin):\n    what_a_lot_of_hassle_these_types_eh: str\n\nprint(SimpleTypedExample.from_dict({'what_a_lot_of_hassle_these_types_eh': 'yes'}))\n```\n```\n% mypy that_listing_above.py\nSuccess: no issues found in 1 source file\n```\n\nNotice that you have to use both the `@dataclass_json` decorator and the\n`JSONMixin` mixin. How very annoying!\n\n\nMigration & Caveats\n-------------------\n\n### `None`\nFields with the value `None` are not included in the produced JSON. This helps\nkeep the JSON nice and compact\n\n```python\nfrom dataclasses import dataclass\nfrom fastclasses_json import dataclass_json\nfrom typing import Optional\n\n@dataclass_json\n@dataclass\nclass Farm:\n    sheep: Optional[int]\n    cows: Optional[int]\n\nFarm(sheep=None, cows=1).to_json()\n# '{\"cows\":1}'\n```\n\n\n### `infer_missing`\nFastclasses JSON does not get annoyed if fields are missing when deserializing.\nMissing fields are initialized as `None`. This differs from the defaults in\n[Dataclasses JSON][dataclasses-json].\n\n```python\nfrom dataclasses import dataclass\nfrom fastclasses_json import dataclass_json\n\n@dataclass_json\n@dataclass\nclass Cupboard:\n    num_hats: int\n    num_coats: int\n\nCupboard.from_dict({'num_hats': 2})\n# Cupboard(num_hats=2, num_coats=None)\n```\n\nIn [Dataclasses JSON][dataclasses-json], there is the `infer_missing`\nparameter that gives this behaviour.\nTo make migration easier, `from_dict` and `from_json` takes the dummy parameter\n`infer_missing`, so that the following code works the same and does\nnot cause errors:\n\n```python\nCupboard.from_dict({'num_hats': 2}, infer_missing=True)\n# Cupboard(num_hats=2, num_coats=None)\n```\n\n[dataclasses-json]: https://github.com/lidatong/dataclasses-json/\n\n### `letter_case`\nFastclasses JSON does not have `letter_case`, instead see\n`field_name_transform` under [Configuration](#configuration)\nwhich can achieve the same goals.\n\n\n<!-- TODO: write about nested Any? -->\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Quickly serialize dataclasses to and from JSON",
    "version": "0.7.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/cakemanny/fastclasses-json/issues",
        "Homepage": "https://github.com/cakemanny/fastclasses-json"
    },
    "split_keywords": [
        "fast",
        "dataclasses",
        "json",
        "fastclasses"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3fe06e20de50b9e2be66bb142ff84cc2759e238b9e158dab4616e7196be325b6",
                "md5": "e30388aabeb74a611fcda23919c1b56c",
                "sha256": "39c49b806203890382aea9b17ad9064afbfcdbeee5edd79caba0c2bcdc5658c7"
            },
            "downloads": -1,
            "filename": "fastclasses_json-0.7.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e30388aabeb74a611fcda23919c1b56c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 12877,
            "upload_time": "2024-02-04T10:35:06",
            "upload_time_iso_8601": "2024-02-04T10:35:06.003632Z",
            "url": "https://files.pythonhosted.org/packages/3f/e0/6e20de50b9e2be66bb142ff84cc2759e238b9e158dab4616e7196be325b6/fastclasses_json-0.7.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ab30f3d841c50395bbebbea2cf755877205f1174c5b5230eacc00aa5c50486bf",
                "md5": "217cb59c4ca0a415801262a0517f9ff5",
                "sha256": "bd0edd1d6cb30712b76056f6a9dceb6ce50a6dea1cc760d8720facce8533d975"
            },
            "downloads": -1,
            "filename": "fastclasses-json-0.7.0.tar.gz",
            "has_sig": false,
            "md5_digest": "217cb59c4ca0a415801262a0517f9ff5",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 20978,
            "upload_time": "2024-02-04T10:35:07",
            "upload_time_iso_8601": "2024-02-04T10:35:07.183074Z",
            "url": "https://files.pythonhosted.org/packages/ab/30/f3d841c50395bbebbea2cf755877205f1174c5b5230eacc00aa5c50486bf/fastclasses-json-0.7.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-02-04 10:35:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "cakemanny",
    "github_project": "fastclasses-json",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "fastclasses-json"
}
        
Elapsed time: 0.17953s