gin 的 c.html() 直接支持 map[string]interface{} 传参,推荐使用 gin.h;注意字段导出规则、避免不可序列化类型、嵌套 map 需用 index 函数或平铺结构,并谨慎使用 | safe 防 xss。

直接用 c.HTML() 第三个参数传 map[string]interface{} 就行
Gin 的 c.HTML() 方法天然支持把 Go 的 map[string]interface{} 作为数据源传给模板,不需要额外包装或转换。只要 key 是字符串、value 能被 html/template 安全渲染(比如 string、int、struct 等),就能直接用 {{.Key}} 取值。
常见错误现象是传了 map[string]string 却在模板里访问小写字段——Go 模板只认导出字段,但 map 的 key 本身不是字段,所以不影响;真正影响的是:如果你传的是结构体嵌套在 map 里,那结构体内部字段必须首字母大写。
- 推荐用
gin.H,它是map[string]interface{}的别名,写起来更简洁 - 避免用
map[string]string存数字或布尔值,否则模板里{{.Age | add 1}}会报错:不支持 string 类型的 pipeline 操作 - 如果 map 的 value 是指针(如
*string),模板中访问时要加判断,否则 nil 指针会触发nil pointer evaluating interface {}.Name
gin.H 和手写 map[string]interface{} 没区别,但别混用类型
gin.H{"title": "home", "count": 42} 和 map[string]interface{}{"title": "home", "count": 42} 在 c.HTML() 里行为完全一致。Gin 不做类型校验,底层都走同一个反射路径。
容易踩的坑是:在同一个项目里一部分路由用 gin.H,另一部分用自定义 struct,结果忘了 struct 字段要大写,而 map 不受此限——导致「为什么 map 能取到值,struct 就是空」这类困惑。
- 统一用
gin.H快速原型时最省心 - 需要复用或类型约束时,改用 struct,但必须确保字段首字母大写
- 不要把函数、channel、mutex 等不可序列化类型塞进 map,模板执行时 panic
模板里访问嵌套 map 要用 .key.subkey,不是 .key["subkey"]
Go 原生模板不支持方括号语法访问 map 元素。如果你传了 gin.H{"user": map[string]string{"name": "alice", "role": "admin"}},模板里只能写 {{.user.name}},不能写 {{.user["name"]}}——后者会报错 unexpected "["。
这是因为 html/template 把 .user 当作一个值(map),然后调用其 name 方法或字段;而 map 类型没有 name 方法,但它实现了 index 函数,所以正确写法其实是 {{index .user "name"}},但前提是你要注册这个函数(见下一条)。
- 最简单做法:传平铺结构,比如
gin.H{"user_name": "alice", "user_role": "admin"} - 想保持嵌套?提前转成 struct,或用
template.FuncMap注册index函数 - 别依赖
.user.name访问 map 的 key——它实际是调用user.MapIndex(reflect.ValueOf("name")),行为不稳定
传 map 时要注意 HTML 自动转义,特殊内容要用 {{.Content | safe}}
所有通过 {{.xxx}} 输出的 map value 都会被自动 HTML 转义。如果你传了带 HTML 标签的字符串(比如富文本编辑器内容),页面上会显示原样标签,而不是渲染效果。
解决方法是显式标记为安全:模板里写 {{.HTMLContent | safe}},前提是这个值你确认可信。Gin 不提供类似 Django 的 |safe 自动开关,必须手动加。
- 不要在 handler 里用
template.HTML包装再传入 map——html/template会识别它并跳过转义,但这是合法且常用的做法 - 更稳妥的方式:在注册
FuncMap时加一个html函数,把字符串转成template.HTML - 千万别对用户输入直接用
| safe,XSS 漏洞就在这儿埋着











