非标json指违反rfc 7159规范的json,如单引号字符串、尾逗号、nan等;gin默认用json.unmarshal解析,因其严格遵循规范,遇非法格式直接报错,不提供原始字节访问。

非标JSON是什么,Gin默认为什么解析失败
Gin 默认用 json.Unmarshal 解析请求体,它严格遵循 RFC 7159 —— 比如不接受尾部逗号、单引号字符串、undefined、NaN、Infinity、注释、重复键(后者实际会覆盖)。一旦遇到这些,c.ShouldBindJSON() 直接返回 json: cannot unmarshal 类错误,且无法获取原始字节。
这不是 Gin 的 bug,是 Go 标准库的故意设计:安全优先,拒绝模糊输入。但现实接口(尤其老旧系统、前端直传、第三方 webhook)常带单引号、尾逗号、甚至 NaN 字面量。
绕过标准 JSON 解析:用 go-json 或 jsoniter 替换底层解码器
Gin 允许替换 JSON 引擎,关键在于重写 gin.JSONMIME 和绑定逻辑:
- 引入
github.com/goccy/go-json(支持单引号、尾逗号、NaN,性能接近原生)或github.com/json-iterator/go - 在 main 初始化时调用
gin.SetMode(gin.ReleaseMode)后,立即设置:gin.JSONMIME = "application/json" gin.DefaultWriter = os.Stdout // 不影响 // 替换解码器(go-json 示例) jsoniter.RegisterTypeEncoderFunc("string", func(encodeFunc jsoniter.ValEncoder, v interface{}, stream *jsoniter.Stream) { encodeFunc(v, stream) }) // 更推荐:全局替换 gin 的 BindJSON 行为 - 更稳妥的做法是**不依赖
c.ShouldBindJSON()**,改用c.GetRawData()手动解码:data, err := c.GetRawData() if err != nil { c.AbortWithStatusJSON(400, gin.H{"error": "read body failed"}) return } var payload map[string]interface{} err = jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, &payload) if err != nil { c.AbortWithStatusJSON(400, gin.H{"error": err.Error()}) return }
注意:go-json 对 NaN 默认仍报错,需显式启用:json.UnmarshalOptions{AllowNaN: true};jsoniter 则默认允许。
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
处理 NaN / Infinity 等非法浮点字面量
标准 Go float64 无法表示 NaN 或 Infinity,json.Unmarshal 遇到直接报错。解决路径只有两条:
- 用字符串字段接收,后续手动转:
type Req struct { Value string `json:"value"` } // 然后用 strconv.ParseFloat(value, 64) + math.IsNaN() 判断 - 用
json.RawMessage延迟解析:type Req struct { Value json.RawMessage `json:"value"` } // 后续用 jsoniter.Unmarshal(Value, &targetFloat) 并捕获 error - 完全放弃 float 类型,统一用
*big.Float或自定义类型封装 —— 仅限金融等精度敏感场景,多数情况没必要
别试图用 json.Number,它只处理数字字符串,对 NaN 无效。
调试非标JSON输入的最快方法
别猜,先看原始字节:
- 加中间件打印 raw body(仅开发环境):
c.Next() if c.Request.Method == "POST" || c.Request.Method == "PUT" { data, _ := c.GetRawData() fmt.Printf("[DEBUG] raw body: %s\n", string(data)) c.Request.Body = io.NopCloser(bytes.NewBuffer(data)) // 恢复 body 供后续读取 } - 用 curl 发送典型非标样例验证:
curl -X POST http://localhost:8080/api \ -H "Content-Type: application/json" \ -d "{'name': 'test', 'age': 25,}"(单引号 + 尾逗号) - 错误日志里重点盯
json: cannot unmarshal .* into Go struct后面的字符 —— 那就是非法 token 的位置
真实接口里,最常踩的坑是前端用 JSON.stringify(obj, null, 2) 后手动删了换行再发,结果留了末尾逗号;或者 Python 后端用 json.dumps(..., allow_nan=True) 输出了 NaN。这些都得在接收端兜底,而不是指望上游改。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










