你的观点与“一片不过是一个指针”没有什么不同。切片(和地图)使用指针来使它们轻量化,是的,但还有更多的事情可以使它们工作。例如,切片包含有关其长度和容量的信息。
至于为什么会出现这种情况,从代码的角度来看,最后一行json.Unmarshal
调用d.unmarshal()
,它执行lines 176-179 of decode.go中的代码。它基本上说“如果该值不是指针,或者是nil
,则返回InvalidUnmarshalError
”。
该文档或许可以为大约事情更清晰,但考虑几件事情:
- 将如何JSON
null
值被分配到地图为nil
,如果你不将指针传递到地图?如果您需要修改地图本身(而不是地图中的项目)的能力,那么将指针传递给需要修改的项目是有意义的。在这种情况下,这是地图。
- 或者,假设您通过
nil
地图到json.Unmarshal
。在代码json.Unmarshal
使用最终调用等效make(map[string]string)
后,值将根据需要解组。然而,你的函数中仍然有一个nil
地图,因为你的地图没有指向任何东西。除了传递指向地图的指针之外,没有办法解决这个问题。
但是,假设没有必要传递地图的地址,因为“它已经是一个指针”,并且您已经初始化地图,所以它不是nil
。然后会发生什么?嗯,如果我绕过我通过改变线路176读取if rv.Kind() != reflect.Map && rv.Kind() != reflect.Ptr || rv.IsNil() {
前面挂线测试,则可能发生这种情况:
`{"foo":"bar"}`: false map[foo:bar]
`{}`: false map[]
`null`: panic: reflect: reflect.Value.Set using unaddressable value [recovered]
panic: interface conversion: string is not error: missing method Error
goroutine 1 [running]:
json.(*decodeState).unmarshal.func1(0xc420039e70)
/home/kit/jstest/src/json/decode.go:172 +0x99
panic(0x4b0a00, 0xc42000e410)
/usr/lib/go/src/runtime/panic.go:489 +0x2cf
reflect.flag.mustBeAssignable(0x15)
/usr/lib/go/src/reflect/value.go:228 +0xf9
reflect.Value.Set(0x4b8b00, 0xc420, 0x15, 0x4b8b00, 0x0, 0x15)
/usr/lib/go/src/reflect/value.go:1345 +0x2f
json.(*decodeState).literalStore(0xc420084360, 0xc42000e3f8, 0x4, 0x8, 0x4b8b00, 0xc420, 0x15, 0xc420000100)
/home/kit/jstest/src/json/decode.go:883 +0x2797
json.(*decodeState).literal(0xc420084360, 0x4b8b00, 0xc420, 0x15)
/home/kit/jstest/src/json/decode.go:799 +0xdf
json.(*decodeState).value(0xc420084360, 0x4b8b00, 0xc420, 0x15)
/home/kit/jstest/src/json/decode.go:405 +0x32e
json.(*decodeState).unmarshal(0xc420084360, 0x4b8b00, 0xc420, 0x0, 0x0)
/home/kit/jstest/src/json/decode.go:184 +0x224
json.Unmarshal(0xc42000e3f8, 0x4, 0x8, 0x4b8b00, 0xc420, 0x8, 0x0)
/home/kit/jstest/src/json/decode.go:104 +0x148
main.main()
/home/kit/jstest/src/jstest/main.go:16 +0x1af
代码导致该输出:
package main
// Note "json" is the local copy of the "encoding/json" source that I modified.
import (
"fmt"
"json"
)
func main() {
for _, data := range []string{
`{"foo":"bar"}`,
`{}`,
`null`,
} {
m := make(map[string]string)
fmt.Printf("%#q: ", data)
if err := json.Unmarshal([]byte(data), m); err != nil {
fmt.Println(err)
} else {
fmt.Println(m == nil, m)
}
}
}
关键是这个这里有点:
reflect.Value.Set using unaddressable value
因为你通过地图的副本,它是不可寻址(即它已经从低层次的机器角度的临时地址,甚至无地址)。我知道一个解决方法(x := new(Type)
后跟*x = value
,除了使用reflect
包),但它实际上并没有解决问题;你正在创建一个本地指针,无法返回给调用者并使用它来代替原始存储位置!
所以现在尝试指针:
if err := json.Unmarshal([]byte(data), m); err != nil {
fmt.Println(err)
} else {
fmt.Println(m == nil, m)
}
输出:
`{"foo":"bar"}`: false map[foo:bar]
`{}`: false map[]
`null`: true map[]
现在,它的工作原理。底线:使用指针如果对象本身可能会被修改(和文档说它可能是,例如,如果null
用于预期对象或数组(地图或切片)的地方
我很喜欢这个答案。谢谢! – ollien