3

我正在尝试包装 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/template 能够处理被传递nil,但无法弄清楚如何处理 nil 指针?

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

4

1 回答 1

3

您最好的选择是始终使 Data 成为映射或结构,方法是使类型成为映射或结构,或者不使用带有 的 nil 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("")
}

播放: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 时,首先 reflect.ValueOf 返回一个有效值。该.Data命令在您传递给 Execute 的整个结构上调用 evalField,而 evalField 调用 FieldByIndex/FieldByName 以获取“数据”字段。这不会返回无效值。

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

当 Data 是一个映射时,间接函数什么都不做,也不会失败。

这可能是 text/template 包中的错误。

于 2016-03-11T16:25:31.483 回答