简体   繁体   中英

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

I have a question about why json.Unmarshal change the pointer value (create_at) first ( test1 ) after append a new one ( test2 ) but num is dont change in golang ?

json.Unmarshal will reuse the address ? I don't understand why append a new value (don't pointer value) will influences the elements insert before, if I change the *time.Time -> time.Time, this problem will be solve...

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()
}

The short version : all elements in your slice are shallow copies of res , so the CreateAt field points to the same value.

Details :

When appending res to icr , the element added to icr is a copy of res .

This works fine for the field Num , it can be modified in res without impacting the data stored in icr . This is because it's a base type.

However, the CreateAt field of res is a pointer , so the copy is still the same pointer. All elements of icr will have CreateAt point to the same value. Any modification of that value will be reflected in all elements of icr .

You have two options (at least):

  • change CreateAt to plain time.Time , meaning it will be copied and not just a pointer
  • use a different variable to unmarshal the second time. eg: json.Unmarshal(newBytes.Bytes(), &res2)

Here is a clearer example without json or slices, just two variables one of which is a copy of the other: 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()
}

Output:

{
  "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"
}

When updating res2 , the res2.Num does not impact res.Num since it is a base type. However, res2.CreateAt and res.CreateAt both point to the same object so the change is reflected in both.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM