使用Gin框架输出包含双引号的模板作为客户端响应

使用Gin框架输出包含双引号的模板作为客户端响应 大家好,我在尝试执行一个包含JSON的文本模板,当我使用c.JSON()将其作为客户端响应发送时,模板中的双引号"显示为\"

以下是我的模板:

package templates

//SecretJSON1 包含秘密JSON的变量
const SecretJSON1 = `{
"apiVersion": "v1",
"data": {
"DB_PASS": "{{.DBPass}}",
"DB_USER": "{{.DBUser}}",
"S3_ACCESS_KEY": "{{.S3AccessKey}}",
"S3_SECRET_KEY": "{{.S3SecretKey}}",
"S3_CREDENTIALS_FILE": "{{.S3FileCreds}}",
"HASH_SALT": "{{.HashSalt}}"
},
"kind": "Secret",
"metadata": {
"name": "{{.Name}}",
"namespace": "{{.Namespace}}"
},
"type": "Opaque"
}`

在POSTMAN中显示如下

Screenshot%20from%202019-08-15%2016-00-21

非常感谢您的帮助,谢谢


7 回复

好的,我明白了,谢谢你的帮助


是的,我知道它在结构体中有效。我必须使用模板来实现,但我无法转义双引号。

不要渲染模板,而是使用传递给 c.JSON() 的结构体或映射,并以能够序列化为所需 JSON 的方式创建它。

正如我在另一个帖子中已经说过的,你不能使用 c.JSON()。你需要发送原始响应并自行设置内容类型头部。

是的!我按照你在另一个帖子中说的做了,而且成功了 fmt.Fprintf(c.Writer, result)。但是有没有办法可以使用 c.JSON() 来实现呢?

因为我正在构建一个供用户使用的 API,我认为在整个代码库中应该尽量避免使用 fmt

fmt.Fprintf(c.Writer, result)
c.JSON()

当你将string传递给c.JSON时,它会被序列化成JSON字符串。你说你不想这样,所以你既不能使用string也不能使用c.JSON

此外,除了fmt.F*还有其他替代方案,例如io.WriteString(c.Writer, result)

不过我仍然认为,将struct传递给c.JSON是更好的方式,因为渲染模板可能比序列化更耗费资源。

io.WriteString(c.Writer, result)

在 Gin 框架中,当你使用 c.JSON() 方法发送响应时,它会自动对内容进行 JSON 编码,包括转义双引号。这是因为 c.JSON() 期望传入一个结构体或映射(map),然后将其序列化为 JSON。如果你直接传入一个已经包含 JSON 字符串的模板输出,Gin 会将其视为字符串值,并在 JSON 编码过程中转义其中的双引号。

要解决这个问题,你有两个主要选择:

  1. 使用 c.String() 并手动设置 Content-Type 为 application/json:这样可以直接输出模板渲染后的原始字符串,而不进行额外的 JSON 编码。
  2. 在 Go 中解析模板输出为结构体或映射,然后使用 c.JSON():这允许 Gin 正确序列化数据,避免转义问题。

以下是两种方法的示例代码:

方法 1:使用 c.String() 直接输出模板

首先,确保你的模板被正确渲染,然后使用 c.String() 发送响应,并设置 Content-Type 为 application/json

package main

import (
    "bytes"
    "html/template"
    "github.com/gin-gonic/gin"
)

// 定义你的模板常量(如提问中的 SecretJSON1)
const SecretJSON1 = `{
"apiVersion": "v1",
"data": {
"DB_PASS": "{{.DBPass}}",
"DB_USER": "{{.DBUser}}",
"S3_ACCESS_KEY": "{{.S3AccessKey}}",
"S3_SECRET_KEY": "{{.S3SecretKey}}",
"S3_CREDENTIALS_FILE": "{{.S3FileCreds}}",
"HASH_SALT": "{{.HashSalt}}"
},
"kind": "Secret",
"metadata": {
"name": "{{.Name}}",
"namespace": "{{.Namespace}}"
},
"type": "Opaque"
}`

func main() {
    r := gin.Default()
    
    r.POST("/secret", func(c *gin.Context) {
        // 假设从请求中获取数据,这里使用示例数据
        data := struct {
            DBPass      string
            DBUser      string
            S3AccessKey string
            S3SecretKey string
            S3FileCreds string
            HashSalt    string
            Name        string
            Namespace   string
        }{
            DBPass:      "mypassword",
            DBUser:      "myuser",
            S3AccessKey: "accesskey",
            S3SecretKey: "secretkey",
            S3FileCreds: "credsfile",
            HashSalt:    "salthash",
            Name:        "mysecret",
            Namespace:   "default",
        }
        
        // 解析并执行模板
        tmpl, err := template.New("secret").Parse(SecretJSON1)
        if err != nil {
            c.JSON(500, gin.H{"error": "Failed to parse template"})
            return
        }
        
        var buf bytes.Buffer
        err = tmpl.Execute(&buf, data)
        if err != nil {
            c.JSON(500, gin.H{"error": "Failed to execute template"})
            return
        }
        
        // 使用 c.String() 输出,并设置 Content-Type 为 application/json
        c.Data(200, "application/json", buf.Bytes())
    })
    
    r.Run(":8080")
}

在这个示例中,c.Data(200, "application/json", buf.Bytes()) 直接发送渲染后的字节数据,并指定 Content-Type 为 application/json。这避免了额外的 JSON 编码,因此双引号不会被转义。

方法 2:使用结构体与 c.JSON()

作为替代方案,你可以定义一个 Go 结构体来匹配你的 JSON 结构,然后使用 c.JSON() 进行序列化。这更符合 Gin 的预期用法,并能自动处理 JSON 编码。

package main

import "github.com/gin-gonic/gin"

// 定义与 JSON 对应的结构体
type Secret struct {
    APIVersion string            `json:"apiVersion"`
    Data       map[string]string `json:"data"`
    Kind       string            `json:"kind"`
    Metadata   map[string]string `json:"metadata"`
    Type       string            `json:"type"`
}

func main() {
    r := gin.Default()
    
    r.POST("/secret", func(c *gin.Context) {
        // 填充结构体数据
        secret := Secret{
            APIVersion: "v1",
            Data: map[string]string{
                "DB_PASS":             "mypassword",
                "DB_USER":             "myuser",
                "S3_ACCESS_KEY":       "accesskey",
                "S3_SECRET_KEY":       "secretkey",
                "S3_CREDENTIALS_FILE": "credsfile",
                "HASH_SALT":           "salthash",
            },
            Kind: "Secret",
            Metadata: map[string]string{
                "name":      "mysecret",
                "namespace": "default",
            },
            Type: "Opaque",
        }
        
        // 使用 c.JSON() 发送,Gin 会自动处理 JSON 序列化
        c.JSON(200, secret)
    })
    
    r.Run(":8080")
}

在这个示例中,我们定义了一个 Secret 结构体,并使用 c.JSON(200, secret) 发送响应。Gin 会正确序列化结构体为 JSON,双引号不会被转义。

推荐使用方法 2,因为它更简洁、类型安全,并符合 Gin 框架的最佳实践。如果你的模板是动态生成的或来自外部源,方法 1 可能更合适。根据你的具体需求选择即可。

回到顶部