4

I have a Go struct which contains a slice of strings which I'd like to save as a jsonB object in Postgres with GORM.

I've come accross a solution which requires to use the GORM specific type (postgres.Jsonb) which I'd like to avoid.

When I try to run the AutoMigrate with a slice in my model, it panics and won't start although when I wrap this slice in a struct (which I'm okay doing) it will run without error but won't create the column in postgres.

type User struct {
        gorm.Model
        Data []string `sql:"type:"jsonb"; json:"data"`
} //Panics

type User struct {
        gorm.Model
        Data struct {
            NestedData []string
        } `sql:"type:"jsonb"; json:"data"`
} //Doesn't crash but doesn't create my column

Has anyone been able to manipulate jsonb with GORM without using the postgres.Jsonb type in models ?

3 Answers 3

3

The simplest way to use JSONB in Gorm is to use pgtype.JSONB.

Gorm uses pgx as it driver, and pgx has package called pgtype, which has type named pgtype.JSONB.

If you have already install pgx as Gorm instructed, you don't need install any other package.

This method should be the best practice since it using underlying driver and no custom code is needed. It can also be used for any JSONB type beyond []string.

type User struct {
    gorm.Model
    Data pgtype.JSONB `gorm:"type:jsonb;default:'[]';not null"`
}

Get value from DB

u := User{}
db.find(&u)

var data []string

err := u.Data.AssignTo(&data)
if err != nil {
    t.Fatal(err)
}

Set value to DB

u := User{}

err := u.Data.Set([]string{"abc","def"})
if err != nil {
    return
}

db.Updates(&u)
Sign up to request clarification or add additional context in comments.

Comments

0

Maybe:

type DataJSONB []string

func (dj DataJSONB) Value() (driver.Value, error) {
    return json.Marshal(dj)
}

func (dj *DataJSONB) Scan(value interface{}) error {
    b, ok := value.([]byte)
    if !ok {
        return fmt.Errorf("[]byte assertion failed")
    }

    return json.Unmarshal(b, dj)
}

// Your bit
type User struct {
    gorm.Model
    Data DataJSONB `sql:"type:"jsonb"; json:"data"`
}

1 Comment

I get this error: struct field tag sql:"type:"jsonb";json:"studs" not compatible with reflect.StructTag.Get: key:"value" pairs not separated by spacesstructtag
-1

Define a new type:

type Data map[string]interface{}

And implement the Valuer and Scanner interfaces onto them, which allows the field to be converted to a value for the database, and scanned back into the field, respectively:

// Value converts into []byte
func (d Data) Value() (driver.Value, error) {
  j, err := json.Marshal(d)
  return j, err
}

// Scan puts the []byte back into Data
func (d *Data) Scan(src interface{}) error {
  source, ok := src.([]byte)
  if !ok {
    return errors.New("Type assertion .([]byte) failed.")
  }

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

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

  return nil
}

Then you can define your field in your model like this:

type User struct {
        gorm.Model
        Data Data `type: jsonb not null default '{}'::jsonb`
}

Using the underlying map[string]interface{} type is nice too, as you can Unmarshal/Marshal any JSON to/from it.

1 Comment

Does this still work? I have been trying this and I keep getting panic: invalid sql type SomeMapType (map) I have an associated question here: stackoverflow.com/questions/63289749/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.