为什么json.Unmarshal使用引用而不是指针?

65

这个示例来自json.Unmarshal文档(为了简单起见,稍作修改将[]Animal改为了Animal),可以正常运行,没有错误:

可工作示例的Playground链接

// ...
var animals Animal
err := json.Unmarshal(jsonBlob, &animals)
// ...

但是这个稍作修改的例子就不行:

无法工作的示例的Playground链接

// ...
var animals *Animal
err := json.Unmarshal(jsonBlob, animals)
// ...

它显示了这个晦涩的错误,真的没什么用(在我看来更像是一个函数调用而不是错误):

json: Unmarshal(nil *main.Animal)

这似乎是因为animals是一个未初始化的指针。但文档说(强调我的部分):

Unmarshal将JSON解组为指向指针所指向的值。 如果指针为nil,则Unmarshal会为其分配一个新值。

那么为什么第二个例子中反序列化失败并显示了这个晦涩的错误呢?

(另外,“unmarshalling”或“unmarshaling”(一个L)哪个正确?文档都使用了。)


3
在拼写问题上,英式英语使用“marshalled”和“marshalling”的双重“l”,但美式英语则不然。 (https://en.oxforddictionaries.com/definition/marshal) - Rick-777
4个回答

56
您遇到了一个InvalidUnmarshalError(请参见decode.go中第109和110行)。

// InvalidUnmarshalError描述传递给Unmarshal的无效参数。
// (Unmarshal的参数必须是非nil指针。)

看起来文档需要一些澄清,因为上面的引用和下面的注释来自Unmarshal源代码似乎相互矛盾。

如果指针为nil,则Unmarshal会为其分配一个新值。


53

1
好的,但是Unmarshal文档中的这句话是什么意思:“如果指针为nil,则Unmarshal会为其分配一个新值。”如果Unmarshal应该为我们执行初始化,那么为什么我们还需要手动初始化呢? - Matt
2
哈哈,是的,我在你的问题中看到了那个。如果你传递指针的引用,它也会分配它。http://play.golang.org/p/3KP7pPKmBp - Eve Freeman
7
如果没有指向指针的引用,就无法改变指针的值(即它所指向的位置)。 - Eve Freeman
也许关于为指针分配新值的文档是指在使用指针字段填充结构体时的行为,而不是传递给Unmarshal的指针。请考虑Unmarshal无法将新指针返回给调用者,因为Unmarshal无法修改传递给它的指针。 - ChrisH
好的,如果你给它一个指向指针的引用,它会分配内存--也许他们应该在文档中澄清这种情况下的预期。另外,来自聚会的Chris你好! - Eve Freeman
显示剩余2条评论

21
我认为问题出在,虽然你可以将指向nil的指针传递给Unmarshal()函数,但你不能传递一个空指针值。
指向nil的指针应该是这样的:
var v interface{}
json.Unmarshal(text, &v)
v的值为nil,但指向v的指针是一个非零指针地址。它是一个非零指针,指向一个nil interface{}(它本身是一个指针类型)。在这种情况下,Unmarshal不会返回错误。
一个nil指针就像:
var v *interface{}
json.Unmarshal(text, v)

在这种情况下,变量v的类型是指向interface{}的指针,但与golang中任何变量声明一样,v的初始值是该类型的零值。因此,v是一个零值指针,这意味着它没有指向内存中的任何有效位置。
https://dev59.com/9mIj5IYBdhLWcg3wPy0a#20478917所述,json.Unmarshal()需要一个指向某个东西的有效指针,以便可以直接更改该东西(无论是零值结构体还是指针)。

1
我之前遇到过类似的情况,但是在不同的案例中。它与Go语言中的接口概念有关。如果一个函数声明了一个接口作为参数或返回值,调用者必须传递或返回引用。
在您的情况下,json.Unmarshal将接受接口作为第二个参数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接