Golang中从查询结果获取嵌套JSON的最佳实践
Golang中从查询结果获取嵌套JSON的最佳实践 我遇到需要返回嵌套 JSON 的情况。示例:
type Post struct{
ID int json:"id"
Title string json:"title"
Author Author json:"author"
Comment []Comment json:"comment"
}
type Author struct {
ID int json:"id"
Name string json:"name"
}
type Comment struct {
ID int json:"id"
Text string json:"text"
}
返回结果应为:
{
"total_result" : 50,
"current_page" : 1,
"posts" : [
{
"title" : "title 1",
"author" : [
"name" : "author 1",
],
"comment" : [
{"text" : "commentary 1.1"}, {"text" : "commentary 1.2"}, ....
]
},
{
"title" : "title 2",
"author" : [
"name" : "author 2",
],
"comment" : [
{"text" : "commentary 2.1"}, {"text" : "commentary 2.2"}, ....
]
}
]
}
我的问题是:执行这样的查询是否是一个好主意:
query := "query that gets the post data with limit and offset of course"
rows, err := db.QueryContext(ctx, query)
//handle error
var record []Post
for rows.Next(){
var post Post
if err := rows.Scan(&post....) {..}
record = append(record, post)
}
然后,我通过循环来获取作者信息:
for i := 0; i < len(record); i++ {
var author Author
query = "select id,name from authors where id=$1"
rows, err := db.QueryContext(ctx, query, record[i].Id)
//handel error
//scan data to author
record[i].Author.Id = author.Id
record[i].Author.Name = author.Name
}
如果帖子有评论,获取评论的过程也类似。
但是,我在思考如何能通过一个过程就获取到作者和评论信息。这就像在扫描第一个查询(选择帖子的查询)时,在 for rows.Next() 的帖子扫描过程中进行内部循环,但我失败了,因为第一个查询需要 rows.Close()。而当我这样做时,扫描过程无法进行,因为连接已经关闭。
提前感谢大家。
更多关于Golang中从查询结果获取嵌套JSON的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang中从查询结果获取嵌套JSON的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Golang中处理嵌套JSON返回,最佳实践是使用JOIN查询配合手动数据组装。以下是具体实现方案:
// 定义响应结构体
type PostResponse struct {
TotalResult int `json:"total_result"`
CurrentPage int `json:"current_page"`
Posts []Post `json:"posts"`
}
// 查询并组装数据的实现
func GetPostsWithDetails(ctx context.Context, db *sql.DB, page, limit int) (*PostResponse, error) {
offset := (page - 1) * limit
// 查询帖子总数
var total int
err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM posts").Scan(&total)
if err != nil {
return nil, err
}
// 使用JOIN查询获取所有相关数据
query := `
SELECT
p.id, p.title,
a.id, a.name,
c.id, c.text
FROM posts p
LEFT JOIN authors a ON p.author_id = a.id
LEFT JOIN comments c ON p.id = c.post_id
WHERE p.id IN (
SELECT id FROM posts
ORDER BY id
LIMIT $1 OFFSET $2
)
ORDER BY p.id, c.id
`
rows, err := db.QueryContext(ctx, query, limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
// 使用map临时存储数据
postsMap := make(map[int]*Post)
for rows.Next() {
var postID int
var postTitle string
var authorID sql.NullInt64
var authorName sql.NullString
var commentID sql.NullInt64
var commentText sql.NullString
err := rows.Scan(
&postID, &postTitle,
&authorID, &authorName,
&commentID, &commentText,
)
if err != nil {
return nil, err
}
// 如果帖子不存在于map中,创建新条目
if _, exists := postsMap[postID]; !exists {
postsMap[postID] = &Post{
ID: postID,
Title: postTitle,
Author: Author{
ID: int(authorID.Int64),
Name: authorName.String,
},
Comment: []Comment{},
}
}
// 添加评论(如果存在)
if commentID.Valid && commentText.Valid {
comment := Comment{
ID: int(commentID.Int64),
Text: commentText.String,
}
postsMap[postID].Comment = append(postsMap[postID].Comment, comment)
}
}
// 将map转换为切片
posts := make([]Post, 0, len(postsMap))
for _, post := range postsMap {
posts = append(posts, *post)
}
// 构建响应
response := &PostResponse{
TotalResult: total,
CurrentPage: page,
Posts: posts,
}
return response, nil
}
对于更复杂的嵌套结构,可以使用JSON聚合函数(如果数据库支持):
// PostgreSQL示例使用json_agg
func GetPostsWithJSONAgg(ctx context.Context, db *sql.DB, page, limit int) (*PostResponse, error) {
offset := (page - 1) * limit
query := `
SELECT
p.id,
p.title,
json_build_object(
'id', a.id,
'name', a.name
) as author,
COALESCE(
json_agg(
json_build_object('id', c.id, 'text', c.text)
) FILTER (WHERE c.id IS NOT NULL),
'[]'
) as comments
FROM posts p
LEFT JOIN authors a ON p.author_id = a.id
LEFT JOIN comments c ON p.id = c.post_id
GROUP BY p.id, a.id, p.title
ORDER BY p.id
LIMIT $1 OFFSET $2
`
rows, err := db.QueryContext(ctx, query, limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
var posts []Post
for rows.Next() {
var post Post
var authorJSON []byte
var commentsJSON []byte
err := rows.Scan(
&post.ID,
&post.Title,
&authorJSON,
&commentsJSON,
)
if err != nil {
return nil, err
}
// 解析JSON到结构体
json.Unmarshal(authorJSON, &post.Author)
json.Unmarshal(commentsJSON, &post.Comment)
posts = append(posts, post)
}
// 查询总数
var total int
err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM posts").Scan(&total)
if err != nil {
return nil, err
}
return &PostResponse{
TotalResult: total,
CurrentPage: page,
Posts: posts,
}, nil
}
使用sqlx库可以简化代码:
import "github.com/jmoiron/sqlx"
func GetPostsWithSqlx(ctx context.Context, db *sqlx.DB, page, limit int) (*PostResponse, error) {
offset := (page - 1) * limit
query := `
SELECT
p.id, p.title,
a.id as "author.id",
a.name as "author.name",
json_agg(
json_build_object('id', c.id, 'text', c.text)
) as comments
FROM posts p
LEFT JOIN authors a ON p.author_id = a.id
LEFT JOIN comments c ON p.id = c.post_id
GROUP BY p.id, a.id, p.title
ORDER BY p.id
LIMIT $1 OFFSET $2
`
var posts []Post
err := db.SelectContext(ctx, &posts, query, limit, offset)
if err != nil {
return nil, err
}
var total int
err = db.GetContext(ctx, &total, "SELECT COUNT(*) FROM posts")
if err != nil {
return nil, err
}
return &PostResponse{
TotalResult: total,
CurrentPage: page,
Posts: posts,
}, nil
}
这些方法避免了N+1查询问题,通过单次数据库查询获取所有需要的数据,然后通过代码逻辑组装成嵌套结构。

