Skip to content

Instantly share code, notes, and snippets.

@bbengfort
Created August 24, 2022 20:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbengfort/975a8c15e75badbf35ee991a472a12fe to your computer and use it in GitHub Desktop.
Save bbengfort/975a8c15e75badbf35ee991a472a12fe to your computer and use it in GitHub Desktop.
Merging a struct from another struct using reflection in Go. https://go.dev/play/p/m4dXDahCgyW?v=goprev
// Merging a struct from another struct using reflection.
package main
import (
"encoding/json"
"errors"
"fmt"
"reflect"
)
type A struct {
Foo int
Bar string
B *B
C *C
D *C
private bool
}
type B struct {
Age int
Color string
C *C
private int
}
type C struct {
Metric float64
Name string
}
// merges b into a based on rules
func merge(a, b interface{}) error {
as := reflect.ValueOf(a)
bs := reflect.ValueOf(b)
if as.Kind() != reflect.Ptr || bs.Kind() != reflect.Ptr {
return errors.New("must pass a pointer to merge")
}
return mergef(as.Elem(), bs.Elem())
}
func mergef(as, bs reflect.Value) error {
if as.Kind() != reflect.Struct || bs.Kind() != reflect.Struct {
return errors.New("must pass a struct value")
}
if as.Type() != bs.Type() {
return errors.New("can't merge different types")
}
fields:
for i := 0; i < as.NumField(); i++ {
af := as.Field(i)
bf := bs.Field(i)
if !af.CanSet() {
continue
}
for af.Kind() == reflect.Ptr {
if bf.IsNil() {
// bf is nil so stop processing
continue fields
}
if af.IsNil() {
if af.Type().Elem().Kind() != reflect.Struct {
// nil pointer to a non-struct; leave it alone
break
}
// nil pointer to struct, create a zero instance
af.Set(reflect.New(af.Type().Elem()))
}
af = af.Elem()
bf = bf.Elem()
}
// If the field is a struct, recursively merge
if af.Kind() == reflect.Struct {
if err := mergef(af, bf); err != nil {
return err
}
continue fields
}
// Otherwise set the af field value from the bf field
if af.IsZero() {
af.Set(bf)
}
}
return nil
}
func main() {
a1 := &A{
Foo: 32,
B: &B{
Color: "red",
C: &C{
Metric: 0.314,
},
private: 21,
},
private: true,
}
a2 := &A{
Foo: 18,
Bar: "hello",
B: &B{
Age: 23,
C: &C{
Metric: 0.2222,
Name: "duece",
},
private: 22222,
},
C: &C{
Metric: 44.1231111,
Name: "balance",
},
private: false,
}
if err := merge(a1, a2); err != nil {
fmt.Println(err)
return
}
data, _ := json.MarshalIndent(a1, "", " ")
fmt.Println(string(data))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment