在之前的文章中,我们详细讲解过自定义 JSONEncoder 的实现。今天换一个方向,聚焦 JSON 的反向过程:通过继承 JSONDecoder 并重写 decode() 方法,把 JSON 字符串精准还原成自定义 Python 对象。很多场景下,json.loads() 默认只能解析出 dict、list 这类基础类型,比如:
- import json
- json_str = '{"name": "\u5f20\u4e09", "age": 25}'
- data = json.loads(json_str)
- 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:
- import json
- from datetime import datetime
- from typing import Any
- class Person:
- def __init__(self, name: str, age: int, created_at: datetime = None):
- self.name = name
- self.age = age
- self.created_at = created_at or datetime.now()
- def __repr__(self):
- return f"Person(name={self.name}, age={self.age}, created_at={self.created_at})"
- class PersonEncoder(json.JSONEncoder):
- def default(self, obj):
- if isinstance(obj, Person):
- return {
- "__type__": "Person",
- "name": obj.name,
- "age": obj.age,
- "created_at": obj.created_at.isoformat()
- }
- if isinstance(obj, datetime):
- return {"__type__": "datetime", "value": obj.isoformat()}
- return super().default(obj)
- class PersonDecoder(json.JSONDecoder):
- def decode(self, s: str) -> Any:
- # 先按标准 JSON 解析成 Python 基础对象
- data = super().decode(s)
- return self._convert(data)
- def _convert(self, data):
- if isinstance(data, dict):
- if data.get("__type__") == "Person":
- created_at = data.get("created_at")
- if created_at:
- created_at = datetime.fromisoformat(created_at)
- return Person(
- name=data["name"],
- age=data["age"],
- created_at=created_at
- )
- if data.get("__type__") == "datetime":
- return datetime.fromisoformat(data["value"])
- # 递归处理字典中的每个值
- return {k: self._convert(v) for k, v in data.items()}
- elif isinstance(data, list):
- return [self._convert(item) for item in data]
- return data
复制代码
测试一下:
- p = Person("\u5f20\u4e09", 25)
- json_str = json.dumps(p, cls=PersonEncoder, ensure_ascii=False)
- print("\u5e8f\u5217\u5316:", json_str)
- restored = json.loads(json_str, cls=PersonDecoder)
- print("\u8fd8\u539f:", restored)
- print("\u7c7b\u578b:", type(restored))
- print("\u59d3\u540d:", restored.name)
- print("\u521b\u5efa\u65f6\u95f4\u7c7b\u578b:", type(restored.created_at))
复制代码
输出可以看到,restored 已经是 Person 实例,created_at 也还原成了 datetime 类型。
三、扩展解码器支持多种自定义类型
如果项目中有多个模型类,比如 Person、Student、Address,可以在同一个解码器里通过 __type__ 字段区分,并递归转换嵌套对象:
- class Address:
- def __init__(self, city: str, district: str):
- self.city = city
- self.district = district
- def __repr__(self):
- return f"Address(city={self.city}, district={self.district})"
- class Student:
- def __init__(self, name: str, age: int, address: Address = None):
- self.name = name
- self.age = age
- self.address = address
- def __repr__(self):
- return f"Student(name={self.name}, age={self.age}, address={self.address})"
- class MultiDecoder(json.JSONDecoder):
- def decode(self, s: str) -> Any:
- data = super().decode(s)
- return self._convert(data)
- def _convert(self, data):
- if isinstance(data, dict):
- obj_type = data.get("__type__")
- if obj_type == "Person":
- return Person(
- name=data["name"],
- age=data["age"],
- created_at=self._convert(data.get("created_at"))
- )
- if obj_type == "Student":
- return Student(
- name=data["name"],
- age=data["age"],
- address=self._convert(data.get("address"))
- )
- if obj_type == "Address":
- return Address(
- city=data["city"],
- district=data["district"]
- )
- if obj_type == "datetime":
- return datetime.fromisoformat(data["value"])
- return {k: self._convert(v) for k, v in data.items()}
- elif isinstance(data, list):
- return [self._convert(item) for item in data]
- return data
- class MultiEncoder(json.JSONEncoder):
- def default(self, obj):
- if isinstance(obj, Person):
- return {"__type__": "Person", "name": obj.name, "age": obj.age, "created_at": obj.created_at}
- if isinstance(obj, Student):
- return {"__type__": "Student", "name": obj.name, "age": obj.age, "address": obj.address}
- if isinstance(obj, Address):
- return {"__type__": "Address", "city": obj.city, "district": obj.district}
- if isinstance(obj, datetime):
- return {"__type__": "datetime", "value": obj.isoformat()}
- return super().default(obj)
复制代码
这里的关键点在于 _convert() 方法会递归处理 dict 和 list。遇到带 __type__ 标记的字典,就根据类型名称构造对应的 Python 对象;遇到普通字典或列表,则继续向下递归。这样即使 JSON 结构里嵌套了多层对象,也能正确还原。
四、把序列化能力封装进类体系
在实际业务中,我们经常需要让多个模型类具备统一的行为。可以将基础属性和能力封装成多个混合类,再通过 Python 多继承组合出具体业务类。
- class Life:
- def __init__(self, name, age):
- self.name = name
- self.age = age
- def __str__(self):
- return f"name:{self.name},age:{self.age}"
- class Animal(Life):
- def __init__(self, name, age, speed):
- super().__init__(name, age)
- self.speed = speed
- def __str__(self):
- return f"{super().__str__()},speed:{self.speed}"
- class Flyable:
- def fly(self):
- return f"{self.name}\u6b63\u5728\u98de\u884c,\u901f\u5ea6\u4e3a{self.speed}"
- class SwimAble:
- def swim(self):
- return f"{self.name}\u6b63\u5728\u6e38\u6cf3,\u901f\u5ea6\u4e3a{self.speed}"
- class AttackAble:
- def attack(self):
- return f"{self.name}\u4f1a\u653b\u51fb,\u7834\u574f\u529b\u5f3a\u5927"
- class Bird(Flyable, Animal, Life, AttackAble):
- def __init__(self, name, age, speed):
- super().__init__(name, age, speed)
- def speek(self):
- return f"{self.name}\u6b63\u5728\u5593\u5593\u5593"
- class Dog(Animal, Life, SwimAble, AttackAble):
- def __init__(self, name, age, speed):
- super().__init__(name, age, speed)
- def speek(self):
- return f"{self.name}\u6b63\u5728\u6c6a\u6c6a\u6c6a"
- class BigSwan(Animal, Life, SwimAble, Flyable, AttackAble):
- def __init__(self, name, age, speed):
- super().__init__(name, age, speed)
- def speek(self):
- return f"{self.name}\u53d1\u51fa\u989d\u989d\u9e45\u7684\u58f0\u97f3"
复制代码
测试多继承组合效果:
- l0 = Life("\u5f20\u98de", 18)
- print(l0)
- a0 = Animal("\u9648\u519b", 18, 30)
- print(a0)
- b1 = Bird("\u5218\u632f\u9633", 18, 100)
- print(b1, b1.speek(), b1.fly(), b1.attack())
- d1 = Dog("\u9648\u519b", 18, 30)
- print(d1, d1.speek(), d1.swim(), d1.attack())
- bs = BigSwan("\u5f20\u6bc5\u98de", 20, 50)
- 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 编解码的路上少踩几个坑。 |