在反序列化的时候,需要一个 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)
}
于是有了几个疑问:
通过分析 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
完整的 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
}
![]() |
1
PTLin 2 天前
印象里 go 的命名返回值会带来一系列奇葩问题,在我眼里都属于语言层面的设计失误了,属于能不用就不用的东西。
|
![]() |
2
Trim21 2 天前
命名返回值确实挺奇葩,但这不是命名返回值带来的奇葩问题之一...
这里问题是问题是,你 doBug 里的 nil 指针是 copy 进去... Unmarshal 内部就算能 new 一个 Student 出来,他要怎么修改你 doBug 里面指针指向的值呢? |
3
leonshaw 2 天前 ![]() 参数是传值的,函数无法改变传入变量本身的值。
另外: > return s, json.Unmarshal(data, &s) 不要这样写,求值顺序有问题 https://groups.google.com/g/golang-nuts/c/Q7KVGTFt3nU/m/WgnbugtwDAAJ |
4
ninjashixuan 2 天前
基本不用命名返回值,除非是在 defer 里用到才会考虑。
|
5
liyunlong41 2 天前 via iPhone
用正常写法,别玩花哨的,为你好也为别人好。
|
![]() |
6
voidmnwzp 2 天前 via iPhone
unmarshal 调用方必须传入指针类型是因为 unmarshal 内部需要写入指针对应的内存,这样反序列化完成后,调用能找到对应的内存,而 nil 不指向任何内存
|
7
lovelylain 1 天前 via Android
return s, json.Unmarshal(data, s) 值传递,你传个 nil 的 s 进去,就算内部给你 new 了一个对象,也没法影响你这个 s 还是 nil
|
![]() |
8
Charlie17Li OP |