Golang中JSON响应包含转义双引号的问题

Golang中JSON响应包含转义双引号的问题 问题描述: curl 命令返回的数据中包含转义的双引号,如下所示

curl -X GET http://test.com/api/appointment {"Title":"OK","status":200,"records":2,"data":"[{\"id\":14,\"start\":\"2022-07-03T18:15:00Z\"}]

期望的输出是不带转义双引号的 {“Title”:“OK”,“status”:200,“records”:2,“data”:"[{“id”:14,“start”:“2022-07-03T18:15:00Z”}]

现在来解释程序逻辑。

需求是使用 gorm 检索数据库记录,并使用 json.NewEncoder(w).Encode 以 JSON 格式作为 API 响应发送回客户端。为此设计了以下结构体,其中 ApiResponse 结构体用于保存数据库查询结果(“Data”)以及元数据,如标题、状态和记录数。Appointment 结构体用于保存检索到的数据库记录,这些记录随后将嵌入到 API 响应中。由于我对不同的数据库表使用不同的结构体,检索到的数据库记录将存储在相应的结构体中,然后序列化为字节,再转换为字符串。最后,写回 http writer。

以下结构体用于构建 API 响应。

type ApiResponse struct {
    Title  string `json:"Title"`
    Status uint   `json:"status"`
    Records int64   `json:"records"`
    Data  string `json:"data"`
}

以下结构体保存从数据库检索到的记录。

type Appointment struct {
    Id       uint       `json:"id"`
    Name     string    `json:"name"`
    Start    time.Time  `json:"start"`
    ....SKIPPING THE REST....
}

// Appointment 是一个结构体,用于保存使用 gorm 检索到的数据库记录 // 将 appointment 序列化为 []byte,以便稍后转换为字符串。

data, _ := json.Marshal(appointment)

records := string(data)
// 将响应写回客户端
json.NewEncoder(w).Encode(ApiResponse{Title: "OK", Status: http.StatusOK, Records: utils.RowsAffected, Data: records })

除了输出中带有转义的双引号外,这完全符合我的需求。

"[{\"id\":14,\"start\":\"2022-07-03T18:15:00Z\"}]

请帮我找出那一两行代码,能够以最优的方式移除输出中的 \。


更多关于Golang中JSON响应包含转义双引号的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

不,因为那样不会是有效的JSON。

更多关于Golang中JSON响应包含转义双引号的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如果引号没有被转义,那么JSON就是无效的。

Data 是一个字符串,而不是数组,因此其中的每个引号都需要被转义。

既然你没有使用 JSON,就不要使用 JSON 相关的包。为了满足你对非标准字符串的要求,你需要编写自己的编码器和解码器,同时你的 API 使用者也需要做同样的事情,因为他们在这里无法使用 JSON 库。

你可以选择在那里使用某种接口类型,或者使用那种字符串形式的处理方式,不过那样的话,你真的应该使用正确编码的字符串,而不是使用其他看起来像但实际并非有效 JSON 的东西。

谢谢。但我正在寻找一种解决方案,以生成不带转义双引号的JSON字符串。在我的情况下,如何实现这一点。有没有办法在使用 json.NewEncoder(w).Encode 时实现以下JSON。

{"Title":"OK","status":200,"records":2,"data":"[{"id":14,"start":"2022-07-03T18:15:00Z"}]}

问题在于你的 Data 属性是一个字符串,而这个字符串恰好包含了 JSON 数据。因此,Go 正确地转义了该字符串中的引号。将你的结构体修改为包含一个子结构体数组,而不是一个字符串,如下所示:

package main

import (
	"encoding/json"
	"net/http"
	"os"
	"time"
)

type Appointment struct {
	Id    uint      `json:"id"`
	Name  string    `json:"name"`
	Start time.Time `json:"start"`
}

type ApiResponse struct {
	Title   string        `json:"Title"`
	Status  uint          `json:"status"`
	Records int64         `json:"records"`
	Data    []Appointment `json:"data"`
}

func main() {
	appts := []Appointment{{1, "Apt Name", time.Now()}}
	resp := ApiResponse{Title: "OK", Status: http.StatusOK, Records: 23, Data: appts}
	json.NewEncoder(os.Stdout).Encode(resp)
}

… 这将产生以下输出:

{“Title”:“OK”,“status”:200,“records”:23,“data”:[{“id”:1,“name”:“Apt Name”,“start”:“2009-11-10T23:00:00Z”}]}

你可以在 Playground 中亲自运行它:Go Playground - The Go Programming Language

再次感谢您抽出时间。但这里的 Data 不是一个单一的结构体。它是一个通用的容器,应该容纳不同结构体的数据,例如客户、预约、员工。因此需要一个灵活的数据类型,比如字符串,以便在将这些结构体作为 API 响应发送之前将其转换为字符串。

//Struct for Appoinment
type Appointment struct {
Id    uint      `json:"id"`
Name  string    `json:"name"`
Start time.Time `json:"start"`
}
//Struct for Customers
type Customers struct {
Id unit `json:"id"`
Name  string    `json:"name"`
Phone string    `json:"phone"`
DOB time         `json:"dob"`
}
//Struct for Employees
type Employee struct {
Id unit `json:"id"`
Name  string    `json:"name"`
Phone string    `json:"phone"`
Designation  string       `json:"designation"`
}

预期的 API 请求和响应 curl -X GET http://test.com/api/appointment {“Title”:“OK”,“status”:200,“records”:1,“data”:"[{“id”:14,“start”:“2022-07-03T18:15:00Z”}]

curl -X GET http://test.com/api/customers {“Title”:“OK”,“status”:200,“records”:1,“data”:"[{“id”:14,“name”:“John", “Phone”:+810000000", “11-11-11”}] & 员工的 curl 请求

虽然可以定义不同的 API 响应结构体,但这似乎效率不高。是否有更好的实现方式?

问题在于你将已经序列化为JSON字符串的数据再次作为字符串字段进行JSON编码,导致了双重编码。Data字段应该是[]Appointment类型,而不是字符串类型。

修改ApiResponse结构体:

type ApiResponse struct {
    Title   string         `json:"Title"`
    Status  uint           `json:"status"`
    Records int64          `json:"records"`
    Data    []Appointment  `json:"data"`  // 直接使用结构体切片
}

然后直接传入appointment切片:

// 假设appointments是[]Appointment类型
json.NewEncoder(w).Encode(ApiResponse{
    Title:   "OK", 
    Status:  http.StatusOK, 
    Records: utils.RowsAffected, 
    Data:    appointments,  // 直接传入结构体切片
})

如果你确实需要保持Data字段为字符串类型(不推荐),可以在编码前使用json.RawMessage

type ApiResponse struct {
    Title   string          `json:"Title"`
    Status  uint            `json:"status"`
    Records int64           `json:"records"`
    Data    json.RawMessage `json:"data"`
}

// 使用方式
data, _ := json.Marshal(appointments)
json.NewEncoder(w).Encode(ApiResponse{
    Title:   "OK",
    Status:  http.StatusOK,
    Records: utils.RowsAffected,
    Data:    data,
})

推荐使用第一种方案,让JSON编码器一次性处理所有数据,避免双重编码问题。

回到顶部