json.Unmarshal()接受指向指针的指针

时间:2016-02-24 14:06:20

标签: pointers go struct

我非常偶然地注意到,我可以成功地将指向结构的指针和指向结构的指针传递给json.Unmarshal(),并且两者都可以正常工作:

package main

import (
    "testing"
    "encoding/json"
)

type Person struct {
    Name string
    Age  int
}

func TestMarshaling(t *testing.T) {
    foo := &Person{Name: "bob", Age: 23}

    // marshal it to bytes
    b, err := json.Marshal(foo)
    if err != nil {
        t.Error(err)
    }

    bar := &Person{}             // pointer to new, empty struct
    err = json.Unmarshal(b, bar) // unmarshal to bar, which is a *Person
    if err != nil {
        t.Error(err)
    }
    testBob(t, bar)  // ok

    bar = &Person{}               // pointer to new, empty struct
    err = json.Unmarshal(b, &bar) // wait a minute, passing in a **Person, yet it still works?
    if err != nil {
        t.Error(err)
    }
    testBob(t, bar) // ok
}

func testBob(t *testing.T, person *Person) {
    if person.Name != "bob" || person.Age != 23 {
        t.Error("not equal")
    }
}

我真的很惊讶第二个(**Person的解组)工作。

json.Unmarshal()发生了什么?是否在找到结构之前取消引用指针?

文档提供:

  

要将JSON解组为指针,Unmarshal首先处理该情况   JSON是JSON文字null。在这种情况下,Unmarshal设置   指向nil的指针。否则,Unmarshal将JSON解组为   指针指向的值

它似乎做得多一些。真正发生了什么?

更充实地解答我的问题:如何自动取消引用指向指针的指针?文档说它会解析“指针指向的值”。由于我的指针的值实际上是另一个指针,并且没有Name / Age字段,我预计它会停在那里。

要明确:我并不是说Unmarshal()中存在错误或错误;我正在努力让我感到惊讶的是,当它给予ptr-to-ptr时,它完全有效,并避免在使用它时出现任何潜在的陷阱。

3 个答案:

答案 0 :(得分:7)

json包没有理由"停在指针",因为指针在json中没有任何意义。它必须继续在树上行走才能找到要写的值。由于json包将允许将相同的值解组为Type*Type,因此它应该能够将其解组为**Type,这也是一种有效的类型在Go。

例如,如果使用指针定义Person来区分nil和zero值,并且你要解组为[]*Person切片,那么json包需要遵循这些指针,并分配必要时的价值观如果Person中的字段被定义为**string

,则同样适用
type Person struct {
    Name **string
    Age  *int
}

type People []*Person

http://play.golang.org/p/vLq0nJPG5M

答案 1 :(得分:1)

json.Unmarshal实现考虑了多个间接。检查来源here,特别是decodeState.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 last pointer so it can be set to nil.
func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
    // 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.Ptr && v.Type().Name() != "" && v.CanAddr() {
        v = v.Addr()
    }
    for {
        if v.Kind() == reflect.Interface && !v.IsNil() {
            e := v.Elem()
            if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
                v = e
                continue
            }
        }

        if v.Kind() != reflect.Ptr {
            break
        }
        //and so on
    }
return nil, nil, v

解组数组时会调用相同的方法:

func (d *decodeState) array(v reflect.Value) {
    u, ut, pv := d.indirect(v, false)
    //...

那会让我相信go可以处理双重间接就好了。如果不出意外,json包源代码就是reflect package的全部例子。

简而言之,检查值,如果解码器处理指针,它将使用反射来计算出有多少级别的间接,并确定目标具有什么类型。从解码源开始的地方是:func (d *decodeState) unmarshal(v interface{}) (err error) {,从那时起,这是非常明显的。

答案 2 :(得分:0)

正如其他答案所说的,指针紧随其后。

有点奇怪,这个错误(零指针),但是当您考虑它时才有意义。

package main

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

type MyStruct struct {
    A string `json:"a"`
}

func main() {
    data := []byte(`{"a":"foo"}`)
    var a *MyStruct
    err := json.Unmarshal(data, a)  // nil ptr
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(a)
}

但这不会出错(指向nil指针的指针)。

package main

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

type MyStruct struct {
    A string `json:"a"`
}

func main() {
    data := []byte(`{"a":"foo"}`)
    var a *MyStruct
    err := json.Unmarshal(data, &a) // **MyStruct, ptr to nil ptr
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(a)
}

https://play.golang.org/p/eI8jqWZOmGW