2016-02-24 119 views
3

我注意到,非常偶然,我可以成功地将一个指针指向一个结构体,并且指向一个指向结构体的指针指向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为指针,解组首先处理的 的情况下,JSON作为JSON字面空。在这种情况下,Unmarshal将 指向nil。否则,解组解编的JSON到 值在由指针

这似乎是做了一些比这更尖。到底发生了什么?

充实了我的问题更:它是如何知道自动取消引用我的指针的指针?该文件表示,它将解组“指针指向的价值”。由于我的指针的值实际上是另一个指针,并且没有名称/年龄字段,所以我期望它停在那里。

要清楚:我并不是说在Unmarshal()中存在错误或错误;我试图满足我的惊讶,即在给定ptr-ptr时,它一切正常,并避免使用它的任何潜在缺陷。

+2

解码器必须遵循指针,并根据需要分配内部指针。这样做的结果是会遵循多级指针。除了你已经提供的答案之外,问题究竟是什么? – JimB

+0

我试着去补充一点。 –

+0

:)它[不停]](https://play.golang.org/p/YJPAvmMwqW)在一个级别额外 – RickyA

回答

4

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

+0

你的例子很清楚发生了什么,谢谢。我只是觉得有些被文档中的一行所误导,说“插入指针指向的值”。 –

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) 
    //... 

这将对我认为去能处理双间接就好了。如果没有其他的话,json软件包源代码就是reflect package的全部内容。

简而言之,检查值,如果解码器正在处理指针,它将使用反射来确定有多少级间接寻址,并确定目标的类型。从解码源开始的地方是这样的:func (d *decodeState) unmarshal(v interface{}) (err error) {,从这一点来说,这是非常明显的。