Python Django安全序列化:前端JSON解析实践

作者:技术拾光

时间:2026-06-06

浏览:577

在 Django 项目里,前后端数据交互是一项高频操作。例如,后端查询到用户的体重记录后,需要传给前端用 Chart.js 绘制趋势图。新手常常会踩一个坑——直接把 Python 的列表或字典对象通过 {{ weight_data|safe }} 丢给模板。 结果呢?前端控制台会报 SyntaxEr

文章目录

在 Django 项目里,前后端数据交互是一项高频操作。例如,后端查询到用户的体重记录后,需要传给前端用 Chart.js 绘制趋势图。新手常常会踩一个坑——直接把 Python 的列表或字典对象通过 {{ weight_data|safe }} 丢给模板。

结果呢?前端控制台会报 SyntaxError,提示 JSON 解析失败。打开页面源码一看,数据格式变成了 [{'weight': '122.00', 'entry_date': '2023-09-28'}]。问题就出在引号上——Python 默认的字符串表示(repr())使用单引号,而 JSON 标准强制要求键名和字符串值必须使用双引号。这一微小差异足以让前端的 JSON.parse() 罢工。

那么,如何安全、正确地将 Django 后端数据转换为前端 JavaScript 能解析的标准 JSON 呢?下面介绍两种经过生产环境验证的方案。

正确方案一:视图层使用 json.dumps() 序列化

最直接的方法是在视图(View)层完成序列化。利用 Python 标准库的 json.dumps() 方法,可以生成完全符合 RFC 8259 规范的 JSON 字符串。

具体操作是,在视图函数中引入 json 模块,对数据进行显式转换:

from json import dumps  #  关键导入
# ... 其他逻辑(分页、查询等)保持不变 ...
# 替换原 serialized_data 赋值语句:
serialized_data = dumps([
    {
        'weight': float(record.weight),  # 推荐保留数值类型,避免后续 parseFloat()
        'entry_date': record.entry_date.isoformat()  # 更健壮的日期格式(ISO 8601)
    }
    for record in user_weight_log
])

注意:dumps() 默认不会转义 HTML 特殊字符(如 <, >, &)。如果数据可能包含这些字符,建议添加参数 separators=(',', ':') 以减少不必要的空格。同时,由于生成的是纯 JSON 字符串,模板中仍需使用 |safe 过滤器以防止 Django 自动转义。

模板中的写法保持不变,将序列化后的字符串通过数据属性传递:

这样,前端 JavaScript 就能安全解析了:

const weightDataElement = document.getElementById('weight-data');
const weightDataJSON = weightDataElement.getAttribute('data-weight-data');
const weightData = JSON.parse(weightDataJSON); //  成功!
console.log(weightData[0].weight); // number: 122

这个方案思路清晰,控制力强,适合需要对序列化过程做精细控制的场景。

正确方案二:模板层使用 |json_script(推荐!)

如果你使用的是 Django 4.2 或更高版本,那么有一个更优雅、更安全的方案——|json_script 模板过滤器。这是 Django 官方推荐的现代做法。

它把序列化和安全转义的工作都交给了模板引擎。你只需要在视图中传入原生的 Python 对象(列表、字典等),完全不用提前调用 dumps():

# 视图中保持原始列表推导式(不调用 dumps)
weight_data = [
    {'weight': float(record.weight), 'entry_date': record.entry_date.isoformat()}
    for record in user_weight_log
]
return render(request, 'profile.html', {
    'user_weight_log': user_weight_log,
    'form': form,
    'weight_data': weight_data  #  传入原生 Python list/dict
})

在模板里,一行代码搞定:


{{ weight_data|json_script:"weight-data" }}

这行代码会在页面中生成一个 标签,其中的内容已经是正确转义后的标准 JSON。前端读取起来也非常直观:

// 自动查找 ID 为 "weight-data" 的 script 标签并解析其内容
const weightData = JSON.parse(document.getElementById('weight-data').textContent);
console.log(weightData.length); //  正确解析为数组

优势总结:
安全性高:自动进行 HTML 转义,从根本上杜绝 XSS 注入风险,即使数据中包含恶意脚本代码,也会被转义成纯文本而不会执行。
符合规范:使用 标签承载数据,是 HTML5 标准中推荐的做法,语义清晰。
逻辑简洁:视图层无需导入和调用 json 模块,代码更干净,职责分离更明确。
类型友好:原生支持 Python 的各种数据类型(字典、列表、元组、None、布尔值、整数、浮点数),无需手动处理类型转换。

可以说,在条件允许的情况下,|json_script 是首选方案。

注意事项与最佳实践

无论选择哪种方案,下面这些细节都值得注意,它们能帮你避开很多隐形的坑:

  • 永远不要对未序列化的 Python 对象使用 |safe:{{ weight_data|safe }} 这个“安全”标记,只适用于你已经 100% 确认是合法 JSON 字符串的变量(比如 dumps() 的输出)。如果直接对 Python 对象使用,不仅会输出非标准 JSON,还可能引入安全漏洞。
  • 日期格式优先用 isoformat():处理日期时间时,record.entry_date.isoformat() 生成的是 ISO 8601 格式的字符串(如 “2023-09-28T00:00:00”)。这种格式是国际标准,JavaScript 的 new Date() 可以直接解析,兼容性远优于自定义的 strftime 格式。
  • 数值类型保持原生:在构造数据时,像体重、价格这类数值字段,建议用 float() 或 int() 明确转换为 Python 数值类型,而不是字符串。这样序列化成 JSON 后依然是数字,前端可以直接使用,避免了不必要的 parseFloat() 调用和潜在的类型错误。
  • 分页数据需谨慎:如果你的查询集使用了分页(例如 Django 的 Paginator),会得到一个 Page 对象。直接对这个 Page 对象使用 |json_script,它会自动序列化其 object_list 属性。但如果你还需要总页数、当前页码等元信息,就需要手动构造一个包含这些信息的字典来传递。

总结一下,前后端数据交互的“最后一公里”看似简单,却藏着单引号这个常见的绊脚石。通过上述任一方案——要么在视图层用 json.dumps() 显式控制,要么在模板层用 |json_script 优雅交付——你都能彻底解决 JSON 解析错误,实现 Django 后端与前端 JavaScript 之间安全、高效的数据通信。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章