go 建议:编码/json:将TextUnmarshaler/Unmarshaler的错误 Package 起来,以便在输入中定位问题,

ztmd8pv5  于 5个月前  发布在  Go
关注(0)|答案(4)|浏览(51)

问题:

目前,在使用 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
所以也许解决方案可能在某种程度上相似(针对每种情况都有一个特殊的错误类型?),并且可以一起实现。

syqv5f0l

syqv5f0l1#

遗憾的是,这是一个破坏性的变化,因为目前人们可以在 json.Unmarshal 返回的错误上进行平等检查(==)。(是的,他们应该使用 errors.Is ,但没有保证他们会这样做,尤其是因为他们的代码可能早于 errors.Is 的存在。)

juud5qan

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.

xfb7svmp

xfb7svmp3#

感谢carlmjohnson指出这个问题。这是一个大规模的重新设计,我猜没有人知道encoding/json(/v2)中何时会出现这个功能。

看起来是为了解决这个问题而设计的,但尚未实现:代码相同,只是将json包替换为"github.com/go-json-experiment/json",输出结果为

json: cannot unmarshal JSON string into Go value of type main.CustomType: test err
&json.SemanticError{requireKeyedLiterals:json.requireKeyedLiterals{}, nonComparable:json.nonComparable{}, action:"unmarshal", ByteOffset:0, JSONPointer:"", JSONKind:0x22, GoType:(*reflect.rtype)(0x4ee120), Err:(*errors.errorString)(0x5d7210)}

JSONPointer目前为空,但根据描述,它将包含指向有问题的json字段的路径(包括切片索引)。

感谢rittneje指出这一点,我甚至不知道有的时候没有errors.Is。如果不是因为这个,我个人甚至不会认为这是一个破坏性的变化(Is / As "应该"被使用)。

有了这个,我能想到的唯一非破坏性变化就是向json.Decoder添加一个类似于.DisallowUnknownFields()UseNumber()的设置(这两个是唯一的两个设置)——类似于.DetailedErrors()的新行为只会在这样的设置下发生。而且这种新行为也不会在json.Unmarshal下可用。

ssgvzors

ssgvzors4#

这个特性对于实现json.Unmarhaler并使用json.Unmarshal(递归)的类型特别有帮助。目前还无法传播和重新上下文化在内部json.Unmarshal中发生的解组错误的位置。
由于这个原因,我自己编写的包github.com/dolmen-go/jsonptrerror(旨在用JSON指针丰富JSON解组错误)的实用性相当有限。

相关问题