首页 话题 小组 问答 好文 用户 我的社区 域名交易 唠叨

[交流][Golang] 反序列化中的隐式转换设计问题讨论

发布于 2025-03-23 00:46:17
0
28

背景

在反序列化的时候,需要一个 interface 用来接收反序列化的内容,如下代码所示

func doNormal(data []byte) (*Student, error) {
    s := &Student{}
    if err := json.Unmarshal(data, s); err != nil {
    	return nil, err
    }
    return s, nil
}

然而为了使代码更加简化,习惯性得使用了命名返回值,导致反序列化失败

func doBug(data []byte) (s *Student, _ error) {
    return s, json.Unmarshal(data, s)
}

原因是因为 json.Unmarshal 中发现 s 是一个 nil ,直接 return error, 但将参数改成二级指针就 work 了

func doSimple(data []byte) (s *Student, _ error) {
    return s, json.Unmarshal(data, &s)
}

于是有了几个疑问:

  1. 这里二级指针能 work 的原因是啥?

  2. 这里二级指针指向的也是一个 nil 的一级指针,它能 work ,为啥直接使用一个 nil 的一级指针不行,这样设计的原因是啥?

开始分析

通过分析 json.Unmarshal 的代码可以发现 indirect 函数,这个函数会将二级指针反解成一级指针,并且发现一级指针是 nil 的时候,会初始化一个 Student

大概过程是如下所示

var p * Student
var pp ** Student = &p

v := reflect.ValueOf(pp).Elem() // v is nil

v.Set(reflect.New(v.Type().Elem())) // v is not nil

问题

  1. 使用二级指针的方式还有啥坑吗,大家是怎么简化反/序列化代码的呢?

  2. 这里二级指针指向的也是一个 nil 的一级指针,而直接使用一个 nil 的一级指针就直接报错,这样设计的原因是啥?


完整的 indirect 代码如下

// indirect walks down v allocating pointers as needed,
// until it gets to a non-pointer.
// If it encounters an Unmarshaler, indirect stops and returns that.
// If decodingNull is true, indirect stops at the first settable pointer so it
// can be set to nil.
func indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
	// Issue #24153 indicates that it is generally not a guaranteed property
	// that you may round-trip a reflect.Value by calling Value.Addr().Elem()
	// and expect the value to still be settable for values derived from
	// unexported embedded struct fields.
	//
	// The logic below effectively does this when it first addresses the value
	// (to satisfy possible pointer methods) and continues to dereference
	// subsequent pointers as necessary.
	//
	// After the first round-trip, we set v back to the original value to
	// preserve the original RW flags contained in reflect.Value.
	v0 := v
	haveAddr := false

	// If v is a named type and is addressable,
	// start with its address, so that if the type has pointer methods,
	// we find them.
	if v.Kind() != reflect.Pointer && v.Type().Name() != "" && v.CanAddr() {
		haveAddr = true
		v = v.Addr()
	}
	for {
		// Load value from interface, but only if the result will be
		// usefully addressable.
		if v.Kind() == reflect.Interface && !v.IsNil() {
			e := v.Elem()
			if e.Kind() == reflect.Pointer && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Pointer) {
				haveAddr = false
				v = e
				continue
			}
		}

		if v.Kind() != reflect.Pointer {
			break
		}

		if decodingNull && v.CanSet() {
			break
		}

		// Prevent infinite loop if v is an interface pointing to its own address:
		//     var v interface{}
		//     v = &v
		if v.Elem().Kind() == reflect.Interface && v.Elem().Elem() == v {
			v = v.Elem()
			break
		}
		if v.IsNil() {
			v.Set(reflect.New(v.Type().Elem()))
		}
		if v.Type().NumMethod() > 0 && v.CanInterface() {
			if u, ok := v.Interface().(Unmarshaler); ok {
				return u, nil, reflect.Value{}
			}
			if !decodingNull {
				if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
					return nil, u, reflect.Value{}
				}
			}
		}

		if haveAddr {
			v = v0 // restore original value after round-trip Value.Addr().Elem()
			haveAddr = false
		} else {
			v = v.Elem()
		}
	}
	return nil, nil, v
}
评论
一个月内的热帖推荐
站长交流