前端多传了字段、写错了字段名,Gin 默认静悄悄地忽略;想要严格怎么办?大整数放在 map 里为什么变了?这篇用同一批请求体测 Gin 的 JSON 绑定默认有多宽松,打开 DisallowUnknownFields 与 UseNumber 后行为怎么变,以及这两个全局开关的影响面。
binding/json.go 的 decodeJSON(Gin v1.12.0):
44 | func decodeJSON(r io.Reader, obj any) error {
45 | decoder := json.API.NewDecoder(r)
46 | if EnableDecoderUseNumber {
47 | decoder.UseNumber()
48 | }
49 | if EnableDecoderDisallowUnknownFields {
50 | decoder.DisallowUnknownFields()
51 | }
52 | if err := decoder.Decode(obj); err != nil {
53 | return err
54 | }
55 | return validate(obj)
56 | }
{"name":"ann","age":30} → {Name:ann Age:30} err=<nil>
{"name":"ann","age":30,"extra":1,"nested":{"a":[1,2]}} → {Name:ann Age:30} err=<nil>
{"NAME":"ann","Age":30} → {Name:ann Age:30} err=<nil>
{"name":"a","name":"b"} → {Name:b Age:0} err=<nil>
{"name":"ann","age":30} {"name":"second"} → {Name:ann Age:30} err=<nil>
{"name":"ann","age":30} garbage → {Name:ann Age:30} err=<nil>
{"name":"ann","age":30}}}} → {Name:ann Age:30} err=<nil>
{"name":"ann","age":30.0} → {Name:ann Age:0} err=json: cannot unmarshal number 30.0 into Go struct field P.age of type int
{"name":"ann","age":30.5} → {Name:ann Age:0} err=json: cannot unmarshal number 30.5 into Go struct field P.age of type int
{"name":"ann","age":"30"} → {Name:ann Age:0} err=json: cannot unmarshal string into Go struct field P.age of type int
{"name":null,"age":null} → {Name: Age:0} err=<nil>
对照 json.Unmarshal 同一个“带垃圾”的输入 → {Name: Age:0} err=invalid character 'g' after top-level valueGin 的 decodeJSON 用 Decode 解码第一个 JSON 值,不检查后面还有没有内容,所以拼接了垃圾的请求体照样通过(与 json.Unmarshal 不同)。多余字段、大小写不一致、重复键都不报错;类型对不上(30.0 给 int、字符串给 int)则会报错。
{"name":"ann"} → {Name:ann In:{Z:0} Items:[]} err=<nil>
{"name":"ann","extra":1} → {Name:ann In:{Z:0} Items:[]} err=json: unknown field "extra"
{"name":"ann","in":{"z":1,"y":2}} → {Name:ann In:{Z:1} Items:[]} err=json: unknown field "y"
{"items":[{"z":1},{"q":2}]} → {Name: In:{Z:0} Items:[{Z:1} {Z:0}]} err=json: unknown field "q"
{"NAME":"ann"} → {Name:ann In:{Z:0} Items:[]} err=<nil>
{"name":"ann"} garbage → {Name:ann In:{Z:0} Items:[]} err=<nil>把 binding.EnableDecoderDisallowUnknownFields 设为 true,未知字段(包括嵌套对象和数组元素里的)会报 json: unknown field。但错误之前的字段已经赋值,并且键的大小写、末尾垃圾仍然宽松,所以它只是“防止拼错字段名”,不是完整的严格校验。
UseNumber=false → ID=9.007199254740992e+15(float64) m.big=1.2345678901234567e+19(float64) m.f=1.1 m.small=7(float64) I64=9007199254740993 err=<nil>
UseNumber=true → ID=9007199254740993(json.Number) m.big=12345678901234567890(json.Number) m.f=1.10 m.small=7(json.Number) I64=9007199254740993 err=<nil>
对照:9007199254740993 的 float64 值 = 9007199254740992interface{} 里的 JSON 数字默认是 float64,超过 253 的整数会静默丢精度。EnableDecoderUseNumber 让它们变成 json.Number 保持原文。声明成 int64 的字段本来就精确,不需要这个开关;只在你确实要用 map[string]any 接收 ID 之类大整数时才有用。
DisallowUnknownFields=false:
/a ShouldBindJSON err=<nil>
/b ShouldBindBodyWith err=<nil>
/c ShouldBind err=<nil>
DisallowUnknownFields=true :
/a ShouldBindJSON err=json: unknown field "extra"
/b ShouldBindBodyWith err=json: unknown field "extra"
/c ShouldBind err=json: unknown field "extra"
DisallowUnknownFields=false:
/a ShouldBindJSON err=<nil>
/b ShouldBindBodyWith err=<nil>
/c ShouldBind err=<nil>
只想让某一个接口严格:自己用 json.NewDecoder + DisallowUnknownFields,不用全局开关:
400 json: unknown field "extra"这两个变量是包级别的,一旦设置,整个进程的所有 JSON 绑定都按新规则走,包括第三方库里调用的 ShouldBindJSON;在运行中切换更会让同一个接口前后行为不一致。只想让个别接口严格时,在该接口里自己创建 json.Decoder 最安全。