Question

Make a Python Class JSON Serializable

pythonjsonserialization

Question

How can a Python class instance be made JSON serializable?

class FileItem:
    def __init__(self, fname):
        self.fname = fname

Attempting to serialize an instance raises an error:

import json

x = FileItem("/foo/bar")
json.dumps(x)
# TypeError: Object of type FileItem is not JSON serializable

What is the correct way to convert this class instance to JSON?

Short Answer

By the end of this page, you will understand why json.dumps() cannot serialize arbitrary Python objects, how to convert an object into JSON-compatible data, and when to use to_dict(), the default argument, or a custom JSONEncoder.

Concept

JSON is a text format with a small set of supported value types:

  • object: Python dict
  • array: Python list or tuple
  • string: Python str
  • number: Python int or float
  • boolean: Python True and False
  • null: Python None

A FileItem instance is a custom Python object, not one of those JSON types. Although its data is stored in x.fname, the JSON module does not automatically know which attributes should be included, renamed, excluded, or converted.

The usual solution is to explicitly convert the object into a JSON-compatible dictionary first. This makes the serialization format clear and keeps your class in control of its public data format.

import json

json_data = json.dumps({"fname": "/foo/bar"})
print(json_data)
# {"fname": "/foo/bar"}

In real programs, explicit conversion matters because objects often contain values that JSON cannot represent directly, such as datetime objects, Path objects, database connections, functions, or other custom instances.

Mental Model

Think of JSON as a shipping form that accepts only a few standard field types: text, numbers, yes/no values, empty values, lists, and labeled groups of fields.

A Python class instance is like a custom item in a box. The shipping form cannot inspect it and guess what belongs on the form. A to_dict() method is the packing instruction: it tells Python exactly which parts of the object should be written as standard JSON data.

Take Quiz

Syntax and Examples

The clearest pattern is to add a method that returns a dictionary containing only JSON-compatible values.

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def to_dict(self):
        return {
            "fname": self.fname
        }

item = FileItem("/foo/bar")
json_text = json.dumps(item.to_dict())

print(json_text)
# {"fname": "/foo/bar"}

item.to_dict() produces this ordinary dictionary:

{"fname": "/foo/bar"}

Since dictionaries and strings are supported by JSON, json.dumps() can serialize it.

For this very simple class, item.__dict__ also works:

json_text = json.dumps(item.__dict__)

However, prefer to_dict() in most applications. It gives you control over the JSON structure and avoids exposing every internal attribute automatically.

To turn JSON back into an object, load the JSON into a dictionary and pass its values to the constructor:

json_text = '{"fname": "/foo/bar"}'
data = json.loads(json_text)
item = FileItem(**data)

print(item.fname)
# /foo/bar

Step by Step Execution

Consider this code:

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def to_dict(self):
        return {"fname": self.fname}

item = FileItem("/foo/bar")
data = item.to_dict()
json_text = json.dumps(data)

Step by step:

  1. FileItem("/foo/bar") creates a custom object named item.
  2. self.fname = fname stores the string "/foo/bar" on that object.
  3. item.to_dict() creates {"fname": "/foo/bar"}.
  4. The dictionary contains only JSON-supported values: a string key and a string value.
  5. json.dumps(data) converts the dictionary into the JSON text '{"fname": "/foo/bar"}'.

If you instead call json.dumps(item), the encoder sees a FileItem object and raises TypeError because no conversion rule was supplied.

Real World Use Cases

Custom-object serialization is common whenever Python data must cross a boundary:

  • API responses: Convert domain objects such as users, orders, or files into JSON response bodies.
  • Configuration files: Store application settings or saved preferences as JSON.
  • Caching: Serialize simple data representations before writing them to a cache.
  • Message queues: Send event payloads, such as file_uploaded, to another service.
  • Logging and auditing: Record structured details in JSON logs.
  • Command-line tools: Export results in a portable format that other tools can read.

For example, an API may return a file record as:

{
    "name": "report.pdf",
    "path": "/uploads/report.pdf",
    "size_bytes": 2048
}

The application may use a FileItem class internally, but it sends a dictionary-shaped JSON representation to clients.

Take Quiz

Real Codebase Usage

In production code, developers usually keep JSON conversion explicit and close to the data model.

Use to_dict() for a stable public representation

class FileItem:
    def __init__(self, fname, is_public):
        self.fname = fname
        self.is_public = is_public
        self._cache_key = "internal-only"

    def to_dict(self):
        return {
            "filename": self.fname,
            "public": self.is_public
        }

This pattern can rename fields and prevent _cache_key from leaking into an API response.

Use default= when serializing collections

If a list or dictionary may contain custom objects, pass a conversion function to json.dumps():

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def to_dict(self):
        return {"fname": self.fname}

def serialize_object(value):
    if isinstance(value, FileItem):
        return value.to_dict()
    raise TypeError(f"Cannot serialize {type(value).__name__}")

