django 4.x 原生支持 models.jsonfield,postgresql、mysql 5.7+ 和 sqlite 3.38+ 可完整启用查询能力,旧版数据库退化为文本存储;需注意 null/default 语义、双下划线嵌套查询语法及局部更新技巧。

直接用 models.JSONField,别绕路存字符串,也别手写自定义字段——Django 4.x 原生支持、数据库级查询、原子更新全都有。
JSONField 在 Django 4.x 的兼容性与底层依赖
Django 4.x 默认要求数据库后端提供原生 JSON 类型支持。PostgreSQL(jsonb)、MySQL 5.7+(JSON 类型)和 SQLite 3.38+(通过 json1 扩展)可完整启用所有查询能力;MariaDB 和旧版 MySQL 会退化为文本存储,丢失索引和嵌套查询能力。
- PostgreSQL 用户默认获得最优体验:
__nested__key查询走索引,__contains支持 GIN 加速 - SQLite 用户需确认已启用
json1扩展(Django 4.2+ 自动检测,但某些嵌入式部署可能未开启) - 若用 MySQL 5.6 或更早版本,
models.JSONField实际退化为TextField,此时filter(data__foo='bar')会静默失败或全表扫描
定义模型时的 null/blank/default 取舍
JSONField 的空值语义容易混淆:SQL NULL 和 JSON null 是两回事,且 Django 不允许用 Python None 作为 default 值(会报 TypeError)。
- 想让字段可为空(即数据库存
NULL):设null=True,但必须配合default=None或显式传None - 想默认存空 JSON 对象
{}:用default=dict(注意是可调用对象,不是dict()) - 想默认存空数组
[]:用default=list -
blank=True仅影响表单校验,对数据库无意义;不要设default=''或default='{}'—— 这会存字符串,破坏类型安全
常见查询写法与易错点
JSON 字段查询不是“点号链”,而是双下划线路径展开,且大小写、键名拼写、嵌套层级都必须严格匹配。出错时通常不报异常,而是返回空结果集。
- 查
{"status": "active", "meta": {"score": 95}}中 score > 90:MyModel.objects.filter(data__meta__score__gt=90) - 查包含键
"tags"的记录:MyModel.objects.filter(data__has_key='tags')(不是__contains='tags',后者查的是 JSON 值是否包含该子串) - 查数组中含某元素(如
"roles": ["admin", "editor"]):MyModel.objects.filter(data__roles__contains='admin') - 错误示范:
data__status__exact='active'→ 若 status 是顶层键,应为data__status='active';多一层__exact反而失效
更新嵌套值时避免全量覆盖
直接赋值 obj.data = new_dict 会替换整个 JSON 字段,丢失未提及的键。要局部更新(比如只改 meta.score),得用数据库函数或两次操作。
- PostgreSQL 推荐用
Func表达式:MyModel.objects.update(data=Func(F('data'), Value('meta.score'), Value(98), function='jsonb_set')) - 通用做法(适合小数据量):先读出,修改 Python 字典,再整体写回:
obj = MyModel.objects.get(id=1); obj.data['meta']['score'] = 98; obj.save() - 并发场景下,两次读写有竞态风险;若需强一致性,必须加
select_for_update()或用数据库原生命令
最常被忽略的是:JSONField 的 default 必须是可调用对象,且 PostgreSQL 的 jsonb 会自动去重键、标准化空格,导致 json.dumps() 后比对失败——别在业务逻辑里依赖 JSON 字符串格式一致性。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











