问题:
目前,在使用 json.Unmarshal()
(或 json.NewDecoder().Decode()
)解析 JSON 时,如果一个值被解析到实现了 encoding.TextUnmarshaler
(或 json.Unmarshaler
)的结构体字段中,而 .UnmarshalText()
返回错误,这个错误将从整个 json.Unmarshal
函数中不变地返回:
https://go.dev/play/p/G5ag-Cq2OMf
type CustomType string
func (p *CustomType) UnmarshalText(_ []byte) error {
return errors.New("test err")
}
type A struct {
B B `json:"b"`
}
type B struct {
Cs []C `json:"cs"`
}
type C struct {
D CustomType `json:"d"`
}
func main() {
a := A{}
err := json.Unmarshal([]byte(`{"b":{"cs":[{"d": "val"}]}}`), &a)
fmt.Println(err)
fmt.Printf("%#v\n", err)
}
输出:
test err
&errors.errorString{s:"test err"}
这样一来,当输入较大/包含多个字段(其中一些嵌套)时,调用者无法在输入的 JSON 中定位错误。
与输入 JSON 中的类型与结构体字段类型不兼容时的类似错误进行比较:
https://go.dev/play/p/ZgexFrs-r_c
// same type definitions
func main() {
a := A{}
err := json.Unmarshal([]byte(`{"b":{"cs":[{"d": 0}]}}`), &a)
fmt.Println(err)
fmt.Printf("%#v\n", err)
}
输出:
json: cannot unmarshal number into Go struct field C.b.cs.d of type main.CustomType
&json.UnmarshalTypeError{Value:"number", Type:(*reflect.rtype)(0x4b5460), Offset:19, Struct:"C", Field:"b.cs.d"}
在这种情况下,返回的错误包含大量有用的信息来定位输入中的问题,特别是包含 JSON 中有问题值的路径的 Field
。
建议:
与 json.UnmarshalTypeError
类似,当 json.Unmarshal
从 .UnmarshalText()
或 .UnmarshalJSON()
获得错误时,错误将被 Package 到类似的结构中,例如(当前不存在,建议的错误) &json.UnmarshalerError{Type:(*reflect.rtype)(0x4b5460), Offset:19, Struct:"C", Field:"b.cs.d", Err: &errors.errorString{s:"test err"}}
。与 json.UnmarshalTypeError
相比,Value
被移除,因为它可能没有用处(如果需要,可以由字段类型的 unmarshal 方法添加),但保留了原始错误的 Err
,以便原始错误仍然可用(或可不可用)。(此外,Field:"b.cs.d"
可以是 Field:"b.cs[0].d"
以更好地在 JSON 列表中定位错误,但为了与 json.UnmarshalTypeError
保持一致,它仍然是相同的,即 Field:"b.cs.d"
)
注意
我看到最近有一个非常相似的提案被提交了 - 返回一个更友好的错误,尽管是在不同的场景下(不允许未知字段):#58649
所以也许解决方案可能在某种程度上相似(针对每种情况都有一个特殊的错误类型?),并且可以一起实现。
4条答案
按热度按时间syqv5f0l1#
遗憾的是,这是一个破坏性的变化,因为目前人们可以在
json.Unmarshal
返回的错误上进行平等检查(==
)。(是的,他们应该使用errors.Is
,但没有保证他们会这样做,尤其是因为他们的代码可能早于errors.Is
的存在。)juud5qan2#
See https://github.com/go-json-experiment/json in which some members of the Go team were working on an experiment to create a new version of encoding/json. ISTM as an outsider that an improvements to encoding/json will have to run through that package before getting into the standard library.
xfb7svmp3#
感谢carlmjohnson指出这个问题。这是一个大规模的重新设计,我猜没有人知道
encoding/json
(/v2
)中何时会出现这个功能。看起来是为了解决这个问题而设计的,但尚未实现:代码相同,只是将
json
包替换为"github.com/go-json-experiment/json",输出结果为。
JSONPointer
目前为空,但根据描述,它将包含指向有问题的json字段的路径(包括切片索引)。感谢rittneje指出这一点,我甚至不知道有的时候没有
errors.Is
。如果不是因为这个,我个人甚至不会认为这是一个破坏性的变化(Is
/As
"应该"被使用)。有了这个,我能想到的唯一非破坏性变化就是向json.Decoder添加一个类似于
.DisallowUnknownFields()
或UseNumber()
的设置(这两个是唯一的两个设置)——类似于.DetailedErrors()
的新行为只会在这样的设置下发生。而且这种新行为也不会在json.Unmarshal
下可用。ssgvzors4#
这个特性对于实现
json.Unmarhaler
并使用json.Unmarshal
(递归)的类型特别有帮助。目前还无法传播和重新上下文化在内部json.Unmarshal
中发生的解组错误的位置。由于这个原因,我自己编写的包
github.com/dolmen-go/jsonptrerror
(旨在用JSON指针丰富JSON解组错误)的实用性相当有限。