首页 > 解决方案 > 解组 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。毕竟,指针只是内存中的地址。如果我们传递那个地址,我们应该能够修改它,不是吗?为什么第二个例子有效,但第一个无效?为什么作为指针的结构会产生重大影响?

标签: jsongopointersinterfaceunmarshalling

解决方案


不同行为的根源在于,如果json包必须创建一个新值,则该值必须是具体类型,并且不能是(非具体)接口类型。

让我们详细说明一下。

首先让我们检查您的第二个工作示例。您x的 type变量Foo包装了 type 的指针值*Bar。当您传递&xjson.Unmarshal()时,json包将获得一个*Foo指针值。指向接口的指针!不应该使用,但它是。包将json取消引用指针,并获得 type 的包装值*Bar。由于这是一个非nil指针,json包可以——也将——继续使用它进行解组。都好!该json包将修改指向的值。

你的第一个例子会发生什么?

在您的第一个示例中,您x的类型变量Foo包装了一个非指针结构值。封装在接口中的值不能修改。

这是什么意思?包将json再次收到 type 的值*Foo。然后它继续并取消引用它,并获得一个Bar包装在接口中的值。接口内的BarFoo不能修改。包“传递”结果的唯一方法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,但这是不可能的。只能创建具体类型的值。


推荐阅读