jinzhu / copier

Copier for golang, copy value from struct to struct and more
MIT License
5.58k stars 489 forks source link

在两个含有 map[string]int 的结构体复制后,新对象改动,会影响原`src`内的map #171

Closed PaulXu-cn closed 1 year ago

PaulXu-cn commented 2 years ago

Reproducible Example

有两个变量都是同一种结构体类型,结构体内有 map,slice,和子结构体。在调用 copier.Copy 复制后,改变 dst 对象内的 map,居然对原 scr 里面的 map 产生了影响,我的预期是复制后,就是彼此独立的对象和map了。

Description

测试代码:

package main

import (
    "fmt"
    "github.com/davecgh/go-spew/spew"
    "github.com/jinzhu/copier"
)

type SC struct {
    C uint8
}

type Map1 struct {
    M map[string]int32
    C *SC
}

func TestCopier() {
    var src = Map1{map[string]int32{"C:": 3, "d": 4}, []int32{9, 8}, &SC{32}}
    var dst1 = Map1{}
    copier.Copy(&dst1, src)
    spew.Printf("1. before src %+v\t\tdst1 %+v\n", src, dst1)
    fmt.Printf("1. before src.map %p\tdst1 %p\n", &src.M, &dst1.M)
    dst1.M["F"] = 5
    dst1.M["g"] = 6
    dst1.A[0] = 7
    dst1.C.C = 27
    spew.Printf("1. after  src %+v\tdst1 %+v\n", src, dst1)
    fmt.Printf("1. after  src.map %p\tdst1 %p\n\n", &src.M, &dst1.M)

    var dst2 = Map1{}
    copier.Copy(&dst2, src)
    spew.Printf("2. before src %+v\t\tdst1 %+v\t\t\tdst2 %+v\n", src, dst1, dst2)
    fmt.Printf("2. before src.map %p\tdst1 %p\tdst2 %p\n", &src.M, &dst1.M, &dst2.M)
    dst2.M["H"] = 7
    dst2.M["i"] = 8
    dst1.A[0] = 6
    dst1.C.C = 62
    spew.Printf("2. after  src %+v\tdst1 %+v\t\tdst2 %+v\n", src, dst1, dst2)
    fmt.Printf("2. after  src.map %p\t dst1.map %p\t dst2.map %p\n\n", &src.M, &dst1.M, &dst2.M)

    var dst3 = Map1{}
    src.M = map[string]int32{"C:": 3, "d": 4}
    copier.Copy(&dst3, src)
    spew.Printf("3. before src %+v\t\tdst1 %+v\t\t\tdst3 %+v\n", src, dst1, dst3)
    fmt.Printf("3. before src.map %p\tdst1 %p\tdst2 %p\tdst3.map %p\n", &src.M, &dst1.M, &dst1.M, &dst3.M)
    dst3.M["H"] = 7
    dst3.M["i"] = 8
    dst1.A[0] = 5
    dst1.C.C = 57
    spew.Printf("3. after  src %+v\tdst1 %+v\t\tdst3 %+v\n", src, dst1, dst3)
    fmt.Printf("3. after  src.map %p\t dst1.map %p\t dst2.map %p\tdst3.map %p\n\n",
        &src.M, &dst1.M, &dst2.M, &dst3.M)
}

运行输出:

1. before src {M:map[C::3 d:4] A:[9 8] C:<*>(0xc000018278){C:32}}       dst1 {M:map[C::3 d:4] A:[9 8] C:<*>(0xc000018308){C:32}}
1. before src.map 0xc0000643c0  dst1 0xc000064420
1. after  src {M:map[g:6 C::3 d:4 F:5] A:[7 8] C:<*>(0xc000018278){C:32}}   dst1 {M:map[F:5 g:6 C::3 d:4] A:[7 8] C:<*>(0xc000018308){C:27}}
1. after  src.map 0xc0000643c0  dst1 0xc000064420

2. before src {M:map[g:6 C::3 d:4 F:5] A:[7 8] C:<*>(0xc000018278){C:32}}       dst1 {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc000018308){C:27}}            dst2 {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc0000186a0){C:32}}
2. after  src {M:map[C::3 d:4 F:5 g:6 H:7 i:8] A:[6 8] C:<*>(0xc000018278){C:32}}   dst1 {M:map[C::3 d:4 F:5 g:6 H:7 i:8] A:[6 8] C:<*>(0xc000018308){C:62}}        dst2 {M:map[C::3 d:4 F:5 g:6 H:7 i:8] A:[6 8] C:<*>(0xc0000186a0){C:32}}
2. after  src.map 0xc0000643c0   dst1.map 0xc000064420   dst2.map 0xc000064780

3. before src {M:map[C::3 d:4] A:[6 8] C:<*>(0xc000018278){C:32}}       dst1 {M:map[g:6 H:7 i:8 C::3 d:4 F:5] A:[6 8] C:<*>(0xc000018308){C:62}}            dst3 {M:map[C::3 d:4] A:[6 8] C:<*>(0xc000018cc8){C:32}}
3. after  src {M:map[C::3 d:4 H:7 i:8] A:[5 8] C:<*>(0xc000018278){C:32}}   dst1 {M:map[H:7 i:8 C::3 d:4 F:5 g:6] A:[5 8] C:<*>(0xc000018308){C:57}}        dst3 {M:map[C::3 d:4 H:7 i:8] A:[5 8] C:<*>(0xc000018cc8){C:32}}
3. after  src.map 0xc0000643c0   dst1.map 0xc000064420   dst2.map 0xc000064780  dst3.map 0xc000064ba0

2022/11/12 18:35:51 go-learn end at 2022-11-12 18:35:51.550851 +0800 CST m=+0.000847494 duration is 0 ms
Pauls-MBP:go-learn paulxu$ go run ./cmd/main.go
2022/11/12 18:37:21 go-learn start at 2022-11-12 18:37:21.187705 +0800 CST m=+0.000306917
1. before src {M:map[C::3 d:4] A:[9 8] C:<*>(0xc0000b61e8){C:32}}       dst1 {M:map[C::3 d:4] A:[9 8] C:<*>(0xc0000b6278){C:32}}
1. before src.map 0xc0000983c0  dst1 0xc000098420
1. after  src {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc0000b61e8){C:32}}   dst1 {M:map[d:4 F:5 g:6 C::3] A:[7 8] C:<*>(0xc0000b6278){C:27}}
1. after  src.map 0xc0000983c0  dst1 0xc000098420

2. before src {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc0000b61e8){C:32}}       dst1 {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc0000b6278){C:27}}            dst2 {M:map[d:4 F:5 g:6 C::3] A:[7 8] C:<*>(0xc0000b6610){C:32}}
2. before src.map 0xc0000983c0  dst1 0xc000098420   dst2 0xc000098780
2. after  src {M:map[H:7 i:8 C::3 d:4 F:5 g:6] A:[6 8] C:<*>(0xc0000b61e8){C:32}}   dst1 {M:map[g:6 H:7 i:8 C::3 d:4 F:5] A:[6 8] C:<*>(0xc0000b6278){C:62}}        dst2 {M:map[H:7 i:8 C::3 d:4 F:5 g:6] A:[6 8] C:<*>(0xc0000b6610){C:32}}
2. after  src.map 0xc0000983c0   dst1.map 0xc000098420   dst2.map 0xc000098780

3. before src {M:map[C::3 d:4] A:[6 8] C:<*>(0xc0000b61e8){C:32}}       dst1 {M:map[C::3 d:4 F:5 g:6 H:7 i:8] A:[6 8] C:<*>(0xc0000b6278){C:62}}            dst3 {M:map[C::3 d:4] A:[6 8] C:<*>(0xc0000b6c38){C:32}}
3. before src.map 0xc0000983c0  dst1 0xc000098420   dst2 0xc000098420   dst3.map 0xc000098ba0
3. after  src {M:map[d:4 H:7 i:8 C::3] A:[5 8] C:<*>(0xc0000b61e8){C:32}}   dst1 {M:map[i:8 C::3 d:4 F:5 g:6 H:7] A:[5 8] C:<*>(0xc0000b6278){C:57}}        dst3 {M:map[i:8 C::3 d:4 H:7] A:[5 8] C:<*>(0xc0000b6c38){C:32}}
3. after  src.map 0xc0000983c0   dst1.map 0xc000098420   dst2.map 0xc000098780  dst3.map 0xc000098ba0

从以上输出 结果看到 src 内的 map 在调用 copier.Copy 方法后,编辑新的dst对象的 map,和子结构体后,src的字结构体不影响,但map也产生了改变。

我的预期是:在 copy出一个新对象后,对新对象的任何编辑,都不应该影响原对象。

所以:

  1. 这是个bug
  2. 提问者才疏学浅,这是预期的表现
  3. 使用copier的方式不对
  4. golang限制,做不到提问者的预期
ly9chee commented 1 year ago

You may try this copier.CopyWithOption(&dst1, src, copier.Option{DeepCopy: true})

uded commented 1 year ago

@PaulXu-cn - my Chinese is strongly limited, but looking at your code, @ly9chee comment seems to address this report. If you agree, please close it. Otherwise, I will in a week or two.