0

I saw several questions asking on how to merge unique structs and how to merge identical structs.

But how would I merge structs that have some overlap? and which fields get taken & when?

e.g.:

    type structOne struct {
        id string `json:id`
        date string `json:date`
        desc string `json:desc`
    }

and

    type structTwo struct {
        id string `json:id`
        date string `json:date`
        name string `json:name`
    }

how would I merge it such that I get

{
    id string `json:id`
    date string `json:date`
    desc string `json:desc`
    name string `json:name`
}

also, what happens if in this case the two id's are the same (assuming a join over id's) but the names are different? In javascript, doing something like Object.assign(structOne, structTwo).

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Andrei S
  • 58
  • 1
  • 8
  • 1
    It really depends on how you "merge" two structs. Given that there are countless way of produce a new struct from two other sturcts, it is much easier for us to answer how to achieve your desired behaviour, or explain the behaviour of a specified way of "merging". – leaf bebop Jul 26 '20 at 01:57

4 Answers4

2

Go is a strongly typed language, unlike javascript you can't merge two struct into one combined struct because all type are determined at compile-time. You have two solution here :

Using embedded struct:

One great solution is to use embedded struct because you don't have to merge anything anymore.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
)

// Shared field
type common struct {
    ID   string `json:id`
    Date string `json:date`
}

type merged struct {
    // Common field is embedded
    common

    Name string `json:name`
    Desc string `json:desc`
}

func main() {
    buf := bytes.Buffer{}
    buf.WriteString("{ \"id\": \"1\",   \"date\": \"27/07/2020\", \"desc\": \"the decription...\"   }")

    merged := &merged{}

    err := json.Unmarshal(buf.Bytes(), merged)
    if err != nil {
        log.Fatal(err)
    }

    // Look how you can easily access field from
    // embedded struct
    fmt.Println("ID:", merged.ID)
    fmt.Println("Date:", merged.Date)
    fmt.Println("Name:", merged.Name)
    fmt.Println("Desc:", merged.Desc)

    // Output:
    // ID: 1
    // Date: 27/07/2020
    // Name:
    // Desc: the decription...
}

If you want to read more about struct embedding: golangbyexample.com travix.io

Using Maps

Another solution is to use maps but you will loose the benefits of struct and methods. This example is not the simplest but there is some great example in the other responses.

In this example I'm using Mergo. Mergo is library that can merge structs and map. Here it is used for creating maps object in the Map methods but you can totally write your own methods.

package main

import (
    "fmt"
    "log"

    "github.com/imdario/mergo"
)

type tOne struct {
    ID   string
    Date string
    Desc string
}

// Map build a map object from the struct tOne
func (t1 tOne) Map() map[string]interface{} {
    m := make(map[string]interface{}, 3)
    if err := mergo.Map(&m, t1); err != nil {
        log.Fatal(err)
    }

    return m
}

type tTwo struct {
    ID   string
    Date string
    Name string
}

// Map build a map object from the struct tTwo
func (t2 tTwo) Map() map[string]interface{} {
    m := make(map[string]interface{}, 3)
    if err := mergo.Map(&m, t2); err != nil {
        log.Fatal(err)
    }

    return m
}

func main() {
    dst := tOne{
        ID:   "destination",
        Date: "26/07/2020",
        Desc: "destination object",
    }.Map()

    src := tTwo{
        ID:   "src",
        Date: "26/07/1010",
        Name: "source name",
    }.Map()

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

    fmt.Printf("Destination:\n%+v", dst)
    // Output:
    // Destination:
    // map[date:26/07/2020 desc:destination object iD:destination name:object name
}

negrel
  • 96
  • 5
  • Thank you for your explanation and suggestions! I took the using embedded struct approach – Andrei S Jul 26 '20 at 13:00
  • Types are not all determined at compile time, since _at runtime_ you can create new struct types using `reflect.StructOf`, and create values of this new type. – Paul Hankin Jul 26 '20 at 14:15
0

Go structs and JavaScript objects are very different. Go structs do not have dynamic fields.

If you want dynamic key/value sets that you can easily iterate over and merge, and are very JSON friendly, why not a map[string]interface{}?

$ go run t.go
map[a:1 b:4]
map[a:1 b:4 c:3]
$ cat t.go
package main

import(
  "fmt"
)

type MyObj map[string]interface{}

func (mo MyObj)Merge(omo MyObj){
  for k, v := range omo {
   mo[k] = v
  }
}

func main() {
  a := MyObj{"a": 1, "b": 4}
  b := MyObj{"b": 2, "c": 3}
  b.Merge(a)
  fmt.Printf("%+v\n%+v\n", a, b)
}
CaptJak
  • 3,592
  • 1
  • 29
  • 50
erik258
  • 14,701
  • 2
  • 25
  • 31
0

you can use github.com/fatih/structs to convert your struct to map. Then iterate over that map and choose which fields need to copy over. I have a snippet of code which illustrates this solution.

func MergeStruct (a structOne,b structTwo) map[string]interface{}{
   a1:=structs.Map(a)
   b1:=structs.Map(b)
  /* values of structTwo over writes values of structOne */
   var myMap=make(map[string]interface{})
     for val,key:=range(a1){
        myMap[key]=val
     }
     for val,key:=range(b1){
        myMap[key]=val
     }
 return myMap
}
whitespace
  • 789
  • 6
  • 13
0

You can use the reflect package to do this. Try iterating through the two structs and then you can either use another struct type to store the values or maybe use a map.

Check out this question to find out how you can iterate over a struct. Check out this question to find out how to get the name of the fields.

Remember to use exported field names for the reflect package to work.

Here is an example which works.

Vaibhav Mishra
  • 415
  • 3
  • 10