【问题标题】:How to merge two Go values of same struct type?如何合并两个相同结构类型的 Go 值?
【发布时间】:2020-10-09 15:20:58
【问题描述】:

我想创建一个名为merge() 的函数,它接受同一结构的两个值,但 any 结构,并返回两个结构的合并值。

我希望第一个值优先。例如,如果有两个结构ab,在调用merge(a,b)之后,如果有ab都包含的字段,我希望它具有a的值给定的字段。

实现这一点的最佳方法是什么? https://play.golang.org/p/7s9PWx26gfz

type cat struct {
name  string
color string
age   int
}

type book struct {
title  string
author string
}

func main() {
c1 := cat{
    name:  "Oscar",
    color: "",
    age:   3,
}

c2 := cat{
    name:  "",
    color: "orange",
    age:   2,
}

c3 := merge(c1, c2)

// want: c3 = cat{
//               name: "Oscar",
//               color: "orange",
//               age: 3,
//       }



// another case...
b1 := book{
    title: "Lord of the Rings",
    author: "John Smith",
}

b2 := book{
    title: "Harry Potter",
    author: "",
}

b3 := merge(b1, b2)

// want: b3 = book{
//               title: "Lord of the Rings",
//               author: "John Smith",
//       }
}

这是我目前所拥有的:

// merges two structs, where a's values take precendence over b's values (a's values will be kept over b's if each field has a value)
func merge(a, b interface{}) (*interface{}, error) {
    var result interface{}
    aFields := reflect.Fields(a)
    bFields := reflect.Fields(b)

    if !reflect.DeepEqual(aFields, bFields) {
        return &result, errors.New("cannot merge structs of different struct types")
    }

    aValOf := reflect.ValueOf(a)
    bValOf := reflect.ValueOf(b)
    resultValOf := reflect.ValueOf(result)
    aValues := make([]interface{}, aValOf.NumField())
    resultValues := make([]interface{}, resultValOf.NumField())

    for i := 0; i < aValOf.NumField(); i++ {
        if reflect.ValueOf(aValues[i]).IsNil() {
            resultValues[i] = bValOf.Field(i).Interface()
            break
        }
        resultValues[i] = aValOf.Field(i).Interface()
    }
    return &result, nil
}

【问题讨论】:

  • 实现这一点的最佳方法是不实现它,这不是有效的。你的用例是什么?可能有更好的解决方案。
  • 除了@luminoslty 所说的之外,我想要求澄清一下,因为您说您希望 a 的属性优先,但在您的书中示例中您偏离了这一点。
  • 我相信您应该看看将reflectionreflect 核心库一起使用。
  • "我希望第一个值优先。" -- 你的代码示例正好相反。

标签: go struct merge reflect


【解决方案1】:

检查这个包https://github.com/imdario/mergo

示例代码:

package main

import (
    "fmt"
    "github.com/imdario/mergo"
)

type Foo struct {
    A string
    B int64
}

func main() {
    src := Foo{
        A: "one",
        B: 2,
    }
    dest := Foo{
        A: "two",
    }
    mergo.Merge(&dest, src)
    fmt.Println(dest)
    // Will print
    // {two 2}
}

在操场上看:https://play.golang.org/p/9KWTK5mSZ6Q

【讨论】:

    【解决方案2】:

    对目标结构中的字段使用自定义类型。

    type firstString string
    type firstInt int
    
    type cat struct {
        Name  firstString
        Color firstString
        Age   firstInt
    }
    
    type book struct {
        Title  firstString
        Author firstString
    }
    

    为每个自定义类型实现 UnMarshalJSON,以便它们仅对空的目标值进行解组。

    func (fs *firstString) UnmarshalJSON(bytes []byte) error {
       if len(*fs) > 0 {
            return nil
        }
        var s string
        err := json.Unmarshal(bytes, &s)
        if err != nil {
            return err
        }
        *fs = firstString(s)
        return nil
    }
    
    func (fi *firstInt) UnmarshalJSON(bytes []byte) error {
        if *fi != 0 {
            return nil
        }
        var i int
        err := json.Unmarshal(bytes, &i)
        if err != nil {
            return err
        }
        *fi = firstInt(i)
        return nil
    }
    

    如果你的数据是通过 JSON 来的,你可以避免使用合并功能;只需将传入的 JSON 解组到相同的结构即可。如果您的数据已经在单独的结构中,您可以在合并函数中使用 JSON 作为中介来抽象出示例中的所有反射。

    // merges two structs, where a's values take precendence over b's values (a's values will be kept over b's if each field has a value)
    func merge(a, b interface{}) interface{} {
    
        jb, err := json.Marshal(b)
        if err != nil {
            fmt.Println("Marshal error b:", err)
        }
        err = json.Unmarshal(jb, &a)
        if err != nil {
            fmt.Println("Unmarshal error b-a:", err)
        }
    
        return a
    }
    

    一个工作示例中的所有内容:https://play.golang.org/p/5YO2HCi8f0N

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-31
      • 2014-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多