JSONEncoder subclasses and iterencode

json · Advanced · 10 min

A reusable encoder class plugs into frameworks that accept cls=, and streaming avoids building huge strings.

json.JSONEncoder is the class behind dumps. Subclass it and override default(self, o) to get the same behaviour as the default= function, but bundled with its options and reusable as cls=:

json.dumps(data, cls=AppEncoder)

Many libraries (Django's JsonResponse, some HTTP clients) take an encoder class rather than a function, which is the main reason to prefer a subclass. In default, call super().default(o) at the end; it raises the standard TypeError.

encoder.encode(obj) returns the whole string, while encoder.iterencode(obj) yields it in chunks. Writing those chunks to a file or socket avoids holding the entire document in memory as one string. json.dump already uses iterencode internally when writing to a file.

Options such as indent, sort_keys, ensure_ascii and check_circular are constructor arguments of the encoder.

You cannot change how built-in types (int, float, str, dict) are formatted by overriding default, and overriding encode isn't enough because the C accelerator handles nested values directly. If you need special float formatting, convert values beforehand (for example round them), or use a third-party library.

Similarly, json.JSONDecoder can be subclassed and passed as cls= to loads, mostly to bundle hooks.

Example

import io
import json
from datetime import date
from enum import Enum

class Color(Enum):
    RED = "red"

class AppEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, date):
            return o.isoformat()
        if isinstance(o, Enum):
            return o.value
        return super().default(o)    # raises TypeError

data = {"day": date(2025, 1, 31), "color": Color.RED}
print(json.dumps(data, cls=AppEncoder))  # {"day": "2025-01-31", "color": "red"}

out = io.StringIO()
enc = AppEncoder(separators=(",", ":"))
rows = [{"n": i} for i in range(3)]
for chunk in enc.iterencode(rows):       # streamed in pieces
    out.write(chunk)
print(out.getvalue())                    # [{"n":0},{"n":1},{"n":2}]

Remember

  • Subclass JSONEncoder and override default; pass it as cls=
  • Finish default with super().default(o) to raise the standard error
  • iterencode yields chunks so huge documents needn't be one string

Watch out for

  • Trying to change float formatting by overriding default; it is never called for floats.

Check yourself

  1. Why might you write a JSONEncoder subclass instead of passing default=?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/json.html#json.JSONEncoder