I am attempting to wrap html/template so I am guaranteed to have certain data in my templates (session data for example) in addition to the data I want to render. However, my current approach is...flawed. Here's a simplified example below:
package main
import "fmt"
import "os"
import "html/template"
func main() {
// Passing nil directly to Execute doesn't render anything for missing struct fields
fmt.Print("Directly rendering nil
")
tmpl, err := template.New("master").Parse("Foo is: {{.Data.Foo}}")
if err != nil {
fmt.Printf(err.Error())
return
}
err = tmpl.Execute(os.Stdout, nil)
if err != nil {
fmt.Printf(err.Error())
return
}
// Wrapping templates works as long as I supply data...
fmt.Print("
Rendering Foo
")
render(struct {
Foo string
}{
"foo",
})
// ...but this breaks.
fmt.Print("
Rendering nil
")
render(nil)
}
func render(data interface{}) {
allData := struct {
Session string
Data interface{}
}{
"sessionData",
data,
}
// Hardcoded template for the example - this could be any arbitrary template
tmpl, err := template.New("master").Parse("Foo is: {{.Data.Foo}}")
if err != nil {
fmt.Printf(err.Error())
return
}
err = tmpl.Execute(os.Stdout, allData)
if err != nil {
fmt.Printf(err.Error())
return
}
}
I get the following output:
Directly rendering nil
Foo is:
Rendering Foo
Foo is: foo
Rendering nil
Foo is: template: master:1:15: executing "master" at <.Data.Foo>: nil pointer evaluating interface {}.Foo
So I'm not quite sure what's going on in the first place - why is it that html/template is capable of handling being passed nil
, but can't figure out what to do with a nil pointer?
Secondly, is there a better way of approaching this problem?
Your best bet is to always make Data a map or struct, either by making the type a map or struct, or by not using a nil with interface{}
:
package main
import "fmt"
import "os"
import "text/template"
func main() {
tmpl, err := template.New("master").Parse("{{if .Data.Foo}}Foo is: {{.Data.Foo}}{{else}}Foo is empty{{end}}")
if err != nil {
fmt.Printf(err.Error())
return
}
err = tmpl.Execute(os.Stdout, nil)
if err != nil {
fmt.Printf(err.Error())
return
}
fmt.Println("")
err = tmpl.Execute(os.Stdout, struct {
Session string
Data map[string]string
}{
"sessionData",
nil,
})
if err != nil {
fmt.Printf(err.Error())
return
}
fmt.Println("")
err = tmpl.Execute(os.Stdout, struct {
Session string
Data interface{}
}{
"sessionData",
map[string]string{},
})
if err != nil {
fmt.Printf(err.Error())
return
}
fmt.Println("")
}
Play: http://play.golang.org/p/9GkAp6ysvD
As for why it works like this, it's a bit complicated, you have to look at the code: https://golang.org/src/text/template/exec.go?s=4647:4717#L521
When execute is called with nil, reflect.ValueOf(nil)
returns an invalid Value, so evalField returns the zero value, and you end up with an empty string.
However, when execute is called with a valid struct, that first reflect.ValueOf returns a valid value. The .Data
command calls evalField on the whole struct you passed to Execute, and evalField calls FieldByIndex/FieldByName to get the "Data" field. This doesn't return an invalid Value.
Next, when .Foo
is evaluated, if Data is an interface or a pointer, the indirect function follows it to the end, and if it finds that it's nil, it fails with this error.
When Data is a map, the indirect function doesn't do anything, and it doesn't fail.
This might be a bug in the text/template package.