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