Create Custom JSON Encoders for Python Dataclasses

Learn how to serialize Python dataclasses, enums, datetimes, and custom types back to JSON using custom JSONEncoder subclasses and default functions.

Serialization

Detailed Explanation

Serializing Python Objects Back to JSON

Python's json.dumps() cannot serialize dataclasses, enums, or datetime objects by default. You need a custom encoder or a default function.

The Problem

import json
from dataclasses import dataclass
from datetime import datetime

@dataclass
class User:
    id: int
    name: str
    created_at: datetime

user = User(id=1, name="Alice", created_at=datetime.now())
json.dumps(user)  # TypeError: Object of type User is not JSON serializable

Solution 1: dataclasses.asdict()

from dataclasses import asdict

json.dumps(asdict(user), default=str)

asdict() converts the dataclass to a dict recursively. The default=str handles datetime by calling str() on it.

Solution 2: Custom Encoder Class

from dataclasses import dataclass, asdict
from datetime import datetime, date
from enum import Enum
import json

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "__dataclass_fields__"):
            return asdict(obj)
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, date):
            return obj.isoformat()
        if isinstance(obj, Enum):
            return obj.value
        if isinstance(obj, set):
            return list(obj)
        return super().default(obj)

json.dumps(user, cls=CustomEncoder)
# '{"id": 1, "name": "Alice", "created_at": "2024-06-15T09:30:00"}'

Solution 3: Default Function

def json_default(obj):
    if hasattr(obj, "__dataclass_fields__"):
        return asdict(obj)
    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    if isinstance(obj, Enum):
        return obj.value
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")

json.dumps(user, default=json_default)

With Pydantic (Built-in)

Pydantic models have built-in serialization:

class User(BaseModel):
    id: int
    name: str
    created_at: datetime

user.model_dump_json()
# '{"id":1,"name":"Alice","created_at":"2024-06-15T09:30:00"}'

Controlling Field Names

Use json.dumps() with a dict comprehension to convert snake_case to camelCase:

def to_camel(s: str) -> str:
    parts = s.split("_")
    return parts[0] + "".join(p.capitalize() for p in parts[1:])

data = {to_camel(k): v for k, v in asdict(user).items()}

Round-Trip Checklist

  1. Deserializejson.loads() + dataclass construction or Pydantic model_validate_json().
  2. Process — Work with typed Python objects.
  3. Serialize — Custom encoder, asdict() + json.dumps(), or Pydantic model_dump_json().

Use Case

You are building a REST API with Flask or Django where you need to serialize response objects containing dataclasses, enums, and datetime fields into clean JSON for the HTTP response body.

Try It — JSON to Python Converter

Open full tool