Home > Software design >  How to scan jsonb objects from Postgresql to golang structs
How to scan jsonb objects from Postgresql to golang structs

Time:09-06

Firstly, I've tried solutions from past stackoverflow answers whose questions were related to mine, and nothing has worked, that's why i am asking it as a separate question.

I have two structs in golang

type otherPayments struct {
    DebitTo     int    `json:"debit_To" binding:"required"`
    CreditFrom  int    `json:"credit_from" binding:"required"`
    OverallType string `json:"overall_type" binding:"required"`
}

type advanceAndRoomPayment struct {
    PmID        int    `json:"pm_id" binding:"required"`        //Payment method id
    PmName      string `json:"pm_name" binding:"required"`      //Payment method name
    DebitTo     int    `json:"debit_To" binding:"required"`     //The ledger to debit from
    CreditFrom  int    `json:"credit_from" binding:"required"`  //The ledger to credit from
    OverallType string `json:"overall_type" binding:"required"` //Overall transaction type
}

And i have 5 SQL columns within my booking_settings postgresql table

  • initial column, type = otherPayments, JSONB
  • cancellation, type = otherPayments, JSONB
  • updation, type = otherPayments, JSONB
  • advance_payment type = advanceAndRoomPayment, JSONB []
  • room_payment, type = advanceAndRoomPayment, JSONB []

The SELECT query is as follows

SELECT initial, cancellation, updation advance_payment, room_payment FROM booking_settings WHERE hotel_id = $1

The sql package i am using is https://jmoiron.github.io/sqlx/

I am trying to scan above columns into their appropriate struct vars, so far i could only manage to scan initial, cancellation and updation but not the JSONB [] advance_payment and room_payment

Any help is really appreciated, thank you

CodePudding user response:

Just in case you're not aware, jsonb[] is a PostgreSQL Array type whose element type is jsonb. It is NOT a "json array" type.

If you want to store JSON data in a column you should use the json/jsonb types regardless of whether you expect that data to contain a scalar, an object, or an array JSON value.

So, unless you chose the PostgreSQL Array type with some specific use-case in mind, it may be better if you just change the column's type from jsonb[] to jsonb.


If you can't, or don't want to, change the column type, then you can still convert the PostgreSQL array into a JSON array within the SELECT query, and then, in your custom Go sql.Scanner implementation, use json.Unmarshal to decode the db data.

SELECT to_jsonb(advance_payment) FROM booking_settings WHERE hotel_id = $1
-- or
SELECT array_to_json(advance_payment)::jsonb FROM booking_settings WHERE hotel_id = $1
type advanceAndRoomPaymentList []advanceAndRoomPayment

func (ls *advanceAndRoomPaymentList) Scan(src any) error {
    var data []byte
    switch v := src.(type) {
    case string:
        data = []byte(v)
    case []byte:
        data = v
    }
    return json.Unmarshal(data, ls)
}

If you have many queries referencing the PostgreSQL array columns and you don't want to update each and every one to do the conversion, you can either parse the PostgreSQL Array yourself and then unmarshal the individual elements, or you can delegate that work to some 3rd party implementation.

Here's an untested example using pq.GenericArray:

// I haven't tested the following but I'd assume it ought to work,
// if not, then perhaps maybe small tweaks are needed here and there...
type advanceAndRoomPaymentList []advanceAndRoomPayment

func (ls *advanceAndRoomPaymentList) Scan(src any) error {
    return pq.GenericArray{ls}.Scan(src)
}

// implement Scanner for the element type of the slice
func (a *advanceAndRoomPayment) Scan(src any) error {
    var data []byte
    switch v := src.(type) {
    case string:
        data = []byte(v)
    case []byte:
        data = v
    }
    return json.Unmarshal(data, a)
}

If you want to do the parsing of the PostgreSQL Array yourself then you'll need to understand the syntax used to represent such arrays. You can find the relevant docs here:

The external text representation of an array value consists of items that are interpreted according to the I/O conversion rules for the array's element type, plus decoration that indicates the array structure. The decoration consists of curly braces ({ and }) around the array value plus delimiter characters between adjacent items. The delimiter character is usually a comma (,) but can be something else: it is determined by the typdelim setting for the array's element type. Among the standard data types provided in the PostgreSQL distribution, all use a comma, except for type box, which uses a semicolon (;).

So, for example, if you have pg-array that contains a json-object, a json-array, a json-string, and a json-bool and you select that, then the array representation that will be passed to the sql.Scanner implementation will look something like:

{"{\"foo\": \"bar\"}","[\"foo\", \"bar\"]","\"foo bar\"",true}
  • Related