Python自定义对象怎么实现JSON序列化?处理datetime与复杂类

木白
木白
发布时间:2026-10-10| 7人浏览过
原创

在 Python 中直接使用标准库的 json.dumps() 序列化自定义类或 datetime 对象时,解释器会直接抛出 TypeError: Object of type ... is not JSON serializable。要让自定义对象和非基础类型支持 JSON 输出,核心解法是通过指定 json.dumps() 的 default 参数函数,或继承 json.JSONEncoder 重写 default() 方法,将无法识别的对象显式转换为字典、列表、字符串或数字等标准 JSON 原生数据类型。本文将深入讲解底层流转逻辑、具体转换步骤、复杂嵌套类与特殊结构的处理方案,并辨析开发中常见的认识误区。

一、底层机制:为什么 json.dumps 无法直接处理自定义对象?

JSON(JavaScript Object Notation)是一种纯文本数据交换规范,在规范层面上只定义了极少数基础数据结构:对象(键值对)、数组(序列)、字符串、数值、布尔值以及 null。Python 标准库的 json 模块在设计时严格对齐了这一规范,其内置编码器建立了一张固定的类型映射表:

  • Python dict 对应 JSON Object(键必须为字符串)
  • Python list、tuple 对应 JSON Array
  • Python str 对应 JSON String
  • Python int、float 对应 JSON Number
  • Python bool 对应 JSON Boolean (true / false)
  • Python None 对应 JSON Null (null)

当执行 json.dumps(obj) 时,底层递归遍历传入的数据结构。一旦遇到未在此映射表中登记的类型(如 datetime.datetime 或任意自定义 class 实例),编码器不会擅自猜测转换逻辑,而是立即查找是否配置了扩展钩子函数 default。如果未配置或该函数仍无法返回合法的基础类型,编码器最终抛出 TypeError 阻断执行。

Python自定义对象怎么实现JSON序列化?处理datetime与复杂类
图 1:Python json.dumps 编码分流逻辑与 default 扩展拦截机制

二、实践步骤:实现自定义序列化的核心流程

处理非基础类型的标准做法主要分为两种:一是直接向 json.dumps() 传入独立的 default 函数;二是派生 json.JSONEncoder 类并重写其 default() 方法。两者的核心转换逻辑是一致的,整个处理过程遵循清晰的四个阶段。

Python自定义对象怎么实现JSON序列化?处理datetime与复杂类
图 2:实现可扩展序列化器的四个规范步骤

1. 处理 datetime 对象的标准实现

在日常业务开发中,最常遇到的非原生序列化类型是 datetime.datetime 和 datetime.date。将日期时间序列化为 JSON 时,行业事实标准是转换为符合 ISO 8601 规范的字符串格式(例如 2026-10-09T09:48:00),这可以通过内置方法 isoformat() 获得。

通过独立函数指定 default 参数的代码如下:

import json
from datetime import datetime, date

def custom_json_default(obj):
    # 统一转换 datetime 与 date 对象
    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")

# 待序列化数据
event_record = {
    "event_name": "系统节点检查",
    "timestamp": datetime(2026, 10, 9, 9, 48, 0),
    "record_date": date(2026, 10, 9)
}

json_output = json.dumps(event_record, default=custom_json_default, ensure_ascii=False)
# 示例输出:
# {"event_name": "系统节点检查", "timestamp": "2026-10-09T09:48:00", "record_date": "2026-10-09"}

2. 继承 JSONEncoder 实现通用自定义类序列化器

对于面向对象业务,常常需要序列化自定义业务类。更规范且适合模块化复用的方式是派生 json.JSONEncoder:

import json
from datetime import datetime, date

class UniversalJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        # 1. 处理日期时间
        if isinstance(obj, (datetime, date)):
            return obj.isoformat()
        
        # 2. 优先支持显式定义的 to_dict 方法
        if hasattr(obj, "to_dict") and callable(obj.to_dict):
            return obj.to_dict()
            
        # 3. 支持普通自定义对象的 __dict__
        if hasattr(obj, "__dict__"):
            return {
                k: v for k, v in obj.__dict__.items()
                if not k.startswith("_")  # 过滤私有属性
            }
            
        # 4. 未识别类型交由父类处理,触发合规的 TypeError
        return super().default(obj)

三、进阶场景:处理嵌套类、Dataclass 与 __slots__ 复杂类

真实项目中的数据结构往往比单一类更为复杂。例如一个包含嵌套对象的订单类、使用 Python 3.7+ dataclasses 定义的数据传输对象,或者为了极致内存优化而定义了 __slots__ 的类。这些特殊场景在转换时各有不同的注意事项。

