This article will demonstrate how to store a plain string slice and without creating another OneToMany table by using gorm.
Problem
Sometimes, There’s no need to do a table normalization on a certain column and you want to store a []string in the database. The stored slice will join as a string and separate each element by a comma.
type Item struct {
gorm.Model
Name string
Owner []string
}func main() {
db := CreateMySQLGormConnection() item := model.Item{
Name: "Book",
Owner: []string{"Mary", "Jane"},
} db.Create(&item)
}
For example, you declared a model called Item, and its Owner property stores a []string.
2022/10/30 17:29:46 /Users/raylin/tutorial/gorm-custom-datatype/main.go:21 Error 1241: Operand should contain 1 column(s)
[6.346ms] [rows:0] INSERT INTO `items` (`created_at`,`updated_at`,`deleted_at`,`name`,`owner`) VALUES ('2022-10-30 17:29:46.787','2022-10-30 17:29:46.787',NULL,'Book',('Mary','Jane'))
Unfortunately, without additional settings. Gorm will return an error when writing new data in the database.
Solution
Gorm has a feature called Customize Data Type. This feature can be used to solve this kind of problem. The official document stores JSON in the database. Check if you’re interested.
- Step 1: Create a custom datatype to replace []string.
Be aware of there has a struct tag after the Owner. It’s important. Gorm doesn’t know which database datatype to store without specifying the database type in the struct tag. In this case, we store the slice to string(VARCHAR).
type Owner []stringtype Item struct {
gorm.Model
Name string
Owner Owner `gorm:"type:VARCHAR(255)"` // Use custom datatype
}
- Step 2: Implement driver.Valuer and sql.Scanner
func (o *Owner) Scan(src any) error {
bytes, ok := src.([]byte)
if !ok {
return errors.New("src value cannot cast to []byte")
} *o = strings.Split(string(bytes), ",") return nil
}func (o Owner) Value() (driver.Value, error) {
if len(o) == 0 {
return nil, nil
} return strings.Join(o, ","), nil
}
- Step 3: Try again
Now the value will be stored in the format which separates each element by a comma. When you query data, the Owner will automatically convert to []string.
func main() {
db := CreateMySQLGormConnection() // Create
item := model.Item{
Name: "Book",
Owner: []string{"Mary", "Jane"},
} db.Create(&item) // Query
queryItem := model.Item{} db.Where("id = ?", item.ID).First(&queryItem) marshaled, _ := json.MarshalIndent(queryItem, "", "\t")
fmt.Println(string(marshaled))
}