首頁 > 軟體

Golang Copier入門到入坑探究

2022-11-21 14:01:48

正文

github: https://github.com/jinzhu/copier

由於 golang 沒有對複雜結構體的 clone 方法,所以,就需要有 copier 這樣的工具庫。

它看起來很簡單,但實際使用中,有些“坑”還是要注意!

本文:

入門為輔,探“坑”為主,

看完再划走,CS我沒有。

安裝

go get github.com/jinzhu/copier

快速入門

好的,來一段程式碼快速瞭解 copier

package main
import (
    "fmt"
    "github.com/jinzhu/copier"
)
type SC struct {
    C uint8
}
type M1 struct {
    A int
    W string
    S *SC
}
func main() {
    var src = M1{12, "Hello", &SC{32}}
    var dst = M1{}
    fmt.Printf("before copy src %+vtdst %+vn", src, dst)
    copier.Copy(&dst, src)
    fmt.Printf("after  copy src %+vtdst %+vn", src, dst)
}

輸出:

before copy src {A:12 W:Hello S:0xc00017f550}   dst {A:0 W: S:<nil>}
after  copy src {A:12 W:Hello S:0xc00017f550}   dst {A:12 W:Hello S:0xc00017f618}

好的,看到這,你就已掌握了 copier 80%的功能了。先彆著急划走,接下來還是踩坑記錄。

本文程式碼執行輸出內容是基於 github.com/jinzhu/copier@v0.3.5 和 go1.16.1 darwin/amd64 環境演示的結果。

入坑

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
    A []int32
    C *SC
}
func main() {
    var src = Map1{map[string]int32{"C:": 3, "d": 4}, []int32{9, 8}, &SC{32}}
    var dst1 = Map1{}
    spew.Printf("before src %+vttdst %+vn", src, dst1)
    copier.Copy(&dst1, src)
    dst1.M["F"] = 5
    dst1.M["g"] = 6
    dst1.A[0] = 7
    dst1.C.C = 27
    spew.Printf("after  src %+vtdst %+vn", src, dst1)
}

以上程式碼執行後會輸出:

before src {M:map[C::3 d:4] A:[9 8] C:<*>(0xc00012a1e8){C:32}}          dst {M:<nil> A:<nil> C:<nil>}

befre 那一行程式碼如上⬆️ , after 那一行會輸出什麼呢?

1. after  src {M:map[C::3 d:4] A:[9 8] C:<*>(0xc00012a1e8){C:27}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}
2. after  src {M:map[C::3 d:4] A:[9 8] C:<*>(0xc00012a1e8){C:32}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}
3. after  src {M:map[C::3 d:4] A:[7 8] C:<*>(0xc00012a1e8){C:32}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}
4. after  src {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a1e8){C:32}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}
5. after  src {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a1e8){C:27}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}

答案是: var a = int(759 / 6 / 31.5)

為了避免不小心看了答案,請計算 759 / 6 / 31.5 得出的值四捨五入便是。

再探坑出坑

我看其他同學使用 copier 也是像上面那樣——copier.Copy($dst, src), 當然啦,也不排除我!它彷彿就是一把小巧精悍的小刀。一個簡單的函數呼叫,就完成了它的使命。

然而,它其實是把多功能刀,我都還沒有開啟它的—— option

上面的問題就是,我 Copy 後,對值的改動,影響了另一個值的 map,那麼這個時候,就需要進行深 copy。接下來引入 copier 的 option

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
    A []int32
    C *SC
}
func main() {
    var src = Map1{map[string]int32{"C:": 3, "d": 4}, []int32{9, 8}, &SC{32}}
    var dst1 = Map1{}
    spew.Printf("before src %+vttdst %+vn", src, dst1)
    copier.CopyWithOption(&dst1, src, copier.Option{DeepCopy: true})   // 這裡!
    dst1.M["F"] = 5
    dst1.M["g"] = 6
    dst1.A[0] = 7
    dst1.C.C = 27
    spew.Printf("after  src %+vtdst %+vn", src, dst1)
}

好的,這樣copy之後,對新變數的改動,不會傳遞會原變數改動了。

