How to Convert Python Dataclasses to JSON
Python's dataclasses
provide a convenient way to create classes that primarily store data.
This guide explains how to serialize dataclass instances to JSON strings, a common requirement for data exchange and storage. We'll cover the standard dataclasses.asdict()
method, creating custom JSON encoders for more complex scenarios, and using the dataclasses-json
library.
Converting Dataclasses to JSON with asdict()
and json.dumps()
The most straightforward and recommended way to convert a dataclass to JSON is to:
- Convert the dataclass to a dictionary using
dataclasses.asdict()
. - Serialize the dictionary to JSON using
json.dumps()
.
import json
from dataclasses import dataclass, asdict
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_available: int = 0
item1 = InventoryItem('A', 100, 1)
print(item1) # Output: InventoryItem(name='A', unit_price=100, quantity_available=1)
json_str = json.dumps(asdict(item1)) # Convert to dict, then to JSON
print(json_str)
# Output: {"name": "A", "unit_price": 100, "quantity_available": 1}
print(type(json_str))
# Output: <class 'str'>
@dataclass
: This decorator automatically generates methods like__init__
,__repr__
, etc., for your class, making it concise.asdict(item1)
: This function from thedataclasses
module recursively converts the dataclass instance (item1
) into a standard Python dictionary. This is the crucial step, asjson.dumps()
can handle dictionaries directly.json.dumps(...)
: This function from thejson
module serializes the dictionary into a JSON-formatted string.
Custom JSON Encoding with JSONEncoder
For more control over the JSON serialization process (e.g., handling custom data types or formatting), you can create a custom JSONEncoder
:
import json
from dataclasses import dataclass, asdict, is_dataclass
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_available: int = 0
class DCJSONEncoder(json.JSONEncoder):
def default(self, o):
if is_dataclass(o): # Check if object is dataclass instance.
return asdict(o) # Use asdict to serialize
return super().default(o) # Use default method for other types.
item1 = InventoryItem('A', 100, 1)
json_str = json.dumps(item1, cls=DCJSONEncoder)
print(json_str) # Output: {"name": "A", "unit_price": 100, "quantity_available": 1}
class DCJSONEncoder(json.JSONEncoder):
: We create a custom encoder by subclassingjson.JSONEncoder
.def default(self, o):
: This method is called byjson.dumps()
when it encounters an object it doesn't know how to serialize.if is_dataclass(o):
: We check if the objecto
is a dataclass instance.return asdict(o)
: If it's a dataclass, we useasdict()
to convert it to a dictionary, whichjson.dumps()
can handle.return super().default(o)
: If it's not a dataclass, we call thedefault()
method of the parent class (JSONEncoder
). This handles the standard types (lists, dictionaries, strings, numbers, etc.) and raises aTypeError
if it encounters an unsupported type. This is essential for correct behavior.
json.dumps(item1, cls=DCJSONEncoder)
: We telljson.dumps()
to use our custom encoder.
Using the dataclasses-json
Library
The dataclasses-json
library provides a convenient way to convert dataclasses to and from JSON:
- First you need to install the package using
pip install dataclasses-json
.
from dataclasses import dataclass
from dataclasses_json import dataclass_json
@dataclass_json
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_available: int = 0
item1 = InventoryItem('A', 100, 1)
print(item1) # Output: InventoryItem(name='A', unit_price=100, quantity_available=1)
json_str = item1.to_json()
print(json_str) # Output: {"name": "A", "unit_price": 100, "quantity_available": 1}
dataclass_obj = InventoryItem.from_json(json_str)
print(dataclass_obj) # Output: InventoryItem(name='A', unit_price=100.0, quantity_available=1)
@dataclass_json
: This decorator adds methods liketo_json()
andfrom_json()
to your dataclass.- You don't have to use the dict class before calling
json.dumps()
. item1.to_json()
: Converts the dataclass instance to a JSON string.InventoryItem.from_json()
: Creates a class instance from the JSON string.
Using default
Argument in json.dumps()
(Less Recommended)
While json.dumps()
accepts a default
argument, it is less flexible and less maintainable than creating a custom JSONEncoder
:
import json
from dataclasses import dataclass, asdict
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_available: int = 0
item1 = InventoryItem('A', 100, 1)
# Avoid using lambda with json.dumps()
json_str = json.dumps(item1, default=lambda o: asdict(o) if isinstance(o, InventoryItem) else o)
print(json_str) # Output: {"name": "A", "unit_price": 100.0, "quantity_available": 1}
It's less readable because the custom logic is defined using a lambda
expression.