我正在使用 json.Unmarshal
,并遇到了以下怪癖 . 运行以下代码时,我收到错误 json: Unmarshal(non-pointer map[string]string)
func main() {
m := make(map[string]string)
data := `{"foo": "bar"}`
err := json.Unmarshal([]byte(data), m)
if err != nil {
log.Fatal(err)
}
fmt.Println(m)
}
查看documentation的documentation,似乎没有迹象表明需要指针 . 我能找到的最接近的是以下一行
Unmarshal解析JSON编码的数据并将结果存储在v指向的值中 .
关于协议Unmarshal的关于 Map 的线条同样不清楚,因为它没有引用指针 .
要将JSON对象解组为 Map ,Unmarshal首先 Build 要使用的 Map . 如果 Map 为nil,则Unmarshal分配新 Map . 否则,Unmarshal会重用现有 Map ,保留现有条目 . 然后,Unmarshal将JSON对象中的键值对存储到 Map 中 . 映射的键类型必须是字符串,整数或实现encoding.TextUnmarshaler .
为什么我必须将指针传递给json.Unmarshal,特别是如果map已经是引用类型?我知道如果我将 Map 传递给一个函数,并将数据添加到 Map 中, Map 的基础数据将会更改(请参阅the following playground example),这意味着如果我将指针传递给 Map 则无关紧要 . 有人可以解决这个问题吗?
3 回答
如文档中所述:
Unmarshal
可以分配变量(map,slice等) . 如果我们传递一个map
而不是指向map
的指针,那么调用者将看不到新分配的map
. 以下示例(Go Playground)演示了这一点:其中输出是:
如果需求说函数/方法可能在必要时分配变量并且新分配的变量需要对调用者可见,那么解决方案将是:(a)变量必须在函数的return语句中 or (b)变量可以分配给函数/方法参数 . 因为在
go
中,一切都是按值传递的,在(b)的情况下,参数必须是指针 . 下图说明了上例中发生的情况:首先, Map
m1
和m2
都指向nil
.调用
mapFunc
会将m1
指向的值复制到m
,结果m
也会指向nil
map .如果在(1)中已经分配了映射,则在(2)中
m1
指向的基础 Map 数据结构的地址(不是m1
的地址)将被复制到m
. 在这种情况下,m1
和m
都指向相同的 Map 数据结构,因此通过m1
修改 Map 项目也将对m
可见 .在
mapFunc
函数中,分配了新映射并将其分配给m
. 无法将其分配给m1
.如果是指针:
调用
mapPtrFunc
时,m2
的地址将被复制到mp
.在
mapPtrFunc
中,分配新 Map 并将其分配给*mp
(不是mp
) . 由于mp
是指向m2
的指针,因此将新映射指定给*mp
将更改m2
指向的值 . 请注意,mp
的值保持不变,即m2
的地址 .文档的另一个关键部分是:
如果Unmarshall接受了 Map ,则无论JSON是
null
还是{}
,都必须使 Map 保持相同状态 . 但是通过使用指针,现在指针设置为nil
与指向空 Map 之间存在差异 .请注意,为了让Unmarshall能够“将指针设置为nil”,您实际上需要传入指向 Map 指针的指针:
这输出:
你的观点与说“切片只不过是一个指针”没有什么不同 . 切片(和贴图)使用指针使它们变得轻量级,是的,但还有更多东西使它们起作用 . 例如,切片包含有关其长度和容量的信息 .
至于为什么会发生这种情况,来自代码透视,
json.Unmarshal
的最后一行调用d.unmarshal()
,它执行lines 176-179 of decode.go中的代码 . 它基本上说“如果值不是指针,或者是nil
,则返回InvalidUnmarshalError
” .文档可能更清楚一些事情,但考虑一些事情:
如果您没有't pass a pointer to the map? If you require the ability to modify the map itself (rather than the items in the map), then it makes sense to pass a pointer to the item that needs modified. In this case, it' Map ,JSON
null
值如何分配给 Mapnil
.或者,假设您将
nil
Map 传递给json.Unmarshal
. 在代码json.Unmarshal
最终调用相当于make(map[string]string)
之后,将根据需要对值进行解组 . 但是,您的函数中仍然有一个nil
Map ,因为您的 Map 没有指向任何内容 . 除了将指针传递给 Map 之外,没有办法解决这个问题 .但是,让's say there was no need to pass the address of your map because 2372972 , and you'已经初始化了 Map ,所以它不是
nil
. 那么会发生什么?好吧,如果我通过更改第176行来读取if rv.Kind() != reflect.Map && rv.Kind() != reflect.Ptr || rv.IsNil() {
之前在我之前链接的行中绕过测试,那么这可能发生:导致该输出的代码:
关键是这里有点:
因为您传递了 Map 的副本,所以它是无法寻址的(即它具有临时地址,甚至从低级机器角度看也没有地址) . 我知道一种方法(
x := new(Type)
后跟*x = value
,除了使用reflect
包),但它没有创建一个无法返回给调用者并使用它而不是原始存储位置的本地指针!所以现在尝试一个指针:
输出:
现在它有效 . 底线:如果对象本身可能被修改,则使用指针(并且文档说它可能是,例如,如果在期望对象或数组( Map 或切片)的地方使用
null
) .