Make the Python json encoder support Python's new dataclasses

Solution 1:

Much like you can add support to the JSON encoder for datetime objects or Decimals, you can also provide a custom encoder subclass to serialize dataclasses:

import dataclasses, json

class EnhancedJSONEncoder(json.JSONEncoder):
        def default(self, o):
            if dataclasses.is_dataclass(o):
                return dataclasses.asdict(o)
            return super().default(o)

json.dumps(foo, cls=EnhancedJSONEncoder)

Solution 2:

Can't you just use the dataclasses.asdict() function to convert the dataclass to a dict? Something like:

>>> @dataclass
... class Foo:
...     a: int
...     b: int
...     
>>> x = Foo(1,2)
>>> json.dumps(dataclasses.asdict(x))
'{"a": 1, "b": 2}'

Solution 3:

Ways of getting JSONified dataclass instance

There are couple of options to accomplish that goal, selection of each imply analyze on which approach suits best for your needs:

Standart library: dataclass.asdict

import dataclasses
import json


@dataclass.dataclass
class Foo:
    x: str

foo = Foo(x='1')
json_foo = json.dumps(dataclasses.asdict(foo)) # '{"x": "1"}'

Picking it back to dataclass instance isn't trivial, so you may want to visit that answer https://stackoverflow.com/a/53498623/2067976

Marshmallow Dataclass

from dataclasses import field
from marshmallow_dataclass import dataclass


@dataclass
class Foo:
    x: int = field(metadata={"required": True})

foo = Foo(x='1') # Foo(x='1')
json_foo = foo.Schema().dumps(foo) # '{"x": "1"}'

# Back to class instance.
Foo.Schema().loads(json_foo) # Foo(x=1)

As a bonus for marshmallow_dataclass you may use validation on the field itself, that validation will be used when someone deserialize the object from json using that schema.

Dataclasses Json

from dataclasses import dataclass
from dataclasses_json import dataclass_json


@dataclass_json
@dataclass
class Foo:
    x: int

foo = Foo(x='1')
json_foo = foo.to_json() # Foo(x='1')
# Back to class instance
Foo.from_json(json_foo) # Foo(x='1')

Also, in addition to that notice that marshmallow dataclass did type conversion for you whereas dataclassses-json(ver.: 0.5.1) ignores that.

Write Custom Encoder

Follow accepted miracle2k answer and reuse custom json encoder.

Solution 4:

If you are ok with using a library for that, you can use dataclasses-json. Here is an example:

from dataclasses import dataclass

from dataclasses_json import dataclass_json


@dataclass_json
@dataclass
class Foo:
    x: str


foo = Foo(x="some-string")
foo_json = foo.to_json()

It also supports embedded dataclasses - if your dataclass has a field typed as another dataclass - if all dataclasses envolved have the @dataclass_json decorator.

Solution 5:

I'd suggest creating a parent class for your dataclasses with a to_json() method:

import json
from dataclasses import dataclass, asdict

@dataclass
class Dataclass:
    def to_json(self) -> str:
        return json.dumps(asdict(self))

@dataclass
class YourDataclass(Dataclass):
    a: int
    b: int

x = YourDataclass(a=1, b=2)
x.to_json()  # '{"a": 1, "b": 2}'

This is especially useful if you have other functionality to add to all your dataclasses.