模板中的无指针评估......为什么?有更好的策略吗?

时间:2016-03-11 01:43:54

标签: go go-templates

我正在尝试包装html / template,因此除了要渲染的数据外,我还保证我的模板中有某些数据(例如会话数据)。但是,我目前的方法是......有缺陷的。以下是一个简化示例:

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\n")
    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("\nRendering Foo\n")
    render(struct {
        Foo string
    }{
        "foo",
    })

    // ...but this breaks.
    fmt.Print("\nRendering nil\n")
    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
    }
}

我得到以下输出:

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

所以我不太确定首先发生了什么 - 为什么html /模板能够处理传递nil,但无法形象用nil指针做什么?

其次,有没有更好的方法来解决这个问题?

1 个答案:

答案 0 :(得分:1)

最好的办法是始终将数据设为地图或结构,方法是将类型设为地图或结构,或者不使用带interface{}的nil:

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

播放:http://play.golang.org/p/9GkAp6ysvD

至于它为何如此工作,它有点复杂,你必须查看代码:https://golang.org/src/text/template/exec.go?s=4647:4717#L521

当使用nil调用execute时,reflect.ValueOf(nil)返回一个无效的值,因此evalField返回零值,最后得到一个空字符串。

但是,当使用有效结构调用execute时,首先反映.ValueOf返回有效值。 .Data命令在传递给Execute的整个struct上调用evalField,evalField调用FieldByIndex / FieldByName来获取&#34; Data&#34;领域。这不会返回无效的值。

接下来,当评估.Foo时,如果Data是一个接口或一个指针,indirect函数会跟随它到最后,如果它发现它是nil,它就会失败有这个错误。

当数据是地图时,间接功能不做任何事情,并且它不会失败。

这可能是文本/模板包中的错误。