Gorm: Save nested Structs as Postgres JSONB

Created on 15 Oct 2019  ·  5Comments  ·  Source: go-gorm/gorm

What version of Go are you using (go version)?

go version go1.13 darwin/amd64

Which database and its version are you using?

PostgreSQL 11.5

transaction.go

type FTransaction struct {
    TxID        string                `json:"txId" gorm:"primary_key;unique;not null"`
    Inputs      []*InputOutput `json:"inputs"`
    Outputs     []*InputOutput `json:"outputs"`
}

type InputOutput struct {
    Address     string `json:"address"`
    Amount      int64  `json:"amount"`
}

Database

CREATE TABLE f_transactions(
    tx_id VARCHAR(64) UNIQUE NOT NULL,
    inputs JSONB,
    outputs JSONB,
    CONSTRAINT f_transactions_tx_id_key PRIMARY KEY(tx_id)
);

db.go

func (c *Context) CreateFTransaction(ftx *model.FTransaction) error {

    if err := c.db.FirstOrCreate(&ftx).Error; err != nil {
        return err
    }
    return nil

}

What works:
– Database entry created

What doesn't work:
inputs & outputs fields are saved as NULL

Question:
I want to store inputs & outputs of transaction as JSONB fields into Postgres DB.
Is it possible with GORM?

I tried gorm:"json", sql:"json", sql:"json" – no success.

Will be appreciated for help.
Thank you!

Most helpful comment

You can implement the interface driver.Valuer to convert the structure to the value actually stored in the database, and implement the interfacedriver.Scanner to read the database value into the structure.
For gorm, implementing driver.Scanner is the only way to mark fields of type struct as normal, of course, except for the built-in typetime.Time.

type InputOutputs []*InputOutput

func (j InputOutputs) Value() (driver.Value, error) {
    content, err := json.Marshal(j)
    if err != nil {
        return nil, err
    }
    return content, err
}

func (j *InputOutputs) Scan(value interface{}) error {
    if value == nil {
        *j = nil
        return nil
    }
    s, ok := value.([]byte)
    if !ok {
        return fmt.Errorf("Invalid Scan Source")
    }
    if err := json.Unmarshal(s, j); err != nil {
        return err
    }
    return nil
}

gorm related source code can be found here: ➡️

All 5 comments

me too~

You can implement the interface driver.Valuer to convert the structure to the value actually stored in the database, and implement the interfacedriver.Scanner to read the database value into the structure.
For gorm, implementing driver.Scanner is the only way to mark fields of type struct as normal, of course, except for the built-in typetime.Time.

type InputOutputs []*InputOutput

func (j InputOutputs) Value() (driver.Value, error) {
    content, err := json.Marshal(j)
    if err != nil {
        return nil, err
    }
    return content, err
}

func (j *InputOutputs) Scan(value interface{}) error {
    if value == nil {
        *j = nil
        return nil
    }
    s, ok := value.([]byte)
    if !ok {
        return fmt.Errorf("Invalid Scan Source")
    }
    if err := json.Unmarshal(s, j); err != nil {
        return err
    }
    return nil
}

gorm related source code can be found here: ➡️

@sswanv do you use any gorm tags with that?

@bumberboy no, no tags required for @sswanv's solution.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

leebrooks0 picture leebrooks0  ·  3Comments

alanyuen picture alanyuen  ·  3Comments

satb picture satb  ·  3Comments

hypertornado picture hypertornado  ·  3Comments

Quentin-M picture Quentin-M  ·  3Comments