您的观点与说“切片只不过是指针”没有什么不同。切片(和映射)使用指针使它们变得轻量,是的,但还有更多的东西可以使它们工作。例如,切片包含有关其长度和容量的信息。
至于为什么会这样,从代码的角度来看,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, 0xc420012300, 0x15, 0x4b8b00, 0x0, 0x15)
/usr/lib/go/src/reflect/value.go:1345 +0x2f
json.(*decodeState).literalStore(0xc420084360, 0xc42000e3f8, 0x4, 0x8, 0x4b8b00, 0xc420012300, 0x15, 0xc420000100)
/home/kit/jstest/src/json/decode.go:883 +0x2797
json.(*decodeState).literal(0xc420084360, 0x4b8b00, 0xc420012300, 0x15)
/home/kit/jstest/src/json/decode.go:799 +0xdf
json.(*decodeState).value(0xc420084360, 0x4b8b00, 0xc420012300, 0x15)
/home/kit/jstest/src/json/decode.go:405 +0x32e
json.(*decodeState).unmarshal(0xc420084360, 0x4b8b00, 0xc420012300, 0x0, 0x0)
/home/kit/jstest/src/json/decode.go:184 +0x224
json.Unmarshal(0xc42000e3f8, 0x4, 0x8, 0x4b8b00, 0xc420012300, 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。