在解组JSON

时间:2018-05-28 10:31:18

标签: pointers go interface unmarshalling

考虑以下结构和接口定义。

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 个答案:

答案 0 :(得分:3)

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

让我们详细说明一下。

首先让我们来看看你的第二个工作实例。类型为x的变量Foo包含类型为*Bar的指针值。当您将&x传递给json.Unmarshal()时,json包将获得*Foo指针值。指向界面的指针!不应该使用,但它是。 json包将取消引用指针,并获取类型为*Bar的包装值。由于这是一个非nil指针,json包可以 - 并且将继续使用它来进行解组。都好! json包将修改指向的值。

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

在第一个示例中,类型为x的{​​{1}}变量包装了非指针结构值。 无法修改界面中包含的值。

这是什么意思? Foo包将再次收到json类型的值。然后它继续并取消引用它,并获得一个包含在接口中的*Foo值。无法修改Bar接口内的Bar值。 Foo包“传递”结果的唯一方法是创建一个实现json的新值,并将此值存储在最初传递的Foo指向的位置。但是无法创建*Foo的值,它是非具体的接口类型。所以unmarshal会返回错误。

一些附录。你的第二个工作实例:

Foo

这是有效的,因为我们“准备”非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

我们会收到相同的错误(在Go Playground上尝试):

var x Foo = &Bar{}
x = (*Bar)(nil)
err := json.Unmarshal([]byte("{\"a\": 5}"), &x)
fmt.Printf("x: %+v\nerr: %s\n", x, err)

解释是一样的:x: <nil> err: json: cannot unmarshal object into Go value of type main.Foo 包不能使用json指针来解组值。它还需要创建一个非具体类型nil的值,但这是不可能的。只能创建具体类型的值。