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

我很偶然地注意到,我可以成功地将指向结构的指针和指向指向结构的指针传递给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 时它仍然有效,并避免在我使用它时出现任何潜在的陷阱。


慕神8447489
浏览 225回答 3
3回答

慕盖茨4494581

json 包没有理由“停在一个指针”,因为指针在 json 中没有任何意义。它必须继续遍历树才能找到要写入的值。由于 json 包将允许将相同的值解组为Typeor *Type,因此它应该能够将其解组为**Type,这也是 Go 中的有效类型。例如,如果Person使用指针定义来区分 nil 和零值,并且您正在解组为 的切片[]*Person,则 json 包需要遵循这些指针,并在必要时分配值。如果 Person 中的字段被定义为**string.type Person struct {    Name **string    Age  *int}type People []*Personhttp://play.golang.org/p/vLq0nJPG5M

繁星淼淼

该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 包源代码是反射包全部内容的一个很好的例子。简而言之,检查值,如果解码器正在处理指针,它将使用反射来计算有多少间接级别,并确定目标具有/是什么类型。从解码源开始的地方是这样的:func (d *decodeState) unmarshal(v interface{}) (err error) {从那一点开始,它是不言自明的。

慕妹3146593

正如其他答案所说,遵循指示。这个错误(零指针)有点奇怪,但当你考虑它时是有道理的。package mainimport (    "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 mainimport (    "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
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go