踩到 Go 的 json 解析坑了,如何才能严格解析 json?

2023 年 9 月 19 日
 BeautifulSoap

精准踩中了 json 解析包的两个坑导致了生产环境出错

假设有下面结构体定义

type Data struct {
	A   string `json:"a"`
	B   int   `json:"b`
	Obj struct {
		AA string `json:"aa"`
		BB int    `json:"bb"`
	} `json:"obj"`
}

使用json.Unmarshal() 解析下列几种 json

{"a":null, "b": null, "obj":null}
{"obj": null}
{"a": "a"}
{"a": "a","z":"z"}
{}
{"obj": {}}

问:解析哪个 json 会报错?

答:全都不报错都正确解析

都是不出事就注意不到的问题。尤其非指针类型字段,我下意识认为遇到 null 是会直接报错的,结果直接是当作不存在(undefined)来处理。。。

so ,go 下怎么才能简单地进行严格 json 解析?要求

  1. 不允许出现未知字段,出现则报错(这个似乎倒是可以用 json 包的 DisallowUnknownFields 简单做到)
  2. 非指针字段不允许传入 null ,否则报错(似乎 json 包没法简单做到)
17778 次点击
所在节点    Go 编程语言
211 条回复
ding2dong
2023 年 9 月 21 日
@BeautifulSoap 实际上我早就注意到这个问题了,比如需要局部跟新某些字段,如果不特殊处理就会把其他字段更新为 0 值。这时候用指针类型来解析就是了。但这种情形确实比较少,遇到了就特殊处理。至于说解析到 null 报错,那就离谱了,到时候又说,别的语言可以解析 null ,go 怎么报错 balabala 的

-------------------------
@ding2dong 你说的很好,没错, null 对应 go 的 nil ,而在 go 中要给 int 类型赋 nil 值是会报错的。所以按照你说的意思,将 nil 解析到 int 字段报错不应该是最符合直觉的行为吗?
jsboy
2023 年 9 月 21 日
@Terminl 有个点不对,不是 json 解析赋值 0 ,是 null 不处理,导致 go 语言采用了默认值。所以其实不能说 json 解析的问题。对于 json 来说为 null 是不存在的意思。
-----------------------
这个问题是其实 java 也有,如果用 int 而不用 Integer ,null 值结果也是 0 ,不同的是 java 中 Integer 的使用和 int 不会有太大的区别,可以自动装箱 拆箱,所以一般都会把字段定义为 Integer 这样的类型。

个人觉得,go 如果也能像 java 一样在指针类型和普通类型的转换上变得方便,采用指针定义属性类型应该还是正确的解决方式,而且 json 中属性可以为 null ,能和 go 中属性为指针类型对应。也更容易理解。

另外自己修改了基础库后续的升级更新也会带来麻烦,只是这个麻烦不在现在,或者说你觉得麻烦更小,但没必要说服别人要和你一样使用这样的解决方案。
kiwi95
2023 年 9 月 21 日
@BeautifulSoap #173 如果牵涉面这么广的话,魔改 json 包很可能 break 别的功能吧,基于你的需求,写了一个 toy validator 刚传到 GH ,通过 strcut tag 在`json.Unmarshal` 之前验证: https://github.com/wuxu92/json-validator
xiaocaiji111
2023 年 9 月 21 日
go 的零值是很恶心有时候,不报错,但是业务逻辑是不对的。有时候分不清是没传值导致的零值的 0 ,还是业务上的 0 。只能用指针解决。
特别是跟外部交互,比如 http 接口,rpc 调用,操作数据库。
myderr
2023 年 9 月 21 日
我测试了一些 c#的 Newtonsoft.Json ,发现:
1 、如果 json 字符串里面没有 IntA 这个字段,对象里面的 IntA 字段是默认值 0
2 、如果 json 字符串里面有 IntA 并且是 null ,解析报错 converting value {null} to type 'System.Int32'. Path 'IntA', line 1, position 45.”
Nugine0
2023 年 9 月 21 日
@snowlyg #180 go 官方不可能采纳的,想想也知道这种标准库行为没法改的吧
snowlyg
2023 年 9 月 21 日
@Nugine0 所以官方不采纳就自己写一个第三方库咯,然后分享一下自己的库。如果单纯的吐槽真的是没啥意义。
Nugine0
2023 年 9 月 21 日
@snowlyg #187 有些回复确实离谱,难怪 op 上火
wwwuser
2023 年 9 月 21 日
看了下官方 Unmarshal 的规则,// To unmarshal JSON into an interface value,
// Unmarshal stores one of these in the interface value:
//
// bool, for JSON booleans
// float64, for JSON numbers
// string, for JSON strings
// []interface{}, for JSON arrays
// map[string]interface{}, for JSON objects
// nil for JSON null
//
wwwuser
2023 年 9 月 21 日
也就 Unmarshal 到非 interface{} 会产生 null 和 0 分辨不清楚,所以先 Unmarshal 到 interface{}, 做一下校验就 OK 了,没必要改官方包
wwwuser
2023 年 9 月 21 日
// The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean
// “not present,” unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error.
所以先把原始 json 数据想办法 unmarshal 到 interface, map, pointer, or slice 这些里面做个校验就行,改官方包也算是一种方法吧, 只是觉得没必要大动干戈改官方包。
dqzcwxb
2023 年 9 月 21 日
其他语言有问题就是语言问题
golang 有问题就是人有问题
aababc
2023 年 9 月 21 日
@dqzcwxb 老哥,真相了,虽然我也写 go ,但是对这门语言确实很难有认同感
snowlyg
2023 年 9 月 21 日
@Nugine0 是的,所以我一般都不会自讨没趣。网上是很难找到认同感的。
meioco3
2023 年 9 月 21 日
@leoleoasd 把类型设为 *int 就可以区分 {"a": 0} 和 {"a": null } , go 语言的结构体包括变量初始化后是零值很合理。这个不是 json 库的问题,你总不能要求 go 给你将 int 类型的变量或字段储存一个 nil 或 null 吧 ?
thevita
2023 年 9 月 21 日
问题还是 go 的 部分 value type 没有 null, 这点与 json 不一致,要弥合这点,

1. 让 go type 去 match json ,可以引入 Null* 的 type, NullString, NullNumber 什么的,
2. 让 json match go type, 依然通过 NewType 实现 UnmarshalJSON 来验证数据呗
codersdp1
2023 年 9 月 21 日
@kiwi95 #183 改动太多,不符合 op 要求。
codersdp1
2023 年 9 月 21 日
@Terminl #171 自以为会解析报错,结果不报错,那只能给语言背锅咯。
hxtheone
2023 年 9 月 21 日
翻下来感觉最根本的原因还是 go 的值类型有默认的空值并且不能为 nil 也没有 optional 之类的容器, 我见过的 go 新手或多或少都会踩到这个坑, 比如 json null 不解析, gorm 里空值不更新数据库这样的问题. 没办法语言的底层就是这样设计, go 也在不违反这个设计的前提下提供自定义 marshaller/unmarshaller 来实现个性化的需求, 嫌麻烦的话引第三方包或换语言呗
thevita
2023 年 9 月 21 日
真要说起来,
@hxtheone 是的,就是 type system 不一致的原因,类似的情况其他语言也一样会存在,同样的问题 sql 也一样存在,完全可以参考 sql 的解决方案呗,扯半天扯不到点上。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://v2ex.ih06.com/t/975214

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX