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):
CreateAt
to plain time.Time
, meaning it will be copied and not just a pointerjson.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.