items = [FileItem("/a.txt"), FileItem("/b.txt")]
print(json.dumps(items, default=serialize_object))
# [{"fname": "/a.txt"}, {"fname": "/b.txt"}]

The function is called only for values the standard JSON encoder does not already support.

Use a custom encoder for a reusable application-wide policy

import json

class FileItemEncoder(json.JSONEncoder):
    def default(self, value):
        if isinstance(value, FileItem):
            return value.to_dict()
        return super().default(value)

json_text = json.dumps(FileItem("/foo/bar"), cls=FileItemEncoder)

A custom encoder is useful when the same rules are needed in many places. For a single class and a few call sites, to_dict() is generally simpler.

Common Mistakes

Passing the instance directly

This fails because FileItem is not a built-in JSON type.

json.dumps(FileItem("/foo/bar"))  # TypeError

Convert it with to_dict() or provide a default function.

Assuming __dict__ is always the best format

json.dumps(item.__dict__)

This can be convenient for temporary scripts, but it may expose internal fields or stop working as expected when the class changes. Prefer an explicit to_dict() method for data that other systems consume.

Returning another unsupported object from to_dict()

The dictionary itself must contain JSON-compatible values.

from pathlib import Path

class FileItem:
    def __init__(self, fname):
        self.fname = Path(fname)

    def to_dict(self):
        return {"fname": self.fname}  # Path is not JSON serializable

Convert unsupported values first:

def to_dict(self):
    return {"fname": str(self.fname)}

Returning None for unknown values in default

A default function should raise TypeError for unsupported types. Returning None silently turns an unknown object into JSON null, which can hide bugs.

Confusing JSON text with a Python dictionary

json.dumps({"fname": "/foo/bar"})  # returns a str
json.loads('{"fname": "/foo/bar"}')  # returns a dict

Use dumps to create JSON text and loads to parse JSON text.

Comparisons

ApproachBest useAdvantageLimitation
json.dumps(item.to_dict())Most classes and APIsExplicit, readable, easy to testMust call to_dict() before dumping
json.dumps(item.__dict__)Quick prototypes with simple objectsVery shortExposes all instance attributes and offers little control
json.dumps(data, default=func)Nested lists or dictionaries containing objectsHandles unsupported values while traversing dataThe conversion policy can be less visible at the call site
json.dumps(data, cls=Encoder)Shared serialization rules across a projectReusable encoder classMore code for simple cases

to_dict() is usually the default choice because it separates two responsibilities:

  • The class decides how it is represented as plain data.
  • The json module turns that plain data into JSON text.
Take Quiz

Cheat Sheet

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def to_dict(self):
        return {"fname": self.fname}

item = FileItem("/foo/bar")

# Object -> JSON string
text = json.dumps(item.to_dict())

# JSON string -> Python dictionary
data = json.loads(text)

# Dictionary -> object
restored = FileItem(**data)

Rules:

  • json.dumps() returns a JSON string.
  • json.loads() accepts a JSON string.
  • JSON supports dictionaries, lists, strings, numbers, booleans, and None.
  • Convert custom objects and values such as Path, datetime, and set to supported values first.
  • Prefer to_dict() when you need a deliberate, stable JSON format.
  • Use default=... or cls=... when unsupported objects appear inside nested data.

FAQ

Why is my Python class not JSON serializable?

Python's JSON encoder only knows the standard JSON value types. A custom class instance needs an explicit conversion rule.

Is __dict__ JSON serializable?

Often, yes: obj.__dict__ is a dictionary of instance attributes. It works only if every attribute value is also JSON serializable. It is less safe than an explicit to_dict() method.

Should I use to_json() or to_dict()?

Use to_dict() when possible, then call json.dumps() separately. A dictionary is useful beyond JSON, such as testing, validation, and API frameworks.

How do I serialize a list of class instances?

Convert each item with a list comprehension:

json.dumps([item.to_dict() for item in items])

Or supply a default conversion function to json.dumps().

Can json.loads() recreate my class automatically?

No. It returns standard Python values such as dictionaries and lists. Create the object yourself, for example FileItem(**data).

What should a default serialization function do for an unsupported type?

It should raise TypeError. This prevents unsupported values from being silently converted to incorrect JSON.

Can JSON store methods or Python behavior?

No. JSON stores data only. It does not preserve class methods, object identity, or arbitrary Python behavior.

Take Quiz

Mini Project

Description

Build a small file catalog exporter. The program stores file metadata in FileItem objects, converts each object to a safe dictionary, and exports the catalog as formatted JSON. This is similar to preparing data for an API response or a JSON report.

Goal

Create a JSON string containing a list of file records without passing custom class instances directly to json.dumps().

Requirements

Create a FileItem class with fname and size_bytes attributes.

Take Quiz

Keep learning

Related questions

View more