查看: 418|回复: 0

Python自定义JSONDecoder重写decode方法完整指南

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在之前的文章中,我们详细讲解过自定义 JSONEncoder 的实现。今天换一个方向,聚焦 JSON 的反向过程:通过继承 JSONDecoder 并重写 decode() 方法,把 JSON 字符串精准还原成自定义 Python 对象。很多场景下,json.loads() 默认只能解析出 dict、list 这类基础类型,比如:
  1. import json
  2. json_str = '{"name": "\u5f20\u4e09", "age": 25}'
  3. data = json.loads(json_str)
  4. print(type(data))  # <class 'dict'>
复制代码

如果我们希望 JSON 数据能自动还原为 Person 实例,直接访问 person.name、person.age,就需要自定义 JSONDecoder。

一、JSONDecoder 的核心骨架

JSONDecoder 有两个关键方法:

__init__():构造方法,可接收 object_hook、parse_float 等参数;
decode(s):核心方法,负责把 JSON 字符串解析成 Python 对象;
raw_decode(s):底层解析方法,返回 (对象, 索引) 元组。

自定义解码器最直接的方式就是重写 decode() 方法,完全控制解析流程。

二、重写 decode 方法完成基础还原

假设我们有 Person 类和配套的 PersonEncoder,序列化时写入了一个 __type__ 字段来标记类型。接下来编写 PersonDecoder:
  1. import json
  2. from datetime import datetime
  3. from typing import Any
  4. class Person:
  5.     def __init__(self, name: str, age: int, created_at: datetime = None):
  6.         self.name = name
  7.         self.age = age
  8.         self.created_at = created_at or datetime.now()
  9.     def __repr__(self):
  10.         return f"Person(name={self.name}, age={self.age}, created_at={self.created_at})"
  11. class PersonEncoder(json.JSONEncoder):
  12.     def default(self, obj):
  13.         if isinstance(obj, Person):
  14.             return {
  15.                 "__type__": "Person",
  16.                 "name": obj.name,
  17.                 "age": obj.age,
  18.                 "created_at": obj.created_at.isoformat()
  19.             }
  20.         if isinstance(obj, datetime):
  21.             return {"__type__": "datetime", "value": obj.isoformat()}
  22.         return super().default(obj)
  23. class PersonDecoder(json.JSONDecoder):
  24.     def decode(self, s: str) -> Any:
  25.         # 先按标准 JSON 解析成 Python 基础对象
  26.         data = super().decode(s)
  27.         return self._convert(data)
  28.     def _convert(self, data):
  29.         if isinstance(data, dict):
  30.             if data.get("__type__") == "Person":
  31.                 created_at = data.get("created_at")
  32.                 if created_at:
  33.                     created_at = datetime.fromisoformat(created_at)
  34.                 return Person(
  35.                     name=data["name"],
  36.                     age=data["age"],
  37.                     created_at=created_at
  38.                 )
  39.             if data.get("__type__") == "datetime":
  40.                 return datetime.fromisoformat(data["value"])
  41.             # 递归处理字典中的每个值
  42.             return {k: self._convert(v) for k, v in data.items()}
  43.         elif isinstance(data, list):
  44.             return [self._convert(item) for item in data]
  45.         return data
复制代码

测试一下:
  1. p = Person("\u5f20\u4e09", 25)
  2. json_str = json.dumps(p, cls=PersonEncoder, ensure_ascii=False)
  3. print("\u5e8f\u5217\u5316:", json_str)
  4. restored = json.loads(json_str, cls=PersonDecoder)
  5. print("\u8fd8\u539f:", restored)
  6. print("\u7c7b\u578b:", type(restored))
  7. print("\u59d3\u540d:", restored.name)
  8. print("\u521b\u5efa\u65f6\u95f4\u7c7b\u578b:", type(restored.created_at))
复制代码

输出可以看到,restored 已经是 Person 实例,created_at 也还原成了 datetime 类型。

三、扩展解码器支持多种自定义类型

如果项目中有多个模型类,比如 Person、Student、Address,可以在同一个解码器里通过 __type__ 字段区分,并递归转换嵌套对象:
  1. class Address:
  2.     def __init__(self, city: str, district: str):
  3.         self.city = city
  4.         self.district = district
  5.     def __repr__(self):
  6.         return f"Address(city={self.city}, district={self.district})"
  7. class Student:
  8.     def __init__(self, name: str, age: int, address: Address = None):
  9.         self.name = name
  10.         self.age = age
  11.         self.address = address
  12.     def __repr__(self):
  13.         return f"Student(name={self.name}, age={self.age}, address={self.address})"
  14. class MultiDecoder(json.JSONDecoder):
  15.     def decode(self, s: str) -> Any:
  16.         data = super().decode(s)
  17.         return self._convert(data)
  18.     def _convert(self, data):
  19.         if isinstance(data, dict):
  20.             obj_type = data.get("__type__")
  21.             if obj_type == "Person":
  22.                 return Person(
  23.                     name=data["name"],
  24.                     age=data["age"],
  25.                     created_at=self._convert(data.get("created_at"))
  26.                 )
  27.             if obj_type == "Student":
  28.                 return Student(
  29.                     name=data["name"],
  30.                     age=data["age"],
  31.                     address=self._convert(data.get("address"))
  32.                 )
  33.             if obj_type == "Address":
  34.                 return Address(
  35.                     city=data["city"],
  36.                     district=data["district"]
  37.                 )
  38.             if obj_type == "datetime":
  39.                 return datetime.fromisoformat(data["value"])
  40.             return {k: self._convert(v) for k, v in data.items()}
  41.         elif isinstance(data, list):
  42.             return [self._convert(item) for item in data]
  43.         return data
  44. class MultiEncoder(json.JSONEncoder):
  45.     def default(self, obj):
  46.         if isinstance(obj, Person):
  47.             return {"__type__": "Person", "name": obj.name, "age": obj.age, "created_at": obj.created_at}
  48.         if isinstance(obj, Student):
  49.             return {"__type__": "Student", "name": obj.name, "age": obj.age, "address": obj.address}
  50.         if isinstance(obj, Address):
  51.             return {"__type__": "Address", "city": obj.city, "district": obj.district}
  52.         if isinstance(obj, datetime):
  53.             return {"__type__": "datetime", "value": obj.isoformat()}
  54.         return super().default(obj)