再盤一盤坑

package main
import (
    "fmt"
    "github.com/davecgh/go-spew/spew"
    "github.com/jinzhu/copier"
)
type ArrTC struct {
    Name [2]string
    C    *ArrTC
}
type ArrT struct {
    A  [3]int32
    S  []int32
    E  []int32
    C  string
    V  string
    M map[string]int32
    AC ArrTC
    s bool
}
func main() {
    var src = ArrT{
        [3]int32{9, 10, 0},
        []int32{12, 0},
        []int32{},
        "",
        "val",
        map[string]int32{"A:": 1, "b": 0},
        ArrTC{},
        true,
    }
    var dst = ArrT{
        [3]int32{1, 2, 3},
        []int32{4, 5, 6, 7},
        []int32{9, 10},
        "char",
        "ha",
        map[string]int32{"C:": 3, "b": 4, ".": 0},
        ArrTC{[2]string{"Y", "Z"}, nil},
        false,
    }
    spew.Printf("before src %+vtdst %+vn", src, dst)
    copier.CopyWithOption(&dst, src, copier.Option{IgnoreEmpty: true, DeepCopy: true})
    spew.Printf("after  src %+vtdst %+vn", src, dst)
    src.M["b"] = 99
    src.S[1] = 1
    dst.S[0] = 2
    spew.Printf("last  src %+vtdst %+vnn", src, dst)
}

輸出:

before src {A:[9 10 0] S:[12 0] E:[] C: V:val M:map[A::1 b:0] AC:{Name:[ ] C:<nil>} s:true}    dst {A:[1 2 3] S:[4 5 6 7] E:[9 10] C:char V:ha M:map[C::3 b:4 .:0] AC:{Name:[Y Z] C:<nil>} s:false}
after  src {A:[9 10 0] S:[12 0] E:[] C: V:val M:map[A::1 b:0] AC:{Name:[ ] C:<nil>} s:true}    dst {A:[9 10 0] S:[12 0 6 7] E:[9 10] C:char V:val M:map[A::1 C::3 b:0 .:0] AC:{Name:[Y Z] C:<nil>} s:true}
last  src {A:[9 10 0] S:[12 1] E:[] C: V:val M:map[A::1 b:99] AC:{Name:[ ] C:<nil>} s:true}    dst {A:[9 10 0] S:[2 0 6 7] E:[9 10] C:char V:val M:map[C::3 b:0 .:0 A::1] AC:{Name:[Y Z] C:<nil>} s:true}

這次的程式碼我加上了 IgnoreEmpty: true, 也就是複製時忽略空的值。 也就說可以當作值 merge 用。

然後,又測試了一下變數獨立性。複製之後,srcdst 兩個變數再無瓜葛,對其中一個值的任意改動都不會同步到另一個值。

但是,這個 merge 的表現,可能不是你想的那樣,

src.S = []int32{12, 0}
dst.S = []int32{4, 5, 6, 7}
## 呼叫 copy 後, 你預期的結果是什麼?[6/7]
6. dst.S = []int32{12, 0}
7. dst.S = []int32{12, 0, 6, 7}
  • 選項6: 嗯,原來是 {12, 0} 複製給 dst 就是 {12, 0}
  • 選項7: 這個是切片,你只給我 0,1 位的值,copier把 0,1 位置的值 copy 了,dst後面2,3位的值,src沒給出,那就不管。所以就是 {12, 0, 6, 7}

這塊的表現,我覺得是有爭議的,大佬們在評論區留下你預期選項,看看大家是不是都這樣想的。

實際執行結果,見上面的程式碼輸出就能找到答案。

結語

copier 本來是一個短小精悍的工具庫,也沒想要水一篇,最近使用時,突然踩坑,就特開一篇,和大家分享一下踩坑經驗。

在使用外部庫的時候,還是建議去 github 上看看詳細說明, 或者上 pkg.go.dev 看看它暴露出來出的介面以及說明。更或者進行完整的測試,充分了解它之後,再使用。

以上就是Golang Copier入門到入坑探究的詳細內容,更多關於Golang Copier入門的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com