为什么 JSONB 返回的是字符串?

huangapple go评论84阅读模式
英文:

Why JSONB is returning as a string?

问题

我已经开始了一个“Instagram克隆”后端,试图学习一些关于Golang的知识,但我遇到了一个我不知道如何解决的问题。

我使用Fiber构建了一个简单的API来获取一些帖子:

package server

import (
	"fmt"
	"instagram/internal/psql"
	queries "instagram/internal/sql"
	"net/http"

	"github.com/gofiber/fiber/v2"
)

type Like struct {
	Username string
	Name     string
	Picture  string
}

type Post struct {
	Id          string `json:"id"`
	Description string `json:"description"`
	Media       string `json:"media"`
	Type        string `json:"type"`
	Likes       string `json:"likes"`
	User_id     string `json:"user_id"`
	Created_at  string `json:"created_at"`
	Updated_at  string `json:"updated_at"`
}

func ListPosts(c *fiber.Ctx) error {
	rows, err := psql.DB().Query(queries.GetAllPosts)

	if err != nil {
		c.SendStatus(400)
		return nil
	}

	defer rows.Close()
	var data []Post

	for rows.Next() {
		var post Post
		err := rows.Scan(&post.Id, &post.Description, &post.Media, &post.Type, &post.Created_at, &post.Updated_at, &post.Likes, &post.User_id)
		if err != nil {
			fmt.Print(err)
			c.SendStatus(400)
			return nil
		}

		data = append(data, post)
	}

	return c.Status(http.StatusOK).JSON(data)
}

问题在于likes属性以字符串形式返回:

[
	{
		"id": "...",
		"description": "...",
		"media": "...",
		"type": "...",
		"likes": "[]",
		"user_id": "...",
		"created_at": "...",
		"updated_at": "..."
	}
]

我尝试了一些方法,比如使用json.Marshal(data),我还编写了Like结构体,但是我无法使其工作,因为当我将Likes的类型从string更改为[]Like时,调用Scan时会出现以下消息:

sql: Scan error on column index 6, name "likes": unsupported Scan, storing driver.Value type []uint8 into type *[]server.Likesql: Scan error on column index 6, name "likes": unsupported Scan, storing driver.Value type []uint8 into type *[]server.Like

我正在使用PostgreSQL数据库,并且我是第一次尝试使用jsonb列,所以likes在数据库中是一个jsonb列。

我理想的有效负载应该是这样的:

[
    {
        "id": "...",
        "description": "...",
        "media": "...",
        "type": "...",
        "likes": [],
        "user_id": "...",
        "created_at": "...",
        "updated_at": "..."
    }
]

请注意,likes现在是一个数组,而不是一个字符串。所以,有人知道如何解决这个问题吗?

英文:

I have started a "instagram clone" backend to try learn somethings about golang, but I'm having a problem that I do not know how to solve.

I have build a simple API with Fiber that get some posts:

package server
import (
"fmt"
"instagram/internal/psql"
queries "instagram/internal/sql"
"net/http"
"github.com/gofiber/fiber/v2"
)
type Like struct {
Username string
Name     string
picture  string
}
type Post struct {
Id          string `json:"id"`
Description string `json:"description"`
Media       string `json:"media"`
Type        string `json:"type"`
Likes       string `json:"likes"`
User_id     string `json:"user_id"`
Created_at  string `json:"created_at"`
Updated_at  string `json:"updated_at"`
}
func ListPosts(c *fiber.Ctx) error {
rows, err := psql.DB().Query(queries.GetAllPosts)
if err != nil {
c.SendStatus(400)
return nil
}
defer rows.Close()
var data []Post
for rows.Next() {
var post Post
err := rows.Scan(&post.Id, &post.Description, &post.Media, &post.Type, &post.Created_at, &post.Updated_at, &post.Likes, &post.User_id)
if err != nil {
fmt.Print(err)
c.SendStatus(400)
return nil
}
data = append(data, post)
}
return c.Status(http.StatusOK).JSON(data)
}

The problem is that likes attribute is returning as a string:

[
{
"id": "...",
"description": "...",
"media": "...",
"type": "...",
"likes": "[]",
"user_id": "...",
"created_at": "...",
"updated_at": "..."
}
]

I have tried some things, like use json.Marshal(data), I also wrote that Like struct, but I could not make it work, because when I change the type of Likes from string to []Like the following message appears when Scan is called

>sql: Scan error on column index 6, name "likes": unsupported Scan, storing driver.Value type []uint8 into type *[]server.Likesql: Scan error on column index 6, name "likes": unsupported Scan, storing driver.Value type []uint8 into type *[]server.Like

I'm using a postgresql database, and I'm trying to work with jsonb columns for the first time, so likes is in the database a jsonb column.

My ideal payload would be like:

[
{
"id": "...",
"description": "...",
"media": "...",
"type": "...",
"likes": [],
"user_id": "...",
"created_at": "...",
"updated_at": "..."
}
]

Note that likes is now a array instead of a string. So anybody knows how to solve this?

答案1

得分: 2

你很可能需要实现 sql.Driver 接口来“了解”如何将数据扫描到你的结构体中,可能是这样的:

type Like struct {
    Username string
    Name     string
    Picture  string
}

type Likes []Like

func (l Likes) Value() (driver.Value, error) {
    return json.Marshal(l)
}

// 让 Likes 实现 sql.Scanner 接口。
func (l *Likes) Scan(value interface{}) error {
    b, ok := value.([]byte)
    if !ok {
        return errors.New("类型断言为 []byte 失败")
    }

    return json.Unmarshal(b, &l)
}

这段代码定义了一个 Like 结构体和一个 Likes 切片,Likes 切片实现了 Value()Scan() 方法,用于在数据库和结构体之间进行转换。Value() 方法将 Likes 切片转换为数据库可以存储的值,而 Scan() 方法将数据库中的值转换为 Likes 切片。

英文:

You most likely need to implement the interface for sql.Driver to "know" how to scan into your struct, probably something like

type Like struct {
Username string
Name     string
picture  string
}
type Likes []Like
func (l Likes) Value() (driver.Value, error) {
return json.Marshal(l)
}
// Make the Likes implement the sql.Scanner interface.
func (l *Likes) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
return json.Unmarshal(b, &l)
}

答案2

得分: 1

也许这只是因为你使用的数据库驱动程序不支持jsonb数据类型。

我已经测试了github.com/jackc/pgx/v5包,它可以正确解码jsonb字段。请参考下面的示例:

package main

import (
	"context"
	"log"

	"github.com/jackc/pgx/v5"
)

func main() {
	// 记得修改下面的 connString。
	connString := "postgres://username:password@localhost:5432/database_name"
	conn, err := pgx.Connect(context.Background(), connString)
	if err != nil {
		log.Fatalf("无法连接到数据库:%v\n", err)
	}
	defer conn.Close(context.Background())

	type Like struct {
		Name    string
		Picture string
	}

	type Post struct {
		Id    string
		Likes []Like
	}

	var post Post
	err = conn.QueryRow(context.Background(), `select 'the_id', '[{"name":"a","picture":"a.png"},{"name":"b","picture":"b.png"}]'::jsonb`).Scan(&post.Id, &post.Likes)
	if err != nil {
		log.Fatalf("QueryRow 失败:%v\n", err)
	}

	log.Printf("%#v", post)
}

输出:

main.Post{Id:"the_id", Likes:[]main.Like{main.Like{Name:"a", Picture:"a.png"}, main.Like{Name:"b", Picture:"b.png"}}}
英文:

Maybe that's just because the database driver you use does not support the data type jsonb.

I have tested the package github.com/jackc/pgx/v5, and it can decode the jsonb field correctly. See the example below:

package main

import (
	"context"
	"log"

	"github.com/jackc/pgx/v5"
)

func main() {
	// remember to modify the connString below.
	connString := "postgres://username:password@localhost:5432/database_name"
	conn, err := pgx.Connect(context.Background(), connString)
	if err != nil {
		log.Fatalf("Unable to connect to database: %v\n", err)
	}
	defer conn.Close(context.Background())

	type Like struct {
		Name    string
		Picture string
	}

	type Post struct {
		Id    string
		Likes []Like
	}

	var post Post
	err = conn.QueryRow(context.Background(), `select 'the_id', '[{"name":"a","picture":"a.png"},{"name":"b","picture":"b.png"}]'::jsonb`).Scan(&post.Id, &post.Likes)
	if err != nil {
		log.Fatalf("QueryRow failed: %v\n", err)
	}

	log.Printf("%#v", post)
}

Output:

main.Post{Id:"the_id", Likes:[]main.Like{main.Like{Name:"a", Picture:"a.png"}, main.Like{Name:"b", Picture:"b.png"}}}

huangapple
  • 本文由 发表于 2023年4月6日 07:36:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75944731.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定