Golang模板变量是否设置

huangapple go评论93阅读模式
英文:

Golang template variable isset

问题

我已经创建了一个函数来检查变量是否已定义:

fm["isset"] = func(a interface{}) bool {
    if a == nil || a == "" || a == 0 {
        fmt.Println("is not set")
        return false
    }
    fmt.Println("is set")
    return false
}

tmpl := template.Must(template.New("").Funcs(fm).ParseFiles("templates/header.html"))

err := tmpl.ExecuteTemplate(w, "header", templateData)

在模板中,我有以下内容:

{{ if isset .Email }}
    email is set
{{ end }}

这个函数可以在变量被templateData包含时工作(templateData是一个包含映射和字符串的自定义结构体),但如果变量不存在,它会给我一个错误。

错误信息如下:

executing "header" at <.Email>: can't evaluate field Email in type base.customData

在我的情况下,"base.go"是处理程序,"customData"由type customData struct{...}定义。

我希望能够重用模板,并且只在从处理程序发送了某些变量时显示某些部分。有没有办法在模板中实现一个isset变量检查?我还尝试使用{{ if .Email}} do stuff {{ end }},但这也给我同样的错误。有什么建议吗?

英文:

I have created a function to check if a variable is defined:

fm[&quot;isset&quot;] = func(a interface{}) bool {
		if a == nil || a == &quot;&quot; || a == 0 {
			fmt.Println(&quot;is not set&quot;)
			return false
		}
		fmt.Println(&quot;is set&quot;)
		return false
	}

tmpl :=  template.Must(template.New(&quot;&quot;).Funcs(fm).ParseFiles(&quot;templates/header.html&quot;))

err := tmpl.ExecuteTemplate(w, &quot;header&quot;, templateData)

In the template I have:

{{ if isset .Email }}
    email is set
{{ end }}

This function works if the variable is contained by the templateData (which is a custom struct that contains a map and a string), but it gives me an error if the variable doesn't exist.

The error is:

executing &quot;header&quot; at &lt;.Email&gt;: can&#39;t evaluate field Email in type base.customData

In my case "base.go" is the handler and "customData" is defined by: type customData struct{..}.

I want to be able to reuse templates and to display some sections only if some variables are sent from the handler. Any idea how can I implement a variable isset check on the template side?

I also tried using: {{ if .Email}} do stuff {{ end }} but this also gives me the same error.

Any idea?

答案1

得分: 22

推荐的方法

首先,推荐的方法是不要依赖于结构体字段是否存在。当然,模板中可能有可选部分,但决定是否渲染某个部分的条件应该依赖于所有情况下都存在的字段。

问题及使用映射避免问题

如果模板数据的类型是struct(或指向struct的指针),并且没有给定名称的字段或方法,模板引擎会返回一个错误。

如果你使用映射,可以轻松解决这个错误,因为映射可以使用它们不包含的键进行索引,而该索引表达式的结果是值类型的零值(而不是错误)。

为了演示,看看下面的例子:

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

输出结果(在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 {}

保持使用struct并提供“isset”

如果你必须或者想要继续使用struct,可以实现并提供这个“isset”功能,我将其称为avail()

这个实现使用了反射,为了检查给定名称的字段是否存在(可用),(包装)数据也必须传递给它:

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

使用示例:

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

输出结果(在Go Playground上尝试):

Filled struct:
  Email is: as@as.com
Empty struct:
  Email is unavailable.
英文:

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(&quot;&quot;).Parse(s))
exec := func(name string, param interface{}) {
	fmt.Printf(&quot;\n%s:\n  &quot;, name)
	if err := t.Execute(os.Stdout, param); err != nil {
		fmt.Println(&quot;Error:&quot;, err)
	}
}

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

exec(&quot;Filled struct&quot;, struct {
	Email string
}{Email: &quot;as@as.com&quot;})
exec(&quot;Empty struct&quot;, 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 &quot;&quot; at &lt;.Email&gt;: can&#39;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 &quot;Email&quot; .)}}Email is: {{.Email}}{{else}}Email is unavailable.{{end}}`

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

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

Output (try it on the Go Playground):

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

答案2

得分: 0

如果您在模板中使用{{ if .Variable }},但没有发送包含变量的数据到模板中,您将会收到以下错误信息:

在执行"templatename"时出错,位于<.Variable>:无法在handler.Data类型中评估字段Variable

我的解决方案是将"Email"变量作为布尔值(false)发送,以通过{{ if .Email }}函数检查。但这只是一个我不喜欢的临时解决方案。

我受到了这个例子的启发:https://stackoverflow.com/a/31527618/1564840。在那个例子中,他们展示了针对已认证和未认证用户的不同HTML。您会发现在两种情况下,他们都发送了"Logged"变量。尝试从结构体中删除该变量并执行该函数,您将收到我上面提到的错误。

英文:

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.

答案3

得分: -2

简单的做法是使用index函数:

{{ if index . "Email" }}

英文:

The simple way of doing:

{{ if .Email }}

is to use index:

{{ if index . &quot;Email&quot; }}

huangapple
  • 本文由 发表于 2017年6月21日 19:41:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/44675087.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定