JSON conversion
The JSONDataclass
mixin provides automatic conversion to and from JSON.
to_dict
/from_dict
convert to and from a Python dict.to_json
/from_json
convert to and from a JSON file-like object.save
/load
convert to and from a JSON file-like object or path.to_json_string
/from_json_string
convert to and from a JSON string.
Usage Example
Define a JSONDataclass
.
from dataclasses import dataclass
from typing import Optional
from fancy_dataclass import JSONDataclass
@dataclass
class Person(JSONDataclass):
name: str
age: int
height: float
hobbies: list[str]
awards: Optional[list[str]] = None
Convert to/from a Python dict.
>>> person = Person(
name='John Doe',
age=47,
height=71.5,
hobbies=['reading', 'juggling', 'cycling']
)
# default values are suppressed by default
>>> person.to_dict()
{'name': 'John Doe',
'age': 47,
'height': 71.5,
'hobbies': ['reading', 'juggling', 'cycling']}
# include all the values
>>> person.to_dict(full=True)
{'name': 'John Doe',
'age': 47,
'height': 71.5,
'hobbies': ['reading', 'juggling', 'cycling'],
'awards': None}
>>> new_person = Person.from_dict(person.to_dict())
>>> new_person == person
True
Convert to/from a JSON string.
>>> person = Person(
name='John Doe',
age=47,
height=71.5,
hobbies=['reading', 'juggling', 'cycling']
)
>>> json_string = person.to_json_string(indent=2)
>>> print(json_string)
{
"name": "John Doe",
"age": 47,
"height": 71.5,
"hobbies": [
"reading",
"juggling",
"cycling"
]
}
>>> new_person = Person.from_json_string(json_string)
>>> person == new_person
True
Details
JSONDataclass
inherits from DictDataclass
, which can be used to convert dataclasses to/from Python dicts via to_dict
and from_dict
. You may use DictDataclass
if you do not need to interact with JSON serialized data.
Class and Field Settings
You may customize the behavior of a JSONDataclass
subclass by passing keyword arguments upon inheritance (see mixin class settings). See DictDataclassSettings
for the full list of settings. For field-specific settings, see DictDataclassFieldSettings
.
Suppressing Defaults
One setting, suppress_defaults
, is set to True
by default. This will suppress fields in an output dict
or JSON whose values match the class's default value. While this is often helpful to keep the output smaller in size, it is sometimes better to be explicit. To override this behavior, you can set suppress_defaults=False
.
@dataclass
class A(JSONDataclass):
x: int = 5
@dataclass
class B(JSONDataclass, suppress_defaults=False):
x: int = 5
print(A().to_json_string())
{}
print(B().to_json_string())
{"x": 5}
Suppressing Null Values
You can suppress fields with null values by supplying suppress_none=True
:
@dataclass
class C(JSONDataclass, suppress_none=True):
x: Optional[int]
print(C(1).to_json_string()
{"x": 1}
print(C(None).to_json_string()
{}
You can be more fine-grained about handling the output behavior of specific fields by setting flags in their field settings:
- Setting
suppress_default
toFalse
orTrue
will override the class setting at the field level. - Setting
suppress_none
toFalse
orTrue
will override the class setting at the field level. - Setting
suppress
toFalse
orTrue
will force inclusion or exclusion of the field, regardless ofsuppress_defaults
orsuppress_none
settings.
Including Types in Output
Another setting of note is store_type
. This relates to type inference when loading an object from a dict
or JSON blob. Suppose you have a class like:
Converting a Circle
object to a JSON string:
This may be undesirable, since the output does not make it clear what type of thing it is. To include the type in the output, you may set store_type='name'
:
@dataclass
class Circle(JSONDataclass, store_type='name'):
radius: float
print(Circle(3).to_json_string())
{"type": "Circle", "radius": 3}
Sometimes it is better to store the fully qualified type name instead. You can do this by setting store_type
to 'qualname'
:
@dataclass
class Circle(JSONDataclass, store_type='qualname'):
radius: float
print(Circle(3).to_json_string())
{"type": "my_module.Circle", "radius": 3}
(Here, my_module
is the name of the module in which Circle
is defined.)
Setting store_type='qualname'
is particularly useful when dealing with inheritance hierarchies. For example, if you try:
This will raise the following error: TypeError: when subclassing a JSONDataclass, you must set store_type to a value other than 'auto', or subclass JSONBaseDataclass instead
. This is saying that when you subclass JSONDataclass
, you must explicitly override store_type
(preferably with 'qualname'
, to prevent type ambiguity when converting back from JSON). An alternative to setting store_type
is subclassing JSONBaseDataclass
instead of JSONDataclass
. This will set store_type
to 'qualname'
automatically.
Let's see why this is useful:
@dataclass
class Shape(JSONBaseDataclass):
...
@dataclass
class Circle(Shape):
radius: float
@dataclass
class Rectangle(Shape):
length: float
width: float
Now you can use the base class, Shape
, to convert from different subtypes:
shape_dicts = [{"type": "Circle", "radius": 3}, {"type": "Rectangle", "length": 3, "width": 5}]
shapes = [Shape.from_dict(d) for d in shape_dicts]
print(shapes)
[Circle(radius=3.0), Rectangle(length=3.0, width=5.0)]
Additional Customization
To customize the JSON output format, you may pass keyword arguments to to_json
or to_json_string
; these will get passed along to json.dump
. For example, ensure_ascii=False
will allow non-ASCII output, and indent=4
will indent the JSON with 4 spaces.
Note
JSONDataclass
is configured to use the default JSON settings provided by Python's standard json
library. This allows out-of-range float values like nan
and inf
to be represented as NaN
and Infinity
, which are not strictly part of the JSON standard. To disallow these values, you can pass allow_nan=False
when calling to_json
or to_json_string
, which will raise a ValueError
if such values occur.
To customize JSON encoding itself, a subclass of JSONDataclass
may override the json_encoder
method. This should return a json.JSONEncoder
subclass.
You can also customize how JSON keys are decoded. For example, you may want to translate an integer key in a JSON file like "1"
to the integer 1
. To accomplish this, override the json_key_decoder
method.