如何处理可为空的Postgres JSONB数据并将其解析为JSON

3

数据库记录

---------------------------------------------------------
| id | test_json                                        |
---------------------------------------------------------
| 1  | NULL                                             |
---------------------------------------------------------
| 2  | { "firstName": "Hello", "lastName": "World" }    |
---------------------------------------------------------

我在Postgres中有一个JSONB列,它可以为NULL。我想在Golang中读取这些记录,并将其发送给客户端。

在SQL扫描时,我遇到了以下错误:

"

sql: Scan error on column index 2, name "test_json": unsupported Scan, storing driver.Value type []uint8 into type *models.TestJSONNullable
exit status 1

我正在使用Echo Web服务器。
package models

import (
    "fmt"
    "github.com/lib/pq"
    "encoding/json"
)

type TestJson struct {
    First_name *string `json:"firstName"`
    Last_name *string `json:"lastName"`
}

type TestJSONNullable struct {
  Valid bool
}

func (i *TestJSONNullable) UnmarshalJSON(data []byte) error {
  if string(data) == "null" {
    i.Valid = false
    return nil
  }

  // The key isn't set to null
  var temp *TestJson
  if err := json.Unmarshal(data, &temp); err != nil {
    return err
  }
  i.Valid = true
  return nil
}

type Test01 struct {
    Id string `json:"id"`
    Test_json *TestJSONNullable `json:"testJson"`
}


func (db *DB) TestRecords () ([]*Test01, error)  {
    rows, err := db.Query("SELECT id, test_json FROM table_1 where success = true")

    if err != nil {
        log.Fatal(err)
        return nil, err
    }

    defer rows.Close()

    recs := []*Test01{}

    for rows.Next() {
        r := new(Test01)

        err := rows.Scan(&r.Id, &r.Test_json)

        if err != nil {
            log.Fatal(err)
            return nil, err
        }

        recs = append(recs, r)
    }

    if err = rows.Err(); err != nil {
        log.Fatal(err)
        return nil, err
    }

    return recs, nil
}
3个回答

2
这里有另一种解决方案:您可以为原始JSON数据实现一个可为空类型,类似于sql.NullString,并将其用作扫描目标。在此场景中,您将首先检查该值是否为null,然后仅在其不为null时进行解组。例如,来自github.com/soroushj/sqltNullRawMessage类型是一个可为空json.RawMessage,可用于此目的。

以下是示例:

package main

import (
    "database/sql"
    "log"

    _ "github.com/lib/pq"
    "github.com/soroushj/sqlt"
)

func main() {
    db, err := sql.Open("postgres", "dbname=dbname user=user password=password sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    row := db.QueryRow(`SELECT test_json FROM my_table WHERE id = $1`, 1)
    testJSON := sqlt.NullRawMessage{}
    err = row.Scan(&testJSON)
    if err != nil {
        log.Fatal(err)
    }
    if testJSON.Valid {
        // test_json is not null
        // Unmarshal testJSON.RawMessage
    } else {
        // test_json is null
    }
}

这个东西让我在浪费了6个小时之后得救了。谢谢。 - sofs1

0

经过一些研究,我找到了解决方案。

type TestJSONMap map[string]interface{}

func (t TestJSONMap) Value() (driver.Value, error) {
    j, err := json.Marshal(t)
    return j, err
}

func (p *TestJSONMap) Scan(val interface{}) error {
    value, ok := val.([]byte)
    if !ok {
        return errors.New("Type assertion .([]byte) failed.")
    }

    var i interface{}
    err := json.Unmarshal(value, &i)
    if err != nil {
        return err
    }

    *p, ok = i.(map[string]interface{})
    if !ok {
        return errors.New("Type assertion .(map[string]interface{}) failed.")
    }

    return nil
}

type Test01 struct {
    Id string `json:"id"`
    Test_json *TestJSONMap `json:"testJson"`
}

https://coussej.github.io/2016/02/16/Handling-JSONB-in-Go-Structs/获得了帮助


答案本身的信息并不是很有用。加上解决方案的简短描述会更好。提及接口将是一个很大的改进。 - Charlie Tumahai

-1

不必使用结构体来存储值,您可以使用映射表。

type TestJson struct {
    First_name *string `json:"firstName"`
    Last_name *string `json:"lastName"`
}

你可以将接口用作

var TestJson interface{}
err := json.Unmarshal(b, &TestJson)

另一方面,您也可以使用动态创建结构体的方法。就像这样

m := map[string]interface{}{
    "key": "value",
}

与其拥有TestJSONNullable,保存数据时使用Switch case会更好。

switch v := TestJson.(type) {
case int:

case float64:

case string:

default:
    // i isn't one of the types above
}

请查看此链接以获取更多详情--> https://godoc.org/encoding/json#Unmarshal


1
这个问题是关于从数据库列中检索可能为NULL的JSON值,而不是关于如何解组JSON。此外,根据我的经验,您建议使用映射而不是结构体是错误的,如果可能/实用,始终最好使用结构体而不是映射。 - mkopriva
好的,@mkopriva谢谢您的意图。似乎没有与结构体和映射相关的规则,如果有的话,您能否提供更多信息?虽然如果您查看代码,使用Switch case和MAP是有道理的。 - arshpreet
没有硬性规定,这就是为什么我写了“根据我的经验”。但是为什么你需要一个规则呢?只要看看你的代码,就会清楚地发现它是次优的,因为你引入了映射,现在必须进行类型切换才能找到映射成员的类型。这怎么比拥有具体结构类型和具体类型字段更好呢? - mkopriva
尽管如果你仔细看代码,使用Map和Switch Case是有道理的。不是这样的。 - mkopriva
3
Go是一种静态类型语言,使用interface{}会破坏这个非常受欢迎的特性。尽管在某些情况下interface{}非常有用,但在大多数其他情况下,如果可以避免使用,就应该避免。 - mkopriva

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接