修改任何具有特定字段的结构的函数

Function which modifies any structure which has particular fields

我有几个结构,它们继承了一些基本结构。像这样:

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"`
}

现在我需要定义一个函数,它对任何具有字段 ab 的结构进行操作。像

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

但必须在 s2、s3 和任何其他继承 s1 的结构上工作。我试图通过一个界面来实现这一点,但到目前为止还没有成功。有什么办法可以做到这一点?模板在 go-playground.

如果您 export the fields, then you can use the reflect 包交换值:

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)
}

必须导出字段才能使用反射包,因为反射包无法设置未导出的字段。

playground example

一般的解决方案是使用反射,如 Cerise Limón 的回答所示。使用反射的缺点是您必须导出字段,而且它比应该或可能的要慢。

在您的示例中,尽管函数采用 *s1 类型的值完全没问题且足够,因为嵌入 s1 的所有类型都具有 s1 的值(明确)。非限定类型名称(没有包名称)作为嵌入字段的字段名称:

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

输出(在 Go Playground 上尝试):

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

如果你仍然希望它接受 "any"(某些)类型的值(这样你就不必引用嵌入的 s1 字段),但不想导出字段,那么您可以为此使用接口。使用接口可以保留两种解决方案的优点(保持快速、灵活并且不必导出字段):

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)
}

正在测试:

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

输出是一样的(在Go Playground上试试):

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

请注意(除了 *s1 类型本身)任何嵌入 *s1 的结构类型(以及任何指向结构类型的指针)自动(隐式)实现 S1 接口,并且类似地,任何指向嵌入 s1 的结构类型的指针也实现了 S1(因此在您的示例中实现了 *s2*s3)。