Go 每日一庫之 mergo

簡介

今天咱們介紹一個合併結構體字段的庫mergomergo能夠在相同的結構體或map之間賦值,能夠將結構體的字段賦值到map中,能夠將map的值賦值給結構體的字段。感謝@thinkgos推薦。git

快速使用

先安裝:github

$ go get github.com/imdario/mergo

後使用:golang

package main

import (
  "fmt"
  "log"

  "github.com/imdario/mergo"
)

type redisConfig struct {
  Address string
  Port    int
  DB      int
}

var defaultConfig = redisConfig{
  Address: "127.0.0.1",
  Port:    6381,
  DB:      1,
}

func main() {
  var config redisConfig

  if err := mergo.Merge(&config, defaultConfig); err != nil {
    log.Fatal(err)
  }

  fmt.Println("redis address: ", config.Address)
  fmt.Println("redis port: ", config.Port)
  fmt.Println("redis db: ", config.DB)

  var m = make(map[string]interface{})
  if err := mergo.Map(&m, defaultConfig); err != nil {
    log.Fatal(err)
  }

  fmt.Println(m)
}

使用很是簡單。mergo提供了兩組接口(其實就是兩個,*WithOverwrite已經廢棄了,可以使用WithOverride選項代替):redis

  • Merge:合併兩個相同類型的結構或map
  • Map:在結構和map之間賦值。

參數 1 是目標對象,參數 2 是源對象,這兩個函數的功能就是將源對象中的字段複製到目標對象的對應字段上。api

高級選項

若是僅僅只是複製結構體,爲啥不直接寫redisConfig = defaultConfig呢?mergo提供了不少選項。數組

覆蓋

默認狀況下,若是目標對象的字段已經設置了,那麼Merge/Map不會用源對象中的字段替換它。咱們在上面程序的var config redisConfig定義下添加一行:微信

config.DB = 2

再看看運行結果,發現輸出的db是 2,而非 1。ide

能夠經過選項來改變這個行爲,調用Merge/Map時,傳入WithOverride參數,那麼目標對象中已經設置的字段也會被覆蓋:函數

if err := mergo.Merge(&config, defaultConfig, mergo.WithOverride); err != nil {
  log.Fatal(err)
}

只須要修改這一行調用。結果輸出db是 1,覆蓋了!學習

這裏用到了 Go 中的選項模式。在參數比較多,且大部分有默認值的狀況下,咱們能夠在函數最後添加一個可變的選項參數,經過傳入選項來改變函數的行爲,不傳入的選項就使用默認值。選項模式在 Go 語言中使用很是普遍,能大大提升代碼的可擴展性,使用可變參數也能使函數更易用。mergo中的選項都是這種形式。想要深刻了解一下?看這裏https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

mergo老的接口MergeWithOverrideMapWithOverride都使用選項模式重構了。

切片

若是某個字段是一個切片,不覆蓋就保留目標對象的值,或者用源對象的值覆蓋都不合適。咱們可能想將源對象中切片的值對添加到目標對象的字段中,這時可使用WithAppendSlice選項。

package main

import (
  "fmt"
  "log"

  "github.com/imdario/mergo"
)

type redisConfig struct {
  Address string
  Port    int
  DBs     []int
}

var defaultConfig = redisConfig{
  Address: "127.0.0.1",
  Port:    6381,
  DBs:     []int{1},
}

func main() {
  var config redisConfig
  config.DBs = []int{2, 3}

  if err := mergo.Merge(&config, defaultConfig, mergo.WithAppendSlice); err != nil {
    log.Fatal(err)
  }

  fmt.Println("redis address: ", config.Address)
  fmt.Println("redis port: ", config.Port)
  fmt.Println("redis dbs: ", config.DBs)
}

咱們將DB字段改成[]int類型的DBs,使用WithAppendSliec選項,最後輸出的DBs[2 3 1]

空值覆蓋

默認狀況下,若是源對象中的字段爲空值(數組、切片長度爲 0 ,指針爲nil,數字爲 0,字符串爲""等),即便咱們使用了WithOverride選項也是不會覆蓋的。下面兩個選項就是強制這種狀況下也覆蓋:

  • WithOverrideEmptySlice:源對象的空切片覆蓋目標對象的對應字段;
  • WithOverwriteWithEmptyValue:源對象中的空值覆蓋目標對象的對應字段,其實這個對切片也有效。

文檔中這兩個選項的介紹比較混亂,我經過看源碼和本身試驗下來發現:

  • 這兩個選項都必須和WithOverride一塊兒使用;
  • WithOverwriteWithEmptyValue這個選項也能夠處理切片類型的值。

看下面代碼:

type redisConfig struct {
  Address string
  Port    int
  DBs     []int
}

var defaultConfig = redisConfig{
  Address: "127.0.0.1",
  Port:    6381,
}

func main() {
  var config redisConfig
  config.DBs = []int{2, 3}

  if err := mergo.Merge(&config, defaultConfig, mergo.WithOverride, mergo.WithOverrideEmptySlice); err != nil {
    log.Fatal(err)
  }

  fmt.Println("redis address: ", config.Address)
  fmt.Println("redis port: ", config.Port)
  fmt.Println("redis dbs: ", config.DBs)
}

最終會輸出空的DBs

類型檢查

這個主要用在map之間的切片字段的賦值,由於使用mergo在兩個結構體之間賦值必須保證兩個結構體類型相同,沒有類型檢查的必要。由於map類型爲map[string]interface{},因此默認狀況下,map切片類型不一致也是能夠賦值的:

func main() {
  m1 := make(map[string]interface{})
  m1["dbs"] = []uint32{2, 3}

  m2 := make(map[string]interface{})
  m2["dbs"] = []int{1}

  if err := mergo.Map(&m1, &m2, mergo.WithOverride); err != nil {
    log.Fatal(err)
  }

  fmt.Println(m1)
}

若是添加mergo.WithTypeCheck選項,則切片類型不一致會拋出錯誤:

if err := mergo.Map(&m1, &m2, mergo.WithOverride, mergo.WithTypeCheck); err != nil {
    log.Fatal(err)
}

輸出:

cannot override two slices with different type ([]int, []uint32)
exit status 1

注意事項

  1. mergo不會賦值非導出字段;
  2. map中對應的鍵名首字母會轉爲小寫;
  3. mergo可嵌套賦值,咱們演示的只有一層結構。

總結

mergo其實在不少知名項目中都有應用,如moby/kubernetes等。本文介紹了mergo的基本用法,感興趣能夠去 GitHub 上深刻學習。關於選項模式,這裏多說一句,我在實際項目中屢次應用,能極大地提升可擴展性,方便從此添加新的功能。

你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄

參考

  1. mergo GitHub:https://github.com/imdario/mergo
  2. Go 每日一庫 GitHub:https://github.com/darjun/go-daily-lib

個人博客

歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~

相關文章
相關標籤/搜索