为什么 json.Unmarshal 在 golang 中追加一个新的指针值后更改指针值?

why json.Unmarshal change the pointer value after append a new one in golang?

我有一个问题,为什么json.Unmarshal先改变指针值(create_at) (test1 ) 添加一个新的后 (test2) 但 num 在 golang 中没有改变 ?

json.Unmarshal 会重用地址吗?我不明白为什么附加一个新值(不是指针值)会影响之前插入的元素,如果我更改 *time.Time -> time.Time,这个问题就会解决......

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "time"
)

type test struct {
    Num      int        `json:"num"`
    CreateAt *time.Time `json:"create_at"`
}

func main() {

    var icr []test
    var res test

    now := time.Now()
    next := time.Now().Add(time.Hour)

    test1 := test{
        Num:      1,
        CreateAt: &now,
    }

    test2 := test{
        Num:      2,
        CreateAt: &next,
    }

    newBytes := new(bytes.Buffer)
    json.NewEncoder(newBytes).Encode(test1)

    json.Unmarshal(newBytes.Bytes(), &res)

    icr = append(icr, res)

    fmt.Println(PrettyPrint(icr))
    // [
    //   {
    //     "num": 1,
    //     "create_at": "2020-09-24T15:03:00.755169076+08:00"
    //   }
    // ]

    newBytes = new(bytes.Buffer)
    json.NewEncoder(newBytes).Encode(test2)

    json.Unmarshal(newBytes.Bytes(), &res)

    icr = append(icr, res)

    fmt.Println(PrettyPrint(icr))
    // [
    //   {
    //     "num": 1,
    //     "create_at": "2020-09-24T16:03:00.755169556+08:00"
    //   },
    //   {
    //     "num": 2,
    //     "create_at": "2020-09-24T16:03:00.755169556+08:00"
    //   }
    // ]
}

// PrettyPrint ...
func PrettyPrint(data interface{}) string {
    var out bytes.Buffer
    b, _ := json.Marshal(data)
    json.Indent(&out, b, "", "  ")
    return out.String()
}

短版:切片中的所有元素都是res的浅拷贝,因此CreateAt字段指向相同的值。

详情:

res附加到icr时,添加到icr的元素是res的副本。

这适用于字段 Num,它可以在 res 中修改而不影响存储在 icr 中的数据。这是因为它是基本类型。

但是resCreateAt字段是一个指针,所以拷贝的还是同一个指针。 icr 的所有元素将 CreateAt 指向相同的值。对该值的任何修改都将反映在 icr.

的所有元素中

你有两个选择(至少):

  • CreateAt 更改为普通 time.Time,这意味着它将被复制而不仅仅是一个指针
  • 第二次使用不同的变量解组。例如:json.Unmarshal(newBytes.Bytes(), &res2)

这是一个没有 json 或切片的更清晰的示例,只有两个变量,其中一个是另一个的副本:see on playground:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "time"
)

type test struct {
    Num      int        `json:"num"`
    CreateAt *time.Time `json:"create_at"`
}

func main() {
    now := time.Now()
    res := test{1, &now}
    res2 := res

    fmt.Println(PrettyPrint(res), PrettyPrint(res2))
    
    // Modify res2:
    res2.Num = 2
    *res2.CreateAt = time.Now().Add(time.Hour)
    
    fmt.Println(PrettyPrint(res), PrettyPrint(res2))
}

// PrettyPrint ...
func PrettyPrint(data interface{}) string {
    var out bytes.Buffer
    b, _ := json.Marshal(data)
    json.Indent(&out, b, "", "  ")
    return out.String()
}

输出:

{
  "num": 1,
  "create_at": "2009-11-10T23:00:00Z"
} {
  "num": 1,
  "create_at": "2009-11-10T23:00:00Z"
}
{
  "num": 1,
  "create_at": "2009-11-11T00:00:00Z"
} {
  "num": 2,
  "create_at": "2009-11-11T00:00:00Z"
}

更新 res2 时,res2.Num 不会影响 res.Num,因为它是基本类型。但是,res2.CreateAtres.CreateAt 都指向同一个对象,因此更改会反映在两者中。