Golang中UnmarshalTypeError不再提供Field name的问题

Golang中UnmarshalTypeError不再提供Field name的问题 在我的代码中,我为特定类型使用了自定义解组器。当发生错误时,我的代码会返回一个 json.UnmarshalTypeError

这允许在类型与标准 JSON 解组器一起在结构中使用时,检索发生错误的字段名称

自从我今天迁移到 go1.11 后,我的代码不再允许检索字段名称(在 go1.10.3 中正常)

查看新版本的 decode.go,我未能找到针对此行为变化的简单解决方法

这是一个简单的示例,展示了该行为:

package main

import (
	"encoding/json"
	"fmt"
	"strings"
)

type MyString string

func (m *MyString) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return &json.UnmarshalTypeError{Value: string(data), Type: reflect.TypeOf(m)}
	}
	*m = MyString(s)
	return nil
}

type MyStruct struct {
	Field MyString
}

func main() {
	var m MyStruct
	err := json.NewDecoder(strings.NewReader(`{"Field": 123}`)).Decode(&m)
	if err != nil {
		if e, ok := err.(*json.UnmarshalTypeError); ok {
			fmt.Printf("Field: %s\n", e.Field)
		}
	}
}

这是回归问题还是不幸消失的意外行为?

感谢您的帮助


更多关于Golang中UnmarshalTypeError不再提供Field name的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

感谢提供的链接,在提交这个求助请求之前我确实已经看到了这个问题
不幸的是,这并不完全是同一个问题

而且我测试了建议的修复方案,但这并不能恢复之前的行为

更多关于Golang中UnmarshalTypeError不再提供Field name的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我认为这个问题正在这里被跟踪:https://github.com/golang/go/issues/27275

在Go 1.11中,json.UnmarshalTypeErrorField字段确实被移除了,这是有意为之的变更。根据Go的发布说明和源代码变更,这个字段被认为是一个实现细节,不应该在公共API中暴露。

当使用自定义解组器时,如果需要获取字段名称,可以通过在自定义解组器中捕获上下文信息来实现。以下是修改后的示例代码:

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
)

type MyString string

func (m *MyString) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		// 创建自定义错误类型来包含字段信息
		return &CustomUnmarshalTypeError{
			Value: string(data),
			Type:  reflect.TypeOf(m),
			Field: "Field", // 在实际应用中需要通过其他方式获取字段名
		}
	}
	*m = MyString(s)
	return nil
}

// 自定义错误类型来保留字段信息
type CustomUnmarshalTypeError struct {
	Value string
	Type  reflect.Type
	Field string
}

func (e *CustomUnmarshalTypeError) Error() string {
	return fmt.Sprintf("json: cannot unmarshal %s into Go struct field %s of type %s", 
		e.Value, e.Field, e.Type)
}

type MyStruct struct {
	Field MyString
}

func main() {
	var m MyStruct
	err := json.NewDecoder(strings.NewReader(`{"Field": 123}`)).Decode(&m)
	if err != nil {
		if e, ok := err.(*CustomUnmarshalTypeError); ok {
			fmt.Printf("Field: %s\n", e.Field)
			fmt.Printf("Error: %s\n", e.Error())
		} else {
			fmt.Printf("Other error: %s\n", err)
		}
	}
}

在实际应用中,可以通过以下方式在自定义解组器中获取字段名称:

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
	"runtime"
	"strings"
)

type FieldAwareUnmarshaler struct {
	FieldName string
}

func (f *FieldAwareUnmarshaler) setFieldName(name string) {
	f.FieldName = name
}

type MyString string

func (m *MyString) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		// 通过调用栈分析获取字段信息(简化示例)
		return &CustomUnmarshalTypeError{
			Value: string(data),
			Type:  reflect.TypeOf(m),
			Field: getCallerField(), // 需要实现此函数来获取字段名
		}
	}
	*m = MyString(s)
	return nil
}

// 简化的字段名获取函数(实际实现会更复杂)
func getCallerField() string {
	pc := make([]uintptr, 10)
	runtime.Callers(2, pc)
	frames := runtime.CallersFrames(pc)
	
	for {
		frame, more := frames.Next()
		if strings.Contains(frame.Function, "UnmarshalJSON") {
			continue
		}
		// 这里可以解析frame来获取字段信息
		break
	}
	return "Field" // 返回硬编码值作为示例
}

这个变更不是回归问题,而是Go团队有意移除的实现细节。建议在需要字段信息的场景中使用自定义错误类型来替代。

回到顶部