How can I merge two structs in Golang?

44,628

Solution 1

You can embed both structs in another.

type name struct {
    Name string `json:"name"`
}
type description struct {
    Description string `json:"description"`
}
type combined struct {
    name
    description
}

The JSON package will treat embedded structs kind of like unions, but this can get clunky pretty quickly.

Solution 2

It's a bit convoluted but I suppose you could do something like this:

    a := struct {
        Name string `json:"name"`
    }{"my name"}
    b := struct {
        Description string `json:"description"`
    }{"my description"}
    var m map[string]string
    ja, _ := json.Marshal(a)
    json.Unmarshal(ja, &m)
    jb, _ := json.Marshal(b)
    json.Unmarshal(jb, &m)
    jm, _ := json.Marshal(m)
    fmt.Println(string(jm))

Solution 3

Go is all about composition over inheritance. Sadly, you're using anonymous structs, but given that you're clearly trying to json marshal them, you're better of defining them as types:

type name struct {
    Name string `json:"name"`
}
type desc struct {
    Description string `json:"description"`
}

You can initialize them in the same way as you're currently doing, but instead of struct{<fields>}{init}, you'll just write

a := name{"foo"}
b := desc{"Description"}

You can then combine them in any way you like by writing:

c := struct {
    name
    description
}{a, b}

One quirk (that might trip you up at first) you have to get used to when composing types like this is how you initialize the members. Say you decide to create a type that combines the other two structs:

type foo struct {
    name
    description
}

You cannot initialize it like this:

o := foo{"Name value", "description value"}

Go will complain about you using type string as type name. You'll have to write something like this:

o := foo{
    name{"Name value"},
    description{Description: "Description val"},//optional with field names
}

An in-line composite built using existing objects (cf c := struct{}{a, b}) does this already.
Depending on what you're trying to do, it sometimes is easier to write something like this:

func (s *MyCompositeType) CopyName(n name) {
    s.Name = n.Name
    //copy other fields
}

It makes life easier when you're nesting composite types several levels deep.

Solution 4

You can merge two struct like this :

package main
import (
    "fmt"
    "encoding/json"
)
type b struct {
    Name string  `json:"name"`
    Description string
    Url string
}
type a struct {
    *b
    MimeType string  `json:"mimeType"`
}
func main() {
    bc := b{"test", "testdecription", "testurl"}
    ac := a{nil, "jpg"}
    ac.b = &bc
    js, _ := json.Marshal(ac)
    fmt.Println(string(js) )
}

Solution 5

One approach to this is to use the inbuilt reflect package and create a new struct programmatically!

func stringInSlice(value string, slice []string) bool {
    for _, elem := range slice {
        if elem == value {
            return true
        }
    }
    return false
}
func mergeStructs(structs ...interface{}) reflect.Type {
    var structFields []reflect.StructField
    var structFieldNames []string
    for _, item := range structs {
        rt := reflect.TypeOf(item)
        for i := 0; i < rt.NumField(); i++ {
            field := rt.Field(i)
            if !stringInSlice(field.Name, structFieldNames) {
                structFields = append(structFields, field)
                structFieldNames = append(structFieldNames, field.Name)
            }
        }
    }
    return reflect.StructOf(structFields)
}

Go Playground: Link

Share:
44,628

Related videos on Youtube

Dmitry Kapsamun
Author by

Dmitry Kapsamun

Updated on July 09, 2022

Comments

  • Dmitry Kapsamun
    Dmitry Kapsamun 11 months

    I have two json-marshallable anonymous structs.

    a := struct {
        Name string `json:"name"`
    }{"my name"}
    b := struct {
        Description string `json:"description"`
    }{"my description"}
    

    Is there any way to merge them into json to get something like that:

    {
        "name":"my name",
        "description":"my description"
    }
    
    • Volker
      Volker over 6 years
      What would be wrong with fmt.Sprintf("{\"name\":%q,\"description\":%q}", a.Name, b.Description) ? Some things do require some coding...
  • Dmitry Kapsamun
    Dmitry Kapsamun over 6 years
    The nuance is in that the struct is anonymous. I don't know it's name. So I can't use embedding.
  • Dmitry Kapsamun
    Dmitry Kapsamun over 6 years
    It's completely what I need! Thank you!
  • Elias Van Ootegem
    Elias Van Ootegem over 6 years
    @DmitryKapsamun: Can't you create a type? Why is the struct anonymous?
  • tutley
    tutley over 5 years
    if you marshal o into json what would that string look like?
  • Elias Van Ootegem
    Elias Van Ootegem over 5 years
    @tutley: try it on go playground. It will/should look like this: {"name": "Name value", "description": "Description val"}. The other types are not exported, embedded structs
  • Yagiz Degirmenci
    Yagiz Degirmenci over 2 years
    Elegant, I love the simplicity of this kind of operations in GO.
  • Dmytro Boichenko
    Dmytro Boichenko over 2 years
    Problems could appear if you have omitempty json tag in your struct. If so the nil values won't be erased.