-1

I have a couple of structures, which inherit some basic structure. Something like this:

type s1 struct {
    a string `json:"a"`
    b string `json:"b"`
}

type s2 struct {
    s1
    c string `json:"c"`
    d string `json:"d"`
}

type s3 struct {
    s1
    c string `json:"c"`
    d string `json:"d"`
    e string `json:"d"`
    f string `json:"d"`
}

Now I need to define a function which operates on any structure which has fields a, b. Something like

func modifyStruct(s *s1) {
    s.a, s.b = s.b, s.a
}

But has to work on s2, s3 and any other structure which inherits s1. I am trying to achieve this with an interface but so far with no luck. Any way to achieve this? The template is on go-playground.

icza
  • 389,944
  • 63
  • 907
  • 827
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
  • Why not attach a method with a `*s1` receiver, i.e. `func (s *s1) modify() { s.a, s.b = s.b, s.a; }`? An `s2` or `s3` variable will forward the address of the embedded `s1` field and pass that to the method as the receiver; that is, `s2foo.modify()` is the same as `(&s2foo.s1).modify()`. [Playground link](https://play.golang.org/p/jFZI6YSMgR) –  Oct 28 '17 at 03:03
  • 1
    The field tags in the example suggest that you are planning to use the encoding/json package with these types. If so, then you should [export](https://golang.org/ref/spec#Exported_identifiers) the field names. The json/encoding package ignores unexported fields. – Charlie Tumahai Oct 28 '17 at 03:30

2 Answers2

4

The general solution is using reflection as shown in Cerise Limón's answer. The cons of using reflection is that you have to export the fields, and that it's slower than it should or could be.

In your example although it is completely fine and sufficient for the function to take a value of type *s1, as all types that embed s1 has a value of s1 (explicitly). The unqualified type name (without package name) acts as the field name for embedded fields:

s2 := s2{s1: s1{"A", "B"}}
fmt.Println(s2)
modifyStruct(&s2.s1)
fmt.Println(s2)

Output (try it on the Go Playground):

{{A B}  }
{{B A}  }

If you still want it to accept values of "any" (certain) types (so you don't have to refer to the embedded s1 field), but don't want to export the fields, then you may use interfaces for this. Using interfaces you keep the good parts of both solutions (remains fast, flexible and you do not have to export the fields):

type S1 interface {
    AB() (string, string)
    SetAB(a, b string)
}

type s1 struct {
    a string `json:"a"`
    b string `json:"b"`
}

func (s s1) AB() (string, string) { return s.a, s.b }
func (s *s1) SetAB(a, b string)   { s.a, s.b = a, b }

func modifyStruct(s S1) {
    a, b := s.AB()
    s.SetAB(b, a)
}

Testing it:

s2 := s2{s1: s1{"A", "B"}}
fmt.Println(s2)
modifyStruct(&s2)
fmt.Println(s2)

Output is the same (try it on the Go Playground):

{{A B}  }
{{B A}  }

Note that (besides the *s1 type itself) any struct type (and also any pointer to struct type) that embeds *s1 automatically (implicitly) implements the S1 interface, and similarly any pointer to struct type that embeds s1 also implements S1 (and therefore *s2 and *s3 in your example).

icza
  • 389,944
  • 63
  • 907
  • 827
3

If you export the fields, then you can use the reflect package to swap the values:

type s1 struct {
    A string `json:"a"`
    B string `json:"b"`
}

...

func modifyStruct(s interface{}) {
    v := reflect.ValueOf(s).Elem() 
    a := v.FieldByName("A")
    b := v.FieldByName("B")
    t := reflect.New(a.Type()).Elem()
    t.Set(a)
    a.Set(b)
    b.Set(t)
}

The fields must be exported to use the reflect package because the reflect package cannot set unexported fields.

playground example

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242