json.JSONEncoder
Override one method, default(o), and every json.dumps(..., cls=YourEncoder) call knows your types. Return something serializable, or call super().default(o) to get the standard TypeError.
Demo
import json from datetime import date class Encoder(json.JSONEncoder): def default(self, o): if isinstance(o, set): return sorted(o) if isinstance(o, date): return o.isoformat() return super().default(o) json.dumps({'tags': set(['dev', 'admin', 'dev']), 'day': date(2026, 9, 29)}, cls=Encoder)
In the subclass tab json calls default() twice — once for the set, once for the date — and encodes whatever comes back; the set becomes a sorted list, so duplicates are gone and the order is stable. In iterencode(), keys, ": " and ", " between dict items are separate pieces, while list items carry the "[" or ", " in front of them. With any indent other than None, item_separator is "," — the line break and indentation take the place of the space.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| default | callable | no (None) | If given, replaces the default() method on this instance. |
| indent | int | str | None | no (None) | Pretty-print with this indent per level. Also switches the default item separator from ", " to ",". |
| separators | tuple[str, str] | no (None) | Sets item_separator and key_separator. |
| sort_keys | bool | no (False) | Emit dict keys in sorted order. |
| ensure_ascii | bool | no (True) | Escape non-ASCII characters as \uXXXX. |
| allow_nan | bool | no (True) | Emit NaN / Infinity; False raises ValueError. |
| skipkeys | bool | no (False) | Skip dict keys that are not str, int, float, bool or None. |
| check_circular | bool | no (True) | Detect self-referencing containers (ValueError). |
Return value
JSONEncoder — An encoder; call .encode(obj) for a str or .iterencode(obj) for the pieces.
Common patterns
import json from datetime import date, datetime from decimal import Decimal from enum import Enum from uuid import UUID class AppEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, (date, datetime)): return o.isoformat() if isinstance(o, (Decimal, UUID)): return str(o) if isinstance(o, (set, frozenset)): return sorted(o) if isinstance(o, Enum): return o.value return super().default(o) json.dumps(payload, cls=AppEncoder)
import dataclasses, json class DataclassEncoder(json.JSONEncoder): def default(self, o): if dataclasses.is_dataclass(o) and not isinstance(o, type): return dataclasses.asdict(o) return super().default(o)
import json for chunk in json.JSONEncoder().iterencode(big_data): sock.sendall(chunk.encode('utf-8'))
Examples
Pitfalls
import json class Encoder(json.JSONEncoder): def default(self, o): if isinstance(o, set): sorted(o) json.dumps({'tags': {'a'}}, cls=Encoder)
import json class Encoder(json.JSONEncoder): def default(self, o): if isinstance(o, set): return sorted(o) return super().default(o) json.dumps({'tags': {'a'}}, cls=Encoder)
import json class Rounder(json.JSONEncoder): def default(self, o): if isinstance(o, float): return round(o, 2) return super().default(o) json.dumps({'pi': 3.14159}, cls=Rounder)
import json data = {'pi': 3.14159} json.dumps({k: round(v, 2) for k, v in data.items()})
import io, json class Upper(json.JSONEncoder): def encode(self, o): return super().encode(o).upper() buf = io.StringIO() json.dump({'a': 'x'}, buf, cls=Upper) (json.dumps({'a': 'x'}, cls=Upper), buf.getvalue())
import io, json class Upper(json.JSONEncoder): def encode(self, o): return super().encode(o).upper() buf = io.StringIO() buf.write(json.dumps({'a': 'x'}, cls=Upper)) buf.getvalue()
When to use
- The same custom types (dates, Decimal, UUID, dataclasses) are serialized in many places
- Streaming output chunk by chunk with iterencode()
- Frameworks that accept an encoder class (cls=, json_encoder=)
- A one-off conversion → json.dumps(data, default=func)
- Changing how floats, strings or lists are written → transform the data first
- Parsing → JSONDecoder / json.loads
Notes
FAQ
Subclass json.JSONEncoder, override default(self, o) to return a serializable value for your types, end it with return super().default(o), and pass the class as json.dumps(data, cls=YourEncoder).