首页 文章

在解组JSON时指向接口与指向接口的指针

提问于
浏览
0

请考虑以下结构和接口定义 .

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 回答

  • 3

    不同行为的根源在于,如果 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会返回错误 .

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

    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 的值) .

    如果我们这样存储"typed" 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 ,但这是不可能的 . 只能创建具体类型的值 .

相关问题