I want to create a function called merge()
that takes in two values of the same struct, but of any struct, and returns the merged values of the two structs.
I want the first value to take precedence. For example, if there are two structs a
and b
, after calling merge(a,b)
, if there are fields that both a
and b
contain, I want it to have a
's value for that given field.
What would be the best way to implement this? 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",
// }
}
This is what I have so far:
// 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
}
Check this package https://github.com/imdario/mergo
Sample code:
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}
}
See it at playground: https://play.golang.org/p/9KWTK5mSZ6Q
Use custom types for fields in your target structs.
type firstString string
type firstInt int
type cat struct {
Name firstString
Color firstString
Age firstInt
}
type book struct {
Title firstString
Author firstString
}
Implement UnMarshalJSON for each custom type such that they only unmarshal for target values that are empty.
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
}
If your data is coming via JSON, you can avoid the use of the merge function; just keep unMarshalling incoming JSON to the same struct. If your data is already in separate structs, you can use JSON as an intermediary in your merge function to abstract away all the reflect-ing you have in your example.
// 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
}
All together in a working example: https://play.golang.org/p/5YO2HCi8f0N
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With