html/template无法直接解析JSON字符串,因其默认对变量执行HTML转义,将{、}、"等JSON关键字符转为HTML实体(如{),导致前端接收到非法字符串而非合法JSON;正确解法是用template.JS包装json.Marshal生成的字节切片,使模板信任并跳过转义,安全输出原生JSON。

为什么 html/template 无法直接解析 JSON 字符串
因为 Go 的 html/template 默认会对所有变量做 HTML 转义,而 JSON 中的 {、}、"、: 等字符会被转义成 { 这类实体,导致前端拿到的是损坏的字符串,不是合法 JSON 对象。
常见错误现象:在模板里写 {{ .Data }},浏览器控制台报 SyntaxError: Unexpected token & in JSON at position 0 —— 实际就是转义后的 { 开头。
- 不能靠前端用
JSON.parse(unescape(...))补救,不安全也不可靠 - 不能把 JSON 当普通字符串传进去再
eval(),XSS 风险极高 - 必须让 Go 模板“信任”这段内容,跳过转义,但又不破坏安全性
用 template.JS 包装 JSON 字符串再传入
Go 标准库提供了 template.JS 类型,它告诉 html/template:“这段内容是安全的 JavaScript 字面量,别转义”。这是最稳妥的官方方案。
关键步骤:
- 后端先用
json.Marshal把结构体转成[]byte - 转成
string后,再强转为template.JS - 传给模板时,类型必须是
template.JS,不是string
示例(Gin handler):
data := map[string]interface{}{"name": "Alice", "score": 95}
jsonBytes, _ := json.Marshal(data)
c.HTML(http.StatusOK, "index.html", gin.H{
"jsonData": template.JS(jsonBytes), // 注意这里不是 string!
})
模板中直接使用:
<script>
const data = {{ .jsonData }};
console.log(data.name); // 正确输出 "Alice"
</script>
如果数据来自请求体(如 POST JSON),注意解码后要重新序列化
Gin 的 c.BindJSON 或 c.ShouldBindJSON 解出来的 Go 结构体,不能直接传给模板并期望它自动变成 JSON 字符串 —— 模板渲染的是 Go 值的字符串表示(类似 fmt.Sprint),不是 JSON。
- 错误做法:
c.HTML(200, "page.html", gin.H{"raw": reqBody})→ 模板里{{ .raw }}输出的是 Go map 格式,不是 JSON - 正确做法:解码后立刻
json.Marshal回去,再转template.JS - 若原始 JSON 中含 HTML 特殊字符(如用户输入的
<script>),json.Marshal已做双引号和反斜杠转义,template.JS不会额外破坏,是安全的
避免用 {{.jsonData | safeJS}} 这种方式
有些文章建议在模板里加 safeJS 函数,但这需要自己注册函数,且容易误用 —— 比如传入未经过 json.Marshal 的任意字符串,就可能引入 XSS。
-
safeJS只是取消转义,不做内容校验 - 不如在后端严格控制输入:只对明确由
json.Marshal产出的内容转template.JS - Gin 本身不提供
safeJS,需手动定义,增加维护成本
真正难的不是怎么传,是怎么确保传进去的那串字符确实是合法、已转义、且来源可信的 JSON —— 所有绕过 json.Marshal → template.JS 链路的操作,都容易在边界 case 上翻车。


















