【问题标题】:json.Unmarshal() accepts a pointer to a pointerjson.Unmarshal() 接受指向指针的指针
【发布时间】:2016-06-06 20:57:03
【问题描述】:

我很偶然地注意到,我可以成功地将指向结构的指针和指向结构指针的指针传递给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 设置 指向零的指针。否则,Unmarshal 将 JSON 解组到 指针指向的值

它的作用似乎不止于此。到底发生了什么?

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

明确一点:我并不是说Unmarshal() 中存在错误或错误功能;我试图让我感到惊讶的是,当给定一个 ptr-to-ptr 时它完全可以工作,并避免在我使用它时出现任何潜在的陷阱。

【问题讨论】:

  • 解码器必须跟随指针,并根据需要分配内部指针。这样做的结果是它将遵循多个级别的指针。除了您已经提供的答案之外,还有什么问题?
  • 我试着充实一点。
  • :) 它does not stop 多了一层

标签: pointers go struct


【解决方案1】:

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

例如,如果 Person 是使用指针定义的以区分零值和零值,并且您要解组为 []*Person 的切片,则 json 包需要遵循这些指针,并在必要时分配值。如果 Person 中的字段被定义为 **string,这同样适用。

type Person struct {
    Name **string
    Age  *int
}

type People []*Person

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

【讨论】:

  • 你的例子很清楚发生了什么,谢谢。我只是被文档中的“指向指针所指向的值”这一行误导了。
【解决方案2】:

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) {,从那时起,它就非常不言自明了。

【讨论】:

    【解决方案3】:

    正如其他答案所说,遵循指针。

    这个错误(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)  // 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-04
      • 1970-01-01
      • 2018-05-16
      • 2021-12-31
      • 1970-01-01
      • 1970-01-01
      • 2016-10-30
      • 1970-01-01
      相关资源
      最近更新 更多