text/template如何确定地图的"default textual representation"?

How does text/template determine the "default textual representation" of a map?

根据 Go 标准库中 text/template 包的 documentation,(据我所知,html/template 在这里是一样的)简单地使用管道运算符会吐输出一个 "default textual representation" 的任何东西:

{{pipeline}}

The default textual representation of the value of the pipeline is copied to the output.

在地图的情况下,您会得到一个很好的打印格式,其中包含键名和所有内容...顺便说一句,这是有效的 JavaScript,因此可以轻松地将整个结构传递到您的JS代码,如果你想要。

我的问题是,这个文本表示是如何确定的,更具体地说,我可以挂钩它吗?我想也许它会检查管道是否是 fmt.Stringer 并且我可以给我的地图子类型一个 String() string 方法,但事实似乎并非如此。我正在寻找 text/template 代码,但我似乎不知道它是如何做到这一点的。

text/template如何确定"default textual representation"?

默认文本表示由 fmt 包打印值的方式决定。所以你找对了树。

看这个例子:

t := template.Must(template.New("").Parse("{{.}}"))
m := map[string]interface{}{"a": "abc", "b": 2}
t.Execute(os.Stdout, m)

它输出:

map[a:abc b:2]

现在,如果我们使用带有 String() 方法的自定义地图类型:

type MyMap map[string]interface{}

func (m MyMap) String() string { return "custom" }

mm := MyMap{"a": "abc", "b": 2}
t.Execute(os.Stdout, mm)

输出为:

custom

Go Playground.

上尝试这些(以及以下示例)

需要注意什么?

注意 MyMap.String() 有一个值接收者(不是指针)。我传递了一个 MyMap 的值,所以它起作用了。如果将接收器类型更改为指向 MyMap 的指针,它将不起作用。这是因为只有 *MyMap 类型的值才会有 String() 方法,而不是 MyMap.

类型的值

如果 String() 方法有一个指针接收器,如果您希望自定义表示起作用,则必须传递 &mm(类型 *MyMap 的值)。

另请注意,在 html/template 的情况下,模板引擎会进行上下文转义,因此 fmt 包的结果可能会进一步转义。

例如,如果您的自定义 String() 方法会 return 一些“不安全”的东西:

func (m MyMap2) String() string { return "<html>" }

正在尝试插入:

mm2 := MyMap2{"a": "abc", "b": 2}
t.Execute(os.Stdout, mm2)

被转义:

&lt;html&gt;

实施

这是它在 text/template package: text/template/exec.go 中实现的地方,未导出的函数 state.PrintValue(),当前行 #848:

_, err := fmt.Fprint(s.wr, iface)

如果您正在使用 html/template package, it's implemented in html/template/content.go,未导出的函数 stringify(),当前第 135 行:

return fmt.Sprint(args...), contentTypePlain

更多选项

另请注意,如果该值实现 error,则将调用 Error() 方法并且它优先于 String():

type MyMap map[string]interface{}

func (m MyMap) Error() string { return "custom-error" }

func (m MyMap) String() string { return "custom" }

t := template.Must(template.New("").Parse("{{.}}"))
mm := MyMap{"a": "abc", "b": 2}
t.Execute(os.Stdout, mm)

将输出:

custom-error

而不是 custom。在 Go Playground.

上试用