Go中如何为PostgreSQL自定义类型数组列实现Scan()方法
问题原因
你遇到的报错是因为PostgreSQL返回的自定义复合类型数组是符合PG格式的字节数组,Go标准库的sql驱动无法自动将其转换为你定义的[]Artist切片,需要为对应的类型实现database/sql.Scanner接口完成自定义解析。
实现步骤
1. 定义切片别名
Go不允许直接为无名切片类型[]Artist定义方法,所以需要先定义别名:
// 原有Artist定义保留 type Artist struct { ArtistName string `json:"artist_name"` ArtistId string `json:"foo_artist_id"` ArtistImageURL string `json:"artist_image_url"` ArtistRank int `json:"artist_rank"` } // 新增切片别名 type ArtistList []Artist
2. 修改TopArtists结构体字段类型
将Artists字段的类型从[]Artist替换为上面定义的ArtistList:
type TopArtists struct { Id int64 `json:"id" db:"id"` FooUserId string `json:"foo_user_id" db:"foo_user_id"` CreatedAt string `json:"created_at" db:"created_at"` TimeSpan string `json:"time_span" db:"time_span"` Artists ArtistList `json:"items" db:"items"` }
3. 为Artist实现Scan方法
解析PG返回的单个复合类型的字符串表示:
import ( "errors" "fmt" "strconv" "strings" "database/sql/driver" ) func (a *Artist) Scan(src interface{}) error { source, ok := src.([]byte) if !ok { return errors.New("Artist类型Scan源数据格式错误") } // 去除PG复合类型首尾的括号 rawStr := strings.TrimPrefix(string(source), "(") rawStr = strings.TrimSuffix(rawStr, ")") // 拆分字段,复杂场景可替换为专业的PG复合类型解析逻辑 fields := strings.Split(rawStr, ",") if len(fields) != 4 { return fmt.Errorf("Artist需要4个字段,实际得到%d个", len(fields)) } // 去除字段首尾的引号和空格 for i := range fields { fields[i] = strings.Trim(fields[i], `" `) } // 赋值 a.ArtistName = fields[0] a.ArtistId = fields[1] a.ArtistImageURL = fields[2] rank, err := strconv.Atoi(fields[3]) if err != nil { return err } a.ArtistRank = rank return nil }
4. 为ArtistList实现Scan方法
解析PG返回的数组格式:
func (al *ArtistList) Scan(src interface{}) error { source, ok := src.([]byte) if !ok { return errors.New("ArtistList类型Scan源数据格式错误") } rawStr := string(source) // 去除PG数组首尾的大括号 rawStr = strings.TrimPrefix(rawStr, "{") rawStr = strings.TrimSuffix(rawStr, "}") // 空数组处理 if rawStr == "" { *al = ArtistList{} return nil } // 拆分数组元素,避免元素内部的逗号干扰拆分 var elements []string var currentElem []rune inQuotes := false for _, c := range rawStr { switch c { case '"': inQuotes = !inQuotes case ',': if !inQuotes { elements = append(elements, string(currentElem)) currentElem = nil continue } } currentElem = append(currentElem, c) } elements = append(elements, string(currentElem)) // 逐个解析每个元素为Artist var list ArtistList for _, elem := range elements { elem = strings.Trim(elem, `"`) var artist Artist if err := artist.Scan([]byte(elem)); err != nil { return err } list = append(list, artist) } *al = list return nil }
可选:为ArtistList实现Value方法
如果需要插入该类型数据到数据库,补充Value方法:
func (al ArtistList) Value() (driver.Value, error) { var elemStrs []string for _, a := range al { val, err := a.Value() if err != nil { return nil, err } elemStrs = append(elemStrs, fmt.Sprintf(`"%s"`, val)) } return []byte(fmt.Sprintf("{%s}", strings.Join(elemStrs, ","))), nil }
注意事项
上面的实现为简化版本,适用于字段内容不含双引号、括号等特殊字符的场景。如果业务中存在特殊字符,需要完善转义字符的解析逻辑。如果使用pgx驱动,可以直接通过驱动自带的类型注册机制绑定PG自定义类型,不需要手动实现解析逻辑,稳定性更高。
内容的提问来源于stack exchange,提问作者PersonWhoLovesLearning
相关产品推荐
相关产品推荐

