json - 解组 JSON 时指向接口的指针与持有指针的接口
问题描述
考虑以下结构和接口定义。
type Foo interface {
Operate()
}
type Bar struct {
A int
}
func (b Bar) Operate() {
//...
}
现在,如果我们尝试执行以下操作(操场):
var x Foo = Bar{}
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err)
我们得到以下输出:
x: {A:0}
err: json: cannot unmarshal object into Go value of type main.Foo
但是,通过将基础数据替换为 struct 类型,它会顺利进行(操场):
var x Foo = &Bar{}
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err)
输出:
x: &{A:5}
err: %!s(<nil>)
然而,这让我很困惑。在我们对 Unmarshall 的调用中,我们仍然传递了一个指向 x 的指针,据我了解,这应该足以让我们修改下面的 Bar。毕竟,指针只是内存中的地址。如果我们传递那个地址,我们应该能够修改它,不是吗?为什么第二个例子有效,但第一个无效?为什么作为指针的结构会产生重大影响?
解决方案
不同行为的根源在于,如果json
包必须创建一个新值,则该值必须是具体类型,并且不能是(非具体)接口类型。
让我们详细说明一下。
首先让我们检查您的第二个工作示例。您x
的 type变量Foo
包装了 type 的指针值*Bar
。当您传递&x
到json.Unmarshal()
时,json
包将获得一个*Foo
指针值。指向接口的指针!不应该使用,但它是。包将json
取消引用指针,并获得 type 的包装值*Bar
。由于这是一个非nil
指针,json
包可以——也将——继续使用它进行解组。都好!该json
包将修改指向的值。
你的第一个例子会发生什么?
在您的第一个示例中,您x
的类型变量Foo
包装了一个非指针结构值。封装在接口中的值不能修改。
这是什么意思?包将json
再次收到 type 的值*Foo
。然后它继续并取消引用它,并获得一个Bar
包装在接口中的值。接口内的Bar
值Foo
不能修改。包“传递”结果的唯一方法json
是创建一个实现的新值,Foo
并将该值存储在最初传递的*Foo
指向的位置。但是Foo
不能创建值,它是一个非具体的接口类型。所以 unmarshal 返回错误。
一些附录。您的第二个工作示例:
var x Foo = &Bar{}
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err)
这是可行的,因为我们“准备”了一个非nil
*Bar
值来解组,所以json
包不必自己创建一个值。我们已经为接口类型准备并传递了一个值Foo
(作为具体类型的值*Bar
)。
如果我们要存储一个这样nil
指向的“类型” x
:
var x Foo = &Bar{}
x = (*Bar)(nil)
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err)
我们将返回相同的错误(在Go Playground上尝试):
x: <nil>
err: json: cannot unmarshal object into Go value of type main.Foo
解释是一样的:json
包不能使用nil
指针将值解组到。它将再次需要创建一个非具体类型的值Foo
,但这是不可能的。只能创建具体类型的值。
推荐阅读
- javascript - 将值更新到 AG 网格中的服务器
- node.js - Git 结帐问题 HEAD -> master
- python - 如何使用python在每个重复的csv行中添加不同的列数据?
- uwp - 使用 TrySilentDownloadAndInstallStorePackageUpdatesAsync 出错:指定的帐户不存在
- javascript - Javascript 将表格内容解析为 Json
- java - 是否有 Microsoft VS Code 的扩展来 lint `.java` 文件并编译为 `.class`?
- mongodb - MongoClient 不想发现副本集节点
- python-3.x - 如何从 selenium python 的下拉列表中输入值?
- java - 如何在java中从构造函数更改为枚举?
- amazon-web-services - Elastic Beanstalk 上的自定义工作器优先级