golang中json转换的特殊字符转义问题
作者:Moshow郑锴
前言
在日常的 Go 开发中,encoding/json 包是最常用的 JSON 序列化与反序列化工具。然而,很多开发者在使用过程中会遇到一个“隐蔽的坑”:特殊字符被自动转义。这不仅可能导致前后端数据不一致,还可能在 API 返回结果中引发解析异常。

本文将深入分析 Go 中 JSON 转义的机制、常见问题场景,并给出实用的解决方案。
1. 问题复现
来看一个简单的例子:
package main
import (
"encoding/json"
"fmt"
)
type Data struct {
Link string `json:"link"`
}
func main() {
data := Data{Link: "https://zhengkai.blog.csdn.net/?a=1&b=2"}
b, _ := json.Marshal(data)
fmt.Println(string(b))
}
输出结果为:
{"link":"https://zhengkai.blog.csdn.net/?a=1\u0026b=2"}可以看到,& 被转义成了 \u0026。如果客户端严格依赖原始 URL,这种转义会导致 请求参数丢失或解析失败。
2. 转义的原因
Go 的 encoding/json 默认会对 HTML 特殊字符进行转义,目的是 防止 XSS 攻击。例如:
<→\u003c>→\u003e&→\u0026
这种设计在 Web 安全层面是合理的,但在 API 返回 JSON 时,往往会带来兼容性问题。
3. 可能导致的异常
- URL 参数丢失:客户端无法正确解析
&,导致 query 参数被截断。 - 配置文件错误:当 JSON 用于配置存储时,转义字符可能导致解析失败。
- 跨系统交互异常:某些语言或框架的 JSON 解析器不支持
\u0026,从而报错。
4. 解决方案
✅ 方法一:关闭 HTML 转义
使用 json.Encoder 并调用 SetEscapeHTML(false):
encoder := json.NewEncoder(os.Stdout) encoder.SetEscapeHTML(false) _ = encoder.Encode(data)
输出结果为:
{"link":"https://zhengkai.blog.csdn.net/?a=1&b=2"}✅ 方法二:自定义序列化函数
func MarshalNoEscape(v interface{}) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
err := enc.Encode(v)
return buf.Bytes(), err
}
这样可以在项目中统一调用,避免重复配置。
5. 最佳实践
- API 返回 JSON 时,建议关闭 HTML 转义,保证数据一致性。
- 涉及安全场景(如模板渲染),保留转义以防止 XSS。
- 团队规范:在编码规范中明确 JSON 转义策略,避免线上 bug。
6.为什么是可逆过程?
- JSON标准兼容:
\u003c、\u003e、\u0026等都是完全有效的JSON转义序列 - 双向转换:
json.Marshal和json.Unmarshal是设计为可逆操作 - 自动处理:在解析时,JSON解码器会自动将转义序列转换回原始字符
package main
import (
"encoding/json"
"fmt"
)
func main() {
original := map[string]string{
"html": "<div>Hello & World</div>",
"sql": "SELECT * FROM users WHERE age > 18",
}
fmt.Println("=== 原始数据 ===")
fmt.Printf("html: %s\n", original["html"])
fmt.Printf("sql: %s\n", original["sql"])
// 第一次 Marshal(默认转义)
jsonData1, _ := json.Marshal(original)
fmt.Println("\n=== 第一次 Marshal 后的 JSON ===")
fmt.Println(string(jsonData1))
// 保存为字符串
savedString := string(jsonData1)
fmt.Println("\n=== 保存的字符串 ===")
fmt.Println(savedString)
// 从字符串再次解析为 JSON
var parsedData map[string]string
err := json.Unmarshal([]byte(savedString), &parsedData)
if err != nil {
fmt.Printf("解析错误: %v\n", err)
} else {
fmt.Println("\n=== 解析后的数据 ===")
fmt.Printf("html: %s\n", parsedData["html"])
fmt.Printf("sql: %s\n", parsedData["sql"])
}
// 验证数据是否一致
fmt.Println("\n=== 数据一致性验证 ===")
fmt.Printf("html 字段是否一致: %t\n", original["html"] == parsedData["html"])
fmt.Printf("sql 字段是否一致: %t\n", original["sql"] == parsedData["sql"])
}=== 原始数据 ===
html: <div>Hello & World</div>
sql: SELECT * FROM users WHERE age > 18
=== 第一次 Marshal 后的 JSON ===
{"html":"\u003cdiv\u003eHello \u0026 World\u003c/div\u003e","sql":"SELECT * FROM users WHERE age \u003e 18"}
=== 保存的字符串 ===
{"html":"\u003cdiv\u003eHello \u0026 World\u003c/div\u003e","sql":"SELECT * FROM users WHERE age \u003e 18"}
=== 解析后的数据 ===
html: <div>Hello & World</div>
sql: SELECT * FROM users WHERE age > 18
=== 数据一致性验证 ===
html 字段是否一致: true
sql 字段是否一致: true7.跨语言转换要注意的点⚠️
- 统一规范:在接口文档中明确字段类型、编码、时间格式。
- 避免隐式转换:大整数、浮点数、布尔值尽量用字符串或明确类型。
- 测试覆盖:跨语言接口必须做双向序列化/反序列化测试。
- 安全性:避免在 JSON 中传递 HTML 片段,必要时使用转义。
7.1 数值精度问题
大整数丢失精度: JavaScript 的
Number类型基于 IEEE 754 双精度浮点数,最大安全整数为2^53-1。如果 Go 或 Java 传递了超过该范围的整数,JS 端会出现精度丢失。 解决方案:将大整数以 字符串 形式传递。
7.2字符编码差异
- JSON 标准要求使用 UTF-8 编码,但部分语言库可能默认使用 UTF-16 或 Latin-1。
- 如果包含中文、emoji 等字符,可能出现乱码或解析失败。 建议:统一使用 UTF-8,并在接口文档中明确说明。
7.3特殊字符转义
- Go 默认会将
<、>、&转义为\u003c、\u003e、\u0026。 - Python、JavaScript 等语言通常不会这样做。 风险:跨语言传递时,接收端可能无法正确还原原始字符串。 解决方案:在 Go 中使用
SetEscapeHTML(false)关闭转义。
结语
Go 的 JSON 转义机制体现了语言对安全的重视,但在实际业务中,开发者需要根据场景灵活调整。通过 SetEscapeHTML(false) 或自定义函数,我们可以在 安全性与兼容性之间找到平衡。
到此这篇关于golang中json转换的特殊字符转义问题的文章就介绍到这了,更多相关golang json 特殊字符转义内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
