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

huangapple go评论96阅读模式
英文:

json.Unmarshal() accepts a pointer to a pointer

问题

我注意到,偶然间发现我可以成功地将指向结构体的指针和指向指向结构体的指针的指针传递给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()中存在错误或错误的功能;我只是想满足自己的惊讶,即当给定一个指向指针的指针时它仍然能正常工作,并避免在使用它时遇到潜在的问题。

英文:

I noticed, quite by accident, that I can successfully pass both a pointer to a struct, and a pointer to a pointer to a struct to json.Unmarshal(), and both work just fine:

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")
	}
}

I was really surprised that the second one (unmarshal to **Person) worked.

What's going on in json.Unmarshal()? Is it dereferencing the pointers until it finds a struct?

The documentation offers:

> To unmarshal JSON into a pointer, Unmarshal first handles the case of
> the JSON being the JSON literal null. In that case, Unmarshal sets
> the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into the
> value pointed at by the pointer

It seems to be doing a bit more than that. What's really going on?

Fleshing out my question more: how does it know to automatically dereference my pointer to a pointer? The documentation says it will unmarshal "into the value pointed at by the pointer". Since the value of my pointer is in fact another pointer, and has no Name/Age fields, I expected it to stop there.

To be clear: I'm not saying there's a bug or misfeature in Unmarshal(); I'm trying to satisfy my astonishment that it works at all when given a ptr-to-ptr, and avoid any potential pitfalls in my use of it.

答案1

得分: 11

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

举个例子,如果使用指针来区分nil和零值来定义Person,并且你将其解组为[]*Person的切片,json包需要跟随这些指针,并在必要时分配值。如果Person中的字段被定义为**string,同样适用。

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

type People []*Person

示例代码

英文:

The json package has no reason to "stop at a pointer", since a pointer means nothing in json. It has to keep walking the tree in order to find a value to write. Since the json package is going to allow unmarshaling the same value into Type or *Type, it stands to reason that it should be able to unmarshal that into **Type, which is also a valid type in Go.

For a example, if Person were defined using pointers to differentiate between nil and zero values, and you were unmarshaling into a slice of []*Person, the json package needs to follow those pointers, and allocate values if necessary. The same applies if a field in Person were defined as a **string.

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

type People []*Person

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

答案2

得分: 3

json.Unmarshal的实现考虑了多级间接引用。在这里查看源代码(https://golang.org/src/encoding/json/decode.go),特别是decodeState.indirect方法:

// indirect会向下遍历v并根据需要分配指针,直到找到非指针为止。
// 如果遇到Unmarshaler,则indirect会停止并返回该值。
// 如果decodingNull为true,则indirect会在最后一个指针处停止,以便将其设置为nil。
func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
// 如果v是一个命名类型并且是可寻址的,
// 从其地址开始,以便如果该类型有指针方法,我们可以找到它们。
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
    }
    //以此类推
}
return nil, nil, v

}

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

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

这使我相信Go可以很好地处理双重间接引用。如果没有其他情况,json包的源代码是说明reflect包用途的绝佳示例。

简而言之,值会被检查,如果解码器处理指针,它将使用反射来确定有多少级间接引用,并确定目标的类型。在解码源代码中,从这个点开始:func (d *decodeState) unmarshal(v interface{}) (err error) {,之后的代码相当容易理解。

英文:

The json.Unmarshal implementation takes multiple indirection into account. Check the source here, in particular the decodeState.indirect method:

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

The same method is called when unmarshaling arrays:

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

That would have me believe that go can handle double indirection just fine. If nothing else, the json package source is a great example of what the reflect package is all about.

In short, values are checked, if the decoder is dealing with pointers, it will use reflection to work out how many levels of indirection there are, and determine what type the target has/is. The place to start from in the decode source is this: func (d *decodeState) unmarshal(v interface{}) (err error) {, from that point on, it's pretty self-explanatory.

答案3

得分: 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)  // 空指针
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(a)
}

但是这段代码不会报错(指向空指针的指针):

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,指向空指针的指针
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(a)
}

你可以在以下链接中查看代码的运行结果:https://play.golang.org/p/eI8jqWZOmGW

英文:

As other answers have said, pointers are followed.

A little weird that this errors (nil pointer), but makes sense when you think about it.

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)
}

But this doesn't error (pointer to nil pointer).

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

huangapple
  • 本文由 发表于 2016年2月24日 22:06:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/35604356.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定