请考虑以下结构和接口定义 .
type Foo interface {
Operate()
}
type Bar struct {
A int
}
func (b Bar) Operate() {
//...
}
现在,如果我们尝试执行以下操作(playground):
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
但是,通过使用结构类型替换基础数据,它可以顺利运行(playground):
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 . 毕竟,指针只是内存中的地址 . 如果我们通过该地址,我们应该可以修改它,不是吗?为什么第二个例子有效,但不是第一个?为什么结构作为指针会产生重大影响?
1 回答
不同行为的根源在于,如果
json
包必须创建新值,则该值必须是具体类型,并且不能是(非具体)接口类型 .我们来详细说明一下 .
首先让我们来看看你的第二个工作实例 .
Foo
类型的变量x
包装类型为*Bar
的指针值 . 当您将&x
传递给json.Unmarshal()
时,json
包将获得*Foo
指针值 . 指向界面的指针!不应该使用,但它是 .json
包将取消引用指针,并获取*Bar
类型的包装值 . 由于这是一个非nil
指针,json
包可以 - 并且将继续使用它进行解组 . 都好!json
包将修改指向的值 .你的第一个例子会发生什么?
在第一个示例中,
Foo
类型的x
变量包装非指针结构值 . Values wrapped in an interface cannot be modified.这是什么意思?
json
包将再次收到*Foo
类型的值 . 然后它继续并取消引用它,并获得一个包含在接口中的Bar
值 . 无法修改Foo
接口内的Bar
值 .json
包到结果的唯一方法是创建一个实现Foo
的新值,并将此值存储在最初传递的*Foo
指向的位置 . 但是无法创建Foo
的值,它是非具体的接口类型 . 所以unmarshal会返回错误 .一些附录 . 你的第二个工作实例:
这是有效的,因为我们要解组的值是非
nil
*Bar
,因此json
包不必自己创建值 . 我们已经准备并传递了Foo
接口类型的值(是具体类型*Bar
的值) .如果我们这样存储"typed"
nil
指向x
:我们将收到相同的错误(在Go Playground上尝试):
解释是一样的:
json
包不能使用nil
指针将值解组为 . 它还需要创建一个非具体类型的值Foo
,但这是不可能的 . 只能创建具体类型的值 .