从json.Marshal到nil Pointer

原文链接:Dave Cheney的博文
先从一段代码说起:

package main

import (
    "encoding/json"
    "fmt"
)

type Result struct {
    Foo string
}

func main() {
    content := `{"foo": "bar"}`

    res := &Result{}
    err := json.Unmarshal([]byte(content), &res)
    if err != nil {
        panic(err)
    }
    fmt.Printf("res  = %+v\n", res) // 正常返回

    res2 := &Result{}
    err = json.Unmarshal([]byte(content), res2)
    if err != nil {
        panic(err)
    }
    fmt.Printf("res2 = %+v\n", res2) // 正常返回

    var res3 *Result
    err = json.Unmarshal([]byte(content), &res3)
    if err != nil {
        panic(err)
    }
    fmt.Printf("res3 = %+v\n", res3) // 正常返回

    var res4 *Result
    err = json.Unmarshal([]byte(content), res4)
    if err != nil {
        panic(err)
    }
    fmt.Printf("res4 = %+v\n", res4) // panic!!!
}

这是一段并不复杂的代码:尝试将一段文本反序列化到一个go 结构体,示例中给出四种定义,其中只有一种情况发生了报错,我们接下来就从这个异常情况来简单说下go语言体系中,空指针的概念。
在上面代码示例中,发生报错的res4res2从数据类型上来说并无不同,都是一个指向Result结构体的指针。我们来简单看下发生报错时我们拿到的提示json: Unmarshal(nil *main.Result)。看上去结果比较清晰,json.Unmarshal并不接受空指针(nil pointer)。
事实是这样吗?我们去encoding/json的文档中查看一下:

Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. If v is nil or not a pointer, Unmarshal returns an InvalidUnmarshalError.(https://pkg.go.dev/encoding/json?tab=doc#Unmarshal
)

果然,文档完善的标准库给了我们足够清晰的结论:对于非指针和空指针的参数,会抛出InvalidUnmarshalError
那么,encoding/json的接口又是为什么这么设计呢?这里我们要先清楚以下两个前提:

  1. go中的赋值操作都是传值的,这里的赋值包括变量的定义初始化,变量的绑定,函数参数传递。
  2. go的空指针和指向空值的指针有本质上区别:指向空值的指针本身指向的是一个空的对象(取决于指针的类型),被指向的对象本身是一个已经分配好的空间;空指针就是指针的空值nil,本身不指向任一对象。

基于以上两个前提,我们再来看下Unmarshal的函数定义:

func Unmarshal(data []byte, v interface{}) error

函数本身的返回值并不包含反序列之后的对象,只有一个描述结果的error返回值。所以Unmarshal的函数行为一定是通过改写v对象来达到的。根据第一条前提,我们知道如果v本身是非指针的话,Unmarshal的改写行为无法影响传递进来的值对象的原始值;又根据第二条前提,v如果是nil指针的话,函数无法根据指针去改写它指向的对象。
一个Unmarshal的文档描述,引出了go语言的两个知识点,还是挺有趣的。

你可能感兴趣的:(从json.Marshal到nil Pointer)