Golang template variable isset

帅比萌擦擦* 提交于 2019-12-01 04:13:34

The recommended way

First, the recommended way is not to rely on whether a struct field exists. Of course there might be optional parts of the template, but the condition to decide whether to render a part should rely on fields that exist in all cases.

The issue, and avoiding it using a map

If the type of the template data is a struct (or a pointer to a struct) and there is no field or method with the given name, the template engine returns an error for that.

You could easily get rid of this error if you were to use a map, as maps can be indexed with keys they don't contain, and the result of that index expression is the zero value of the value type (and not an error).

To demonstrate, see this example:

s := `{{if .Email}}Email is: {{.Email}}{{else}}Email is NOT set.{{end}}`

t := template.Must(template.New("").Parse(s))
exec := func(name string, param interface{}) {
    fmt.Printf("\n%s:\n  ", name)
    if err := t.Execute(os.Stdout, param); err != nil {
        fmt.Println("Error:", err)
    }
}

exec("Filled map", map[string]interface{}{"Email": "as@as"})
exec("Empty map", map[string]interface{}{})

exec("Filled struct", struct {
    Email string
}{Email: "as@as.com"})
exec("Empty struct", struct{}{})

Output (try it on the Go Playground):

Filled map:
  Email is: as@as
Empty map:
  Email is NOT set.
Filled struct:
  Email is: as@as.com
Empty struct:
  Error: template: :1:5: executing "" at <.Email>: can't evaluate field Email in type struct {}

Sticking to struct and providing "isset"

If you must or want to stick to a struct, this "isset" can be implemented and provided, I'll call it avail().

This implementation uses reflection, and in order to check if the field given by its name exists (is available), the (wrapper) data must also be passed to it:

func avail(name string, data interface{}) bool {
    v := reflect.ValueOf(data)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }
    if v.Kind() != reflect.Struct {
        return false
    }
    return v.FieldByName(name).IsValid()
}

Example using it:

s := `{{if (avail "Email" .)}}Email is: {{.Email}}{{else}}Email is unavailable.{{end}}`

t := template.Must(template.New("").Funcs(template.FuncMap{
    "avail": avail,
}).Parse(s))
exec := func(name string, param interface{}) {
    fmt.Printf("\n%s:\n  ", name)
    if err := t.Execute(os.Stdout, param); err != nil {
        fmt.Println("Error:", err)
    }
}

exec("Filled struct", struct {
    Email string
}{Email: "as@as.com"})
exec("Empty struct", struct{}{})

Output (try it on the Go Playground):

Filled struct:
  Email is: as@as.com
Empty struct:
  Email is unavailable.
Pascut

If you don't send data containing a variable to the template but you use {{ if .Variable }} you will get the error:

executing "templatename" at <.Variable>: can't evaluate field Variable in type handler.Data

My solution was to send the "Email" variable as a boolean (false) in order to pass the {{ if .Email }} function check. But this is a short term solution that I don't like.

I was inspired by: https://stackoverflow.com/a/31527618/1564840. In that example they show different HTML for authenticated and non-authenticated users. You will see that in both cases they send the "Logged" variable. Try removing that variable from the struct and execute the function. You will receive the error that I mentioned above.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!