1

Given two structs where some fields might be null on either one, what is the most elegant way of merging them? (taking one side if both have a non-nulled field?)

I am unsure how to do this without comparing each field.

4
  • 1
    Is it possible for you to also provide code examples/code attempts? Commented Jun 15, 2018 at 2:21
  • What would your desired output be if the fields are defined in both? Commented Jun 15, 2018 at 3:37
  • 1
    "I am unsure how to do this without comparing each field." No need to be unsure, it is simple: You cannot do this. Merge each and every field individually. Or jump into reflection (just joking, don't do it!) and handle each and every type individually. Commented Jun 15, 2018 at 5:20
  • Consider the case where you are updating a struct with a JSON object- you would take the updated side if available and not update if the key is omitted. Commented Jun 16, 2018 at 7:21

2 Answers 2

3

If you don't care the cost too much.I have an very inefficient solution. Using the json marshal and unmarshal with omitempty tag. Hope it can help you somewhere.

package main

import (
    "fmt"
    "encoding/json"
)

type myStruct struct{
    Id int      `json:"id,omitempty"`
    Name string `json:"name,omitempty"`
    Sex bool `json:"sex,omitempty"`
    Age int `json:"age,omitempty"`
    Addr string `json:"addr,omitempty"`
}

func MergeStruct(to interface{},from interface{}) error{
    byte1,err := json.Marshal(to)
    if err != nil {
        return err
    }
    byte2,err := json.Marshal(from)
    if err != nil {
        return err
    }
    map1 := make(map[string]interface{})
    err = json.Unmarshal(byte1,&map1)
    if err != nil {
        return err
    }
    map2 := make(map[string]interface{})
    err = json.Unmarshal(byte2,&map2)
    if err != nil {
        return err
    }
    for k,v := range map2{
        map1[k] = v
    }
    byteDest,err := json.Marshal(map1)
    if err != nil {
        return err
    }
    err = json.Unmarshal(byteDest,to)
    return err
}

func main() {
    st1 := myStruct{
        Id:1,
        Name:"saky1",
        Addr:"addr1",
    }
    st2 := myStruct{
        Id:2,
        Name:"saky2",
        Age:20,
    }
    fmt.Println("befor merge: ",st1)
    MergeStruct(&st1,&st2)
    fmt.Println("after merge: ",st1)

}

Output:

befor merge:  {1 saky1 false 0 addr1}
after merge:  {2 saky2 false 20 addr1}
Sign up to request clarification or add additional context in comments.

Comments

2

You'd also need to ask yourself the following questions:

  • What if both are nil?
  • What if both are non-nil?
  • Not all types are nil-able
  • How do you handle nesting?

The first one isn't really a problem in most cases because usually the result will be nil then. The second one isn't really a problem in most cases because usually you'll define some precedence (i.e. first argument wins over second argument).

The third one is a bit trickier. You can define to take whichever is non-zero.

Nesting is also a bit tricky. You might want to "deep merge" some structs or just superficially merge some structs.

Anyway, you can do this with reflect but it's going to be tricky (unless somebody has already done it). With some 5minutes hacking you can get this:

func f(a,b interface{}) {
    ra := reflect.ValueOf(a).Elem()
    rb := reflect.ValueOf(b).Elem()

    numFields := ra.NumField()

    for i := 0; i < numFields; i++ {
        field_a := ra.Field(i)
        field_b := rb.Field(i)

        switch field_a.Kind() {
        case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
            if field_a.IsNil() {
                field_a.Set(field_b)
            }
        }
    }
}

but this is FAR from complete and it'll only work with exported fields because accessing unexported fields isn't too convenient with reflect and requires more quirks to do it. It also doesn't recurse but you can of course recurse if you detect nested structs etc.

However, it's probably better if you just write a manual merge function for your structs.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.