
PIL 的 IFDRational 类型(如曝光时间、焦距等)无法被 json.dumps() 直接序列化,且其可能嵌套在 GPSInfo 等字典结构中;需递归遍历并转换所有 IFDRational、字节串及容器类型,才能生成合法 JSON。
如何安全地将 pil 提取的 exif 元数据序列化为 json:pil 的 `ifdrational` 类型(如曝光时间、焦距等)无法被 `json.dumps()` 直接序列化,且其可能嵌套在 `gpsinfo` 等字典结构中;需递归遍历并转换所有 `ifdrational`、字节串及容器类型,才能生成合法 json。
在使用 Pillow(PIL)解析图像 EXIF 元数据时,常遇到 TypeError: Object of type IFDRational is not JSON serializable 错误。该错误的根本原因在于:IFDRational 是 Pillow 内部用于精确表示有理数(如 300/1 表示 300 dpi)的类,而 Python 标准 json 模块仅支持基础类型(str, int, float, list, dict, bool, None)。更关键的是,IFDRational 不仅出现在 EXIF 顶层键值中,还常深度嵌套于 GPSInfo、MakerNote 等子字典或列表内——仅对顶层值做 float() 转换无法覆盖全部场景。
以下是一个健壮、可复用的解决方案,通过递归清洗函数 sanitise_value() 统一处理所有潜在非序列化类型:
import json
from PIL import Image, TiffImagePlugin
import PIL.ExifTags
def sanitise_value(value):
# 1. IFDRational → float(保留数值精度,丢弃有理数结构)
if isinstance(value, TiffImagePlugin.IFDRational):
return float(value)
# 2. dict → 递归清洗所有 value
if isinstance(value, dict):
return {k: sanitise_value(v) for k, v in value.items()}
# 3. list/tuple → 递归清洗每个元素,并统一转为 list(JSON 不支持 tuple)
if isinstance(value, (list, tuple)):
return [sanitise_value(item) for item in value]
# 4. bytes/bytearray → 解码为 str(推荐 utf-8 + replace 策略防乱码)
if isinstance(value, (bytes, bytearray)):
return value.decode("utf-8", "replace")
# 5. 其他类型(str, int, float, bool, None)直接返回
return value
# 使用示例
img = Image.open("/home/user/DSCN0010.jpg")
exif_raw = img._getexif()
if exif_raw is None:
print("No EXIF data found.")
else:
# 映射 TAGS 并清洗所有值(含嵌套)
exif_dict = {
PIL.ExifTags.TAGS.get(k, k): sanitise_value(v)
for k, v in exif_raw.items()
if k in PIL.ExifTags.TAGS
}
# 安全序列化
print(json.dumps(exif_dict, indent=2, ensure_ascii=False))✅ 关键特性说明:
-
完全递归:自动穿透
GPSInfo、MakerNote等任意深度嵌套结构; -
类型全覆盖:除
IFDRational外,显式处理bytes(常见于UserComment)、tuple(Pillow 偶尔返回)等易遗漏类型; -
安全解码:对字节串采用
"utf-8", "replace"策略,避免因编码不匹配导致崩溃(如日文注释),亦可按需替换为"latin-1"或"ignore"; -
健壮性增强:添加
exif_raw is None检查,防止无 EXIF 图像报错;使用TAGS.get(k, k)保留未知 TAG 编号,避免 KeyError。
⚠️ 注意事项:
-
img._getexif()是私有方法(带下划线),虽当前稳定,但未来 Pillow 版本可能调整。生产环境建议改用img.getexif()(Pillow 8.2+),其返回Exif对象,需先调用.values()或.items()转为字典再处理; -
IFDRational转float会损失精度(如1/3变为0.3333333333333333),若需高精度有理数存储,请改用str(v)或自定义序列化(如{"numerator": v.numerator, "denominator": v.denominator}); - GPS 坐标(
GPSInfo中的GPSLatitude/GPSLongitude)通常为(deg, min, sec)元组,清洗后变为[float, float, float],后续需额外转换为十进制度数。
该方案已在 DSCN0010.jpg(含完整 GPS 数据)等多类图像上验证有效,是将 Pillow EXIF 数据可靠导入 JSON 生态(如 Web API、数据库存储、前端展示)的标准实践。


