Python自定义对象怎么实现JSON序列化?处理datetime与复杂类
图 3:复杂嵌套类向标准 JSON 字典树的映射解构

1. 嵌套对象与 Dataclass 处理

如果类使用了标准库的 @dataclass 装饰,Python 提供了官方工具函数 dataclasses.asdict(),该函数会自动递归地把 dataclass 及其内部嵌套的字段全部转换为基础字典。但是,如果 dataclass 字段内部包含 datetime,直接调用 asdict() 转换出的字典依然包含未序列化的 datetime 对象。因此,正确的处理方式是将 dataclass 解包逻辑与前面编写的类型编码器结合:

import json
from dataclasses import dataclass, is_dataclass, asdict
from datetime import datetime

@dataclass
class UserProfile:
    user_id: int
    name: str

@dataclass
class Order:
    order_id: str
    created_at: datetime
    user: UserProfile

class CompleteEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        if is_dataclass(obj):
            # 将 dataclass 展开为 dict;dict 内部包含的 datetime
            # 会在后续遍历中继续进入本 default() 进行转换
            return asdict(obj)
        return super().default(obj)

order = Order(
    order_id="ORD-9021",
    created_at=datetime(2026, 10, 9, 9, 48, 0),
    user=UserProfile(user_id=1042, name="Alex")
)

print(json.dumps(order, cls=CompleteEncoder, ensure_ascii=False))

2. 针对 __slots__ 类的提取方案

在高性能或大规模数据场景下,很多类会声明 __slots__ 来禁止创建实例字典以节省内存。这类对象没有 __dict__ 属性,直接访问 obj.__dict__ 会引发 AttributeError。针对这类对象,可以通过遍历其 __slots__ 并使用 getattr() 读取属性:

class ProductTag:
    __slots__ = ("tag_id", "code")

    def __init__(self, tag_id: int, code: str):
        self.tag_id = tag_id
        self.code = code

def extract_slots_or_dict(obj):
    if hasattr(obj, "__dict__"):
        return {k: v for k, v in obj.__dict__.items() if not k.startswith("_")}
    if hasattr(obj, "__slots__"):
        return {slot: getattr(obj, slot) for slot in obj.__slots__ if hasattr(obj, slot)}
    raise TypeError(f"Cannot serialize object of type {type(obj).__name__}")

四、容易混淆的说法辨析

在处理自定义对象 JSON 序列化的过程中,开发者常会接触到一些看似简便的经验规则。理解这些规则在什么条件下成立、什么条件下会失效,有助于避免线上隐患。

说法一:“用 default=lambda o: o.__dict__ 一行代码就能序列化所有自定义类”

为什么看似合理:对于最简单的自定义类,其属性确实全部保存在实例的 __dict__ 字典中,一行 lambda 表达式看似优雅地解决了属性提取问题。

限制条件与需要修正之处:该写法只在“所有属性均为基本数据类型、且类未定义 __slots__”的单一条件下成立。一旦对象中含有 datetime、Decimal、枚举或其它未定义 __dict__ 的第三方类型,返回的字典中依然含有非法类型,会在后续编码步骤中直接崩溃。此外,若类中包含对自身或其他对象的双向引用,会导致递归深度溢出(RecursionError)。因此,生产环境不能盲目依赖单一的 __dict__ 映射。

说法二:“给自定义类重写 __str__ 或 __repr__ 就能直接被 JSON 序列化”

为什么看似合理:在打印对象或格式化输出时,__str__ 能够返回字符串表示,直觉上似乎可以被 JSON 识别。

限制条件与需要修正之处:Python 的 json.dumps() 绝不会自动调用未知对象的 __str__() 来代替序列化行为。即使在 default 中写 str(obj),它得到的也是一个纯扁平字符串(例如 "<Order ORD-9021>"),而不是结构化的 JSON 对象({"order_id": "ORD-9021"})。下游系统将无法解析内部的具体字段,丢失了结构化数据的核心价值。

五、方案选型与技术总结

面对不同的业务体量和工程约束,选择最适合的序列化方式能够大幅减少维护成本:

Python自定义对象怎么实现JSON序列化?处理datetime与复杂类
图 4:Python 对象 JSON 序列化技术方案矩阵总结

综上所述,Python 自定义对象与 datetime 的 JSON 序列化并非不可逾越的障碍。其本质在于利用 json 模块提供的 default 机制,在编码遇到未注册类型时,将其准确映射为 JSON 能理解的基础数据结构。掌握这一机制不仅能写出健壮的序列化代码,也能在面对复杂数据模型时从容做出架构决策。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。