JSON conversion
The JSONDataclass mixin provides automatic conversion to and from JSON.
to_dict/from_dictconvert to and from a Python dict.to_json/from_jsonconvert to and from a JSON file-like object.save/loadconvert to and from a JSON file-like object or path.to_json_string/from_json_stringconvert 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_defaulttoFalseorTruewill override the class setting at the field level. - Setting
suppress_nonetoFalseorTruewill override the class setting at the field level. - Setting
suppresstoFalseorTruewill force inclusion or exclusion of the field, regardless ofsuppress_defaultsorsuppress_nonesettings.
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.