复制代码

这里的关键点在于 _convert() 方法会递归处理 dict 和 list。遇到带 __type__ 标记的字典,就根据类型名称构造对应的 Python 对象;遇到普通字典或列表,则继续向下递归。这样即使 JSON 结构里嵌套了多层对象,也能正确还原。

四、把序列化能力封装进类体系

在实际业务中,我们经常需要让多个模型类具备统一的行为。可以将基础属性和能力封装成多个混合类,再通过 Python 多继承组合出具体业务类。
  1. class Life:
  2.     def __init__(self, name, age):
  3.         self.name = name
  4.         self.age = age
  5.     def __str__(self):
  6.         return f"name:{self.name},age:{self.age}"
  7. class Animal(Life):
  8.     def __init__(self, name, age, speed):
  9.         super().__init__(name, age)
  10.         self.speed = speed
  11.     def __str__(self):
  12.         return f"{super().__str__()},speed:{self.speed}"
  13. class Flyable:
  14.     def fly(self):
  15.         return f"{self.name}\u6b63\u5728\u98de\u884c,\u901f\u5ea6\u4e3a{self.speed}"
  16. class SwimAble:
  17.     def swim(self):
  18.         return f"{self.name}\u6b63\u5728\u6e38\u6cf3,\u901f\u5ea6\u4e3a{self.speed}"
  19. class AttackAble:
  20.     def attack(self):
  21.         return f"{self.name}\u4f1a\u653b\u51fb,\u7834\u574f\u529b\u5f3a\u5927"
  22. class Bird(Flyable, Animal, Life, AttackAble):
  23.     def __init__(self, name, age, speed):
  24.         super().__init__(name, age, speed)
  25.     def speek(self):
  26.         return f"{self.name}\u6b63\u5728\u5593\u5593\u5593"
  27. class Dog(Animal, Life, SwimAble, AttackAble):
  28.     def __init__(self, name, age, speed):
  29.         super().__init__(name, age, speed)
  30.     def speek(self):
  31.         return f"{self.name}\u6b63\u5728\u6c6a\u6c6a\u6c6a"
  32. class BigSwan(Animal, Life, SwimAble, Flyable, AttackAble):
  33.     def __init__(self, name, age, speed):
  34.         super().__init__(name, age, speed)
  35.     def speek(self):
  36.         return f"{self.name}\u53d1\u51fa\u989d\u989d\u9e45\u7684\u58f0\u97f3"
复制代码

测试多继承组合效果:
  1. l0 = Life("\u5f20\u98de", 18)
  2. print(l0)
  3. a0 = Animal("\u9648\u519b", 18, 30)
  4. print(a0)
  5. b1 = Bird("\u5218\u632f\u9633", 18, 100)
  6. print(b1, b1.speek(), b1.fly(), b1.attack())
  7. d1 = Dog("\u9648\u519b", 18, 30)
  8. print(d1, d1.speek(), d1.swim(), d1.attack())
  9. bs = BigSwan("\u5f20\u6bc5\u98de", 20, 50)
  10. print(bs, bs.speek(), bs.swim(), bs.fly(), bs.attack())
复制代码

这种设计让每个类只关注自己的特有能力,公共能力通过混合类复用。在真实的序列化/反序列化项目中,我们可以把 SerializableMixin、DeserializableMixin 也做成混合类,再配合类型注册表,让 JsonDecoder 能够更灵活地扩展。

五、总结

继承 JSONDecoder 并重写 decode() 方法,是实现自定义 JSON 反序列化的核心思路。需要记住几个关键点:

1. decode() 内部先调用 super().decode(s) 拿到标准解析结果;
2. 通过 __type__ 标记字段识别原始对象类型;
3. 递归处理 dict 和 list,保证嵌套对象也能被还原;
4. 多继承 + 混合类可以很好地复用序列化与业务能力。

如果只是简单转换,也可以考虑 object_hook 参数,它比重写 decode() 更轻量。但在需要深度控制解析流程、处理复杂嵌套以及定制类型还原时,重写 decode() 是一个清晰且可扩展的方案。希望这篇文章能帮你在 Python 自定义 JSON 编解码的路上少踩几个坑。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-8-26 12:10 , Processed in 0.028247 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部