JSON
Enum
Serialization
Programming
Python

Serialising an Enum member to JSON

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Python's json.dumps() cannot serialize Enum members by default — it raises TypeError: Object of type Color is not JSON serializable. The fix is to use a custom JSONEncoder that converts enum members to their value (or name), use enum.value explicitly before serialization, or define enums that inherit from str or int alongside Enum (e.g., class Color(str, Enum)) so they are natively JSON-serializable. This article covers all approaches.

The Problem

python
1import json
2from enum import Enum
3
4class Color(Enum):
5    RED = 1
6    GREEN = 2
7    BLUE = 3
8
9data = {"name": "widget", "color": Color.RED}
10
11json.dumps(data)
12# TypeError: Object of type Color is not JSON serializable

json.dumps() only handles basic Python types (dict, list, str, int, float, bool, None). Enum members are not in this set.

Fix 1: Custom JSONEncoder

python
1import json
2from enum import Enum
3
4class EnumEncoder(json.JSONEncoder):
5    def default(self, obj):
6        if isinstance(obj, Enum):
7            return obj.value  # Serialize as the underlying value
8        return super().default(obj)
9
10class Color(Enum):
11    RED = 1
12    GREEN = 2
13    BLUE = 3
14
15data = {"name": "widget", "color": Color.RED}
16result = json.dumps(data, cls=EnumEncoder)
17print(result)  # {"name": "widget", "color": 1}
18
19# Serialize as name instead of value
20class EnumNameEncoder(json.JSONEncoder):
21    def default(self, obj):
22        if isinstance(obj, Enum):
23            return obj.name  # "RED" instead of 1
24        return super().default(obj)
25
26result = json.dumps(data, cls=EnumNameEncoder)
27print(result)  # {"name": "widget", "color": "RED"}

Override default() in a JSONEncoder subclass to handle enum members. Pass cls=EnumEncoder to json.dumps().

python
1from enum import Enum, StrEnum, IntEnum
2import json
3
4# IntEnum — members are also ints
5class Priority(IntEnum):
6    LOW = 1
7    MEDIUM = 2
8    HIGH = 3
9
10# StrEnum (Python 3.11+) — members are also strings
11class Status(StrEnum):
12    ACTIVE = "active"
13    INACTIVE = "inactive"
14    PENDING = "pending"
15
16# Both serialize natively without a custom encoder
17data = {"priority": Priority.HIGH, "status": Status.ACTIVE}
18print(json.dumps(data))
19# {"priority": 3, "status": "active"}
20
21# Pre-3.11 string enum pattern
22class Color(str, Enum):
23    RED = "red"
24    GREEN = "green"
25    BLUE = "blue"
26
27print(json.dumps({"color": Color.RED}))
28# {"color": "red"}

IntEnum and StrEnum members are instances of int and str respectively, so json.dumps() handles them without a custom encoder.

Fix 3: Using default Parameter

python
1import json
2from enum import Enum
3
4class Size(Enum):
5    SMALL = "S"
6    MEDIUM = "M"
7    LARGE = "L"
8
9# Lambda as the default handler
10data = {"size": Size.LARGE, "quantity": 5}
11result = json.dumps(data, default=lambda obj: obj.value if isinstance(obj, Enum) else str(obj))
12print(result)  # {"size": "L", "quantity": 5}
13
14# More robust default handler
15def json_default(obj):
16    if isinstance(obj, Enum):
17        return obj.value
18    if hasattr(obj, "__dict__"):
19        return obj.__dict__
20    return str(obj)
21
22result = json.dumps(data, default=json_default)

The default parameter is simpler than subclassing JSONEncoder when you only need to handle a few types.

Fix 4: Pydantic Models

python
1from enum import Enum
2from pydantic import BaseModel
3
4class Role(str, Enum):
5    ADMIN = "admin"
6    USER = "user"
7    GUEST = "guest"
8
9class User(BaseModel):
10    name: str
11    role: Role
12
13user = User(name="Alice", role=Role.ADMIN)
14print(user.model_dump_json())
15# {"name": "Alice", "role": "admin"}
16
17# Deserialization works automatically
18data = '{"name": "Bob", "role": "user"}'
19bob = User.model_validate_json(data)
20print(bob.role)        # Role.USER
21print(bob.role.value)  # "user"

Pydantic handles enum serialization and deserialization automatically when enum members inherit from str or int.

Deserialization (JSON to Enum)

python
1import json
2from enum import Enum
3
4class Color(Enum):
5    RED = 1
6    GREEN = 2
7    BLUE = 3
8
9# Serialize
10data = {"color": Color.RED}
11json_str = json.dumps(data, default=lambda o: o.value if isinstance(o, Enum) else o)
12print(json_str)  # {"color": 1}
13
14# Deserialize — custom object hook
15def decode_with_enums(d):
16    if "color" in d:
17        d["color"] = Color(d["color"])
18    return d
19
20restored = json.loads(json_str, object_hook=decode_with_enums)
21print(restored["color"])        # Color.RED
22print(type(restored["color"]))  # <enum 'Color'>

Use object_hook in json.loads() to convert raw values back into enum members during deserialization.

Multiple Enum Types

python
1import json
2from enum import Enum
3
4class Color(Enum):
5    RED = "red"
6    BLUE = "blue"
7
8class Size(Enum):
9    SMALL = "S"
10    LARGE = "L"
11
12# Generic encoder that handles all Enum subclasses
13class EnumEncoder(json.JSONEncoder):
14    def default(self, obj):
15        if isinstance(obj, Enum):
16            return {"__enum__": type(obj).__name__, "value": obj.value}
17        return super().default(obj)
18
19# Decoder that restores enum types
20ENUM_REGISTRY = {"Color": Color, "Size": Size}
21
22def enum_decoder(d):
23    if "__enum__" in d:
24        enum_class = ENUM_REGISTRY[d["__enum__"]]
25        return enum_class(d["value"])
26    return d
27
28data = {"color": Color.RED, "size": Size.LARGE}
29encoded = json.dumps(data, cls=EnumEncoder)
30print(encoded)
31# {"color": {"__enum__": "Color", "value": "red"}, "size": {"__enum__": "Size", "value": "L"}}
32
33decoded = json.loads(encoded, object_hook=enum_decoder)
34print(decoded["color"])  # Color.RED

Common Pitfalls

  • Using str(enum_member) instead of .value or .name: str(Color.RED) returns "Color.RED", not "RED" or 1. Use .value for the underlying value or .name for the member name.
  • IntEnum members compare equal to plain ints: Priority.HIGH == 3 is True with IntEnum. This can cause subtle bugs if you rely on type-checking. Use regular Enum if you want strict type separation.
  • Forgetting deserialization: Serializing enums to JSON is only half the problem. You also need a strategy to convert raw JSON values back to enum members when loading data.
  • Mixed enum types in one payload: A generic default=lambda o: o.value works for serialization, but deserialization requires knowing which value maps to which enum type. Include type information in the JSON if needed.
  • StrEnum behavior differences: StrEnum members are actual strings and can be compared with == to plain strings. This is convenient but means isinstance(Status.ACTIVE, str) returns True, which may surprise code that type-checks.

Summary

  • IntEnum and StrEnum (Python 3.11+) serialize natively with json.dumps()
  • Custom JSONEncoder with default() handles any enum type
  • Use .value for the underlying value, .name for the member name
  • default=lambda o: o.value is the simplest approach for quick serialization
  • Pydantic models handle enum serialization and deserialization automatically
  • For round-trip fidelity, include the enum class name in the JSON payload

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

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

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.