在 Go 中将 SQL 行转换为 JSON 格式

Pav*_*ndu 4 mysql rest go

在我正在开发的 REST API 中,有一个/courses端点以 JSON 格式返回从 SQL 表查询的一些数据。但是,我找不到将查询数据(行)转换为 JSON 的方法。

func GetCoursesEndpoint(w http.ResponseWriter, req *http.Request) {
  db, err := sql.Open("mysql", "root:@/academy")
  checkErr(err)

  rows, err := db.Query("SELECT course_name,price FROM course;")
  checkErr(err)

  //how to convert returned rows to JSON?
  msg, err := json.Marshal(rows)
  checkErr(err)

  json.NewEncoder(w).Encode(msg)
  return  
}
Run Code Online (Sandbox Code Playgroud)

mko*_*iva 6

*sql.Rows类型的实例不能直接编组到 json 中。它没有实现json.Marshaler接口,并且它的所有字段都未导出,因此无法访问encoding/json包。

您需要做的是将行的内容扫描到一个可以编组的中间对象中,然后将该对象编组为 json。

所以首先,首先声明一个代表这个“中间”对象的类型,例如:

type Course struct {
    Name  string
    Price int
}
Run Code Online (Sandbox Code Playgroud)

然后,由于您要选择多个记录,您需要使用它的Next方法迭代行对象,并在每次迭代时将记录的内容扫描到该Course类型的实例中。

var courses []*Course // declare a slice of courses that will hold all of the Course instances scanned from the rows object
for rows.Next() { // this stops when there are no more rows
    c := new(Course) // initialize a new instance
    err := rows.Scan(&c.Name, &c.Price) // scan contents of the current row into the instance
    if err != nil {
        return err
    }

    courses = append(courses, c) // add each instance to the slice
}
if err := rows.Err(); err != nil { // make sure that there was no issue during the process
    return err
}
Run Code Online (Sandbox Code Playgroud)

最后,您可以courses通过将切片传递给编码器来将切片转换为 json。

if err := json.NewEncoder(w).Encode(courses); err != nil {
    log.Println(err)
}
Run Code Online (Sandbox Code Playgroud)

如果您将上述建议应用于您的处理程序,您应该开始看到您期望的结果,或类似的结果……但是,如果您不希望应用程序崩溃,您的处理程序还有一些其他问题需要解决.

第一的:

db, err := sql.Open("mysql", "root:@/academy")
Run Code Online (Sandbox Code Playgroud)

每次执行处理程序时都没有必要打开一个连接,因此如果您将 open-db-connection 代码移到处理程序之外并且只使db处理程序可以访问该变量会更好。但是,如果您希望每次都保持打开连接,请确保每次也关闭它,否则您将用完可用连接。

第二:

rows, err := db.Query("SELECT course_name,price FROM course;")
Run Code Online (Sandbox Code Playgroud)

返回的rows对象需要关闭,原因与db每次打开句柄时都需要关闭句柄的原因相同,即您将用完可用连接并且您的应用程序将崩溃。

因此,应该可以正常工作的更完整的代码版本如下所示:

var db *sql.DB // declare a global variable that will be used by all handlers

func init() {
    var err error
    db, err = sql.Open("mysql", "root:@/academy") // initialize the global connection
    if err != nil {
        panic(err)
    }
}

type Course struct {
    Name  string
    Price int
}

func GetCoursesEndpoint(w http.ResponseWriter, req *http.Request) {
    rows, err := db.Query("SELECT course_name,price FROM course;")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer rows.Close() // make sure rows is closed when the handler exits

    var courses []*Course
    for rows.Next() {
        c := new(Course)
        err := rows.Scan(&c.Name, &c.Price)
        if err != nil {
            fmt.Println(err)
            return
        }

        courses = append(courses, c)
    }
    if err := rows.Err(); err != nil {
        fmt.Println(err)
        return
    }

    if err := json.NewEncoder(w).Encode(courses); err != nil {
        fmt.Println(err)
    }
    return
}
Run Code Online (Sandbox Code Playgroud)