Introduction
Python’s built-in json module is a vital tool, but it currently lacks native support for several foundational types within Python’s own standard library. Nearly every modern application relies on datetime, uuid.UUID, decimal.Decimal, and set, yet passing any of these to json.dumps() results in a TypeError.
This forces developers into a repetitive cycle of rewriting custom JSONEncoder subclasses, copying boilerplate snippets from Stack Overflow, or importing heavy external libraries just to handle basic serialization.
I propose adding an opt-in json.ExtendedEncoder to json/encoder.py. This provides a standard, “batteries-included” way to serialize these common types without introducing any backward-compatibility breaking changes to the default behavior.
The Proposed Class
import json
from datetime import datetime, date, time
from decimal import Decimal
from uuid import UUID
class ExtendedEncoder(json.JSONEncoder):
"""
An opt-in JSONEncoder that natively handles common Python standard library
types like datetime, UUID, Decimal, and set.
"""
def default(self, obj):
if isinstance(obj, (datetime, date, time)):
return obj.isoformat()
if isinstance(obj, UUID):
return str(obj)
if isinstance(obj, Decimal):
# Serialized as string to prevent IEEE 754 float precision loss
return str(obj)
if isinstance(obj, (set, frozenset)):
try:
return sorted(list(obj))
except TypeError:
# Mixed-type sets (e.g. {1, 'a'}) cannot be sorted reliably.
# We acknowledge this fallback is imperfect; an alternative
# would be raising TypeError with a clear message. Open to
# discussion on which is preferable.
return sorted(list(obj), key=str)
return super().default(obj)
Usage Example
import json
from datetime import datetime
from decimal import Decimal
import uuid
data = {
"transaction_id": uuid.uuid4(),
"amount": Decimal("100.50"),
"timestamp": datetime.now(),
"tags": {"finance", "api"}
}
# Clean, safe, out-of-the-box serialization
json_string = json.dumps(data, cls=json.ExtendedEncoder)
Why This Belongs in the Standard Library
-
Zero Regression Risk: Because this is strictly opt-in (cls=json.ExtendedEncoder), the default behavior of json.dumps() is left entirely untouched. Performance and compatibility for existing codebases remain identical.
-
Upstreaming Existing Ecosystem Consensus: We wouldn’t be inventing new standards. Major Python frameworks have used this exact logic for over a decade: Django’s DjangoJSONEncoder handles datetime, UUID, and Decimal; Flask/Werkzeug natively serializes datetime to ISO-8601; orjson and msgspec gained massive adoption partly by baking these exact defaults into their core engines.
-
Fulfilling “Batteries Included”: It is a poor developer experience when native standard library siblings like json and datetime cannot communicate without boilerplate wrapper code.
Anticipated Design Critiques & Defenses
1. Why draw the line at these four types? What about bytes, pathlib.Path, or Enum?
We apply a strict rule: only serialize types that map cleanly to an unambiguous internet-standard primitive.
- bytes is excluded: JSON is a text format. Converting bytes requires choosing an encoding (Base64, Hex, UTF-8) no single right answer exists.
- pathlib.Path is excluded: Path strings change structurally across platforms (\ vs /), introducing cross-platform bugs.
- enum.Enum is excluded: Preferences vary too widely between .name and .value.
- The included types: datetime maps to ISO-8601 (RFC 3339); UUID maps to its canonical 36-character string (RFC 4122); Decimal serializes as a string to prevent IEEE 754 precision loss.
2. Won’t serializing set to a list cause non-deterministic behavior?
ExtendedEncoder enforces sorting via sorted(list(obj)) to guarantee deterministic output. For mixed-type sets that can’t be sorted naturally, it falls back to key=str. We acknowledge this fallback is imperfect and are open to the alternative of raising a TypeError with a descriptive message instead, happy to discuss which behavior the community prefers.
3. Why not keep this on PyPI?
The standard library should provide standard solutions to universal standard library problems. Requiring a pip install to convert a datetime to JSON forces fragmentation for a problem with decade-long industry consensus.
The implementation itself is small roughly 20–30 lines in json/encoder.py plus tests. I’d love to get the core team’s thoughts on whether a PR is something the steering committee would be open to reviewing.