JSON
Serialization
Programming
Class
Data Structures

Serializing class instance to JSON

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

JSON can represent strings, numbers, booleans, arrays, and objects, but it does not know how to serialize an arbitrary class instance by itself. When you serialize a custom object, the real job is converting it into JSON-compatible data first. In Python, that usually means turning the instance into a dictionary or supplying a custom encoder.

Why Custom Objects Fail by Default

The standard json module only understands built-in JSON-compatible types. If you pass a custom instance directly, you get a TypeError.

python
1import json
2
3class Person:
4    def __init__(self, name, age):
5        self.name = name
6        self.age = age
7
8person = Person("Ava", 30)
9
10try:
11    print(json.dumps(person))
12except TypeError as exc:
13    print(exc)

That happens because JSON needs a data structure such as a dictionary, not an opaque Python object with methods and identity.

The Simplest Case: Use __dict__

If the class only contains JSON-safe attributes, serializing __dict__ is often enough.

python
1import json
2
3class Person:
4    def __init__(self, name, age):
5        self.name = name
6        self.age = age
7
8person = Person("Ava", 30)
9print(json.dumps(person.__dict__, indent=2))

This is fine for simple objects, but it becomes too blunt when the object contains nested custom classes, dates, or internal fields you do not want to expose.

Use default for Explicit Conversion

A cleaner pattern is to pass a conversion function through the default parameter of json.dumps.

python
1import json
2
3class Person:
4    def __init__(self, name, age):
5        self.name = name
6        self.age = age
7
8
9def encode_object(obj):
10    if isinstance(obj, Person):
11        return {
12            "name": obj.name,
13            "age": obj.age,
14        }
15    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
16
17
18person = Person("Ava", 30)
19print(json.dumps(person, default=encode_object, indent=2))

This keeps the JSON representation intentional. You can rename fields, omit sensitive values, or normalize nested content.

Dataclasses Make This Easier

If the class is mainly data, dataclasses are a natural fit. asdict converts the instance into a serializable structure.

python
1import json
2from dataclasses import asdict, dataclass
3
4@dataclass
5class Person:
6    name: str
7    age: int
8
9person = Person("Ava", 30)
10print(json.dumps(asdict(person), indent=2))

This is often the cleanest option when you control the class definition and the object is essentially a data container.

Nested Objects and Non-JSON Types

Real objects often contain values such as datetime, Decimal, or nested custom instances. Those still need explicit conversion.

python
1import json
2from dataclasses import dataclass
3from datetime import datetime
4
5@dataclass
6class Address:
7    city: str
8    country: str
9
10@dataclass
11class User:
12    name: str
13    created_at: datetime
14    address: Address
15
16
17def encode_object(obj):
18    if isinstance(obj, datetime):
19        return obj.isoformat()
20    if hasattr(obj, "__dict__"):
21        return obj.__dict__
22    raise TypeError(f"Unsupported type: {type(obj).__name__}")
23
24
25user = User("Ava", datetime(2025, 9, 24, 10, 30), Address("Toronto", "Canada"))
26print(json.dumps(user, default=encode_object, indent=2))

The important idea is that JSON serialization is about projecting object state into portable data, not about serializing Python behavior.

Let the Class Define Its Public Shape

Another strong pattern is giving the class a method such as to_dict():

python
1import json
2
3class Person:
4    def __init__(self, name, age):
5        self.name = name
6        self.age = age
7
8    def to_dict(self):
9        return {
10            "name": self.name,
11            "age": self.age,
12        }
13
14person = Person("Ava", 30)
15print(json.dumps(person.to_dict(), indent=2))

This works well when the class itself should own the mapping between internal state and public JSON representation.

Common Pitfalls

The biggest mistake is assuming json.dumps can serialize arbitrary class instances automatically. It cannot unless you first convert them into JSON-compatible data.

Another issue is dumping __dict__ blindly and exposing internal or sensitive attributes that should not appear in JSON.

People also forget nested or special types. One datetime or child object is enough to break a naive serialization path.

Finally, do not confuse JSON serialization with object persistence. JSON is a data interchange format, not a full capture of Python object behavior.

Summary

  • Serializing a class instance to JSON means converting it into JSON-compatible data first.
  • '__dict__ works for simple cases but is often too blunt for real applications.'
  • 'default in json.dumps gives you explicit control over conversion.'
  • Dataclasses combine naturally with asdict for data-oriented objects.
  • Be deliberate about nested objects, non-JSON types, and which attributes should be exposed.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.