在 Go 模板中按名称访问结构成员
Access struct member by name in Go template
我希望创建一个 common/generic Go html 模板,它将根据其输入生成标准 html table。我曾希望按名称查找结构成员,但我无法完成这项工作。
我环顾四周但找不到解决方案,所以我要么遗漏了一些明显的东西,要么方法不对。在这方面,我会接受一个解决方案,该解决方案显示了避免尝试此查找的替代或更好的方法。
示例模板:
{{ $fields := .FieldMap }}
<table>
<thead>
<tr>
{{ range $key, $value := $fields }}
<th>{{ $key }}</th>
{{ end }}
</tr>
</thead>
<tbody>
{{ range $i, $v := .Model }}
<tr>
{{ $rowData := . }}
{{/* FAILS: error calling index: can't index item of type main.Person <td> {{ index . "FirstName"}}</td>*/}}
{{ range $key, $value := $fields }}
{{/* FAILS: error calling index: can't index item of type main.Person <td> {{ index $rowData $value }}</td>*/}}
{{/* FAILS: bad character U+0024 '$' <td> {{ $rowData.$value }}</td>*/}}
{{ end }}
</tr>
{{ end }}
</tbody>
</table>
Go 示例:
主要包
import (
"html/template"
"os"
)
type Person struct {
FirstName string
LastName string
}
type Animal struct {
Species string
}
type TemplateData struct {
Model interface{}
FieldMap map[string]string
}
func main() {
t, err := template.ParseFiles("table.gohtml")
if err != nil {
panic(err)
}
// Here we use Person, but I may want to pass other types of struct to the template, for example "Animal"
dataPerson := TemplateData{
Model: []Person{
{
FirstName: "Test",
LastName: "Template",
},
},
FieldMap: map[string]string{"First": "FirstName", "Last": "LastName"},
}
err = t.Execute(os.Stdout, dataPerson)
if err != nil {
panic(err)
}
}
我希望它清楚我正在尝试做什么 - 有一个模板,我可以在各种类型的结构中重复使用它。
创建一个模板函数,returns 结构字段名称和值作为映射:
// fields returns map of field names and values for struct s.
func fields(s interface{}) (map[string]interface{}, error) {
v := reflect.Indirect(reflect.ValueOf(s))
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("%T is not a struct", s)
}
m := make(map[string]interface{})
t := v.Type()
for i := 0; i < t.NumField(); i++ {
sv := t.Field(i)
m[sv.Name] = v.Field(i).Interface()
}
return m, nil
}
解析文件时指定函数:
t, err := template.New("").Funcs(template.FuncMap{"fields": fields}).ParseFiles("table.gohtml")
if err != nil {
panic(err)
}
这样使用:
{{range $i, $v := .Model}}
<tr>
{{$m := fields $v}}
{{range $key, $value := $fields}}
<td>{{index $m $value}}</td>
{{end}}
</tr>
{{end}}
另一种方法是编写一个按名称查找字段的函数:
func field(s interface{}, k string) (interface{}, error) {
v := reflect.Indirect(reflect.ValueOf(s))
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("%T is not a struct", s)
}
v = v.FieldByName(k)
if !v.IsValid() {
return nil, fmt.Errorf("no field in %T with name %s", s, k)
}
return v.Interface(), nil
}
用函数解析:
t, err := template.New("").Funcs(template.FuncMap{"field": field}).ParseFiles("table.gohtml")
这样使用:
{{range $i, $v := .Model}}
<tr>
{{range $key, $value := $fields}}
<td>{{field $v $value}}</td>
{{end}}
</tr>
{{end}}
我希望创建一个 common/generic Go html 模板,它将根据其输入生成标准 html table。我曾希望按名称查找结构成员,但我无法完成这项工作。
我环顾四周但找不到解决方案,所以我要么遗漏了一些明显的东西,要么方法不对。在这方面,我会接受一个解决方案,该解决方案显示了避免尝试此查找的替代或更好的方法。
示例模板:
{{ $fields := .FieldMap }}
<table>
<thead>
<tr>
{{ range $key, $value := $fields }}
<th>{{ $key }}</th>
{{ end }}
</tr>
</thead>
<tbody>
{{ range $i, $v := .Model }}
<tr>
{{ $rowData := . }}
{{/* FAILS: error calling index: can't index item of type main.Person <td> {{ index . "FirstName"}}</td>*/}}
{{ range $key, $value := $fields }}
{{/* FAILS: error calling index: can't index item of type main.Person <td> {{ index $rowData $value }}</td>*/}}
{{/* FAILS: bad character U+0024 '$' <td> {{ $rowData.$value }}</td>*/}}
{{ end }}
</tr>
{{ end }}
</tbody>
</table>
Go 示例:
主要包
import (
"html/template"
"os"
)
type Person struct {
FirstName string
LastName string
}
type Animal struct {
Species string
}
type TemplateData struct {
Model interface{}
FieldMap map[string]string
}
func main() {
t, err := template.ParseFiles("table.gohtml")
if err != nil {
panic(err)
}
// Here we use Person, but I may want to pass other types of struct to the template, for example "Animal"
dataPerson := TemplateData{
Model: []Person{
{
FirstName: "Test",
LastName: "Template",
},
},
FieldMap: map[string]string{"First": "FirstName", "Last": "LastName"},
}
err = t.Execute(os.Stdout, dataPerson)
if err != nil {
panic(err)
}
}
我希望它清楚我正在尝试做什么 - 有一个模板,我可以在各种类型的结构中重复使用它。
创建一个模板函数,returns 结构字段名称和值作为映射:
// fields returns map of field names and values for struct s.
func fields(s interface{}) (map[string]interface{}, error) {
v := reflect.Indirect(reflect.ValueOf(s))
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("%T is not a struct", s)
}
m := make(map[string]interface{})
t := v.Type()
for i := 0; i < t.NumField(); i++ {
sv := t.Field(i)
m[sv.Name] = v.Field(i).Interface()
}
return m, nil
}
解析文件时指定函数:
t, err := template.New("").Funcs(template.FuncMap{"fields": fields}).ParseFiles("table.gohtml")
if err != nil {
panic(err)
}
这样使用:
{{range $i, $v := .Model}}
<tr>
{{$m := fields $v}}
{{range $key, $value := $fields}}
<td>{{index $m $value}}</td>
{{end}}
</tr>
{{end}}
另一种方法是编写一个按名称查找字段的函数:
func field(s interface{}, k string) (interface{}, error) {
v := reflect.Indirect(reflect.ValueOf(s))
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("%T is not a struct", s)
}
v = v.FieldByName(k)
if !v.IsValid() {
return nil, fmt.Errorf("no field in %T with name %s", s, k)
}
return v.Interface(), nil
}
用函数解析:
t, err := template.New("").Funcs(template.FuncMap{"field": field}).ParseFiles("table.gohtml")
这样使用:
{{range $i, $v := .Model}}
<tr>
{{range $key, $value := $fields}}
<td>{{field $v $value}}</td>
{{end}}
</tr>
{{end}}