本文对实际开发场景中面对高频的场景,总结出来的一些处理方案,希望能对业务开发的同学提供帮助!
结构体转换
实际开发中会面对一个相似的数据结构,由于引用不同的包,需要开发转换到对应的结构上,本质上这些数据结构是一致的,但是所在包不同所以不能直接赋值。
常规的方案大致分为下面几种:
- 直接转换 struct
这种适合结构完全一致的情况,参数名和类型都必须保持一致;适用场景相对较少,面对不同包的协议转换如果包含一个枚举就无效了
go
type aType int64
type A struct {
a aType
}
type bType int64
type B struct {
a bType
}
func Test(t *testing.T) {
a := A{a: 1}
b := B(a)
fmt.Println(a, b)// 如果把aType和bType直接当做in64就可以正常转换
}
2. 手撸代码
开发手动转换结构,适合字段比较少的结构,同时命名不会很相似,如果相似度较高存在写错的可能,面对复杂有嵌套数据结构效率低下。
3. 正反序列化转换
这种方案相对于第一种具备更强的兼容性,可以通过 tag 来实现不同类型的转换,但是面对不同协议生成的代码还是具有局限性,同时效率比较低下,序列化是比较消耗 cpu 的操作;
需要注意的是,官方的原生 json 库处理大数存在精度丢失的问题,我们这里采用 jsonx 默认支持大数
jsonx: code.byted.org/gopkg/jsonx
go
type aType int64
type A struct {
A aType `json:"a"`
}
type bType int64
type B struct {
A bType `json:"a"`
}
func Test(t *testing.T) {
aStr := jsonx.ToString(A{1})
b := &B{}
_ = jsonx.UnmarshalFromString(aStr, &b)
fmt.Println(aStr, b)
}
最佳实现
这里的最佳实现其实要区分场景来考虑:
- 面对高并发或是简单结构的场景,需要减少资源消耗,可以采用【手撸代码】的方式实现
- 面对并发比较低的场景,通过【正反序列化】是比较好的方案,使用起来更简单
数据库中存储json结构体
表中有extra字段,存储的是扩展信息,比如执行时间,通常的结构声明是这样的:
go
type BaseInfo struct {
ID int64 `json:"id" gorm:"column:id"`
Extra string `json:"extra" gorm:"column:extra"`
}
意味着查询出来结构后还需要进行 unmarshal 操作,且写入数据的时候也要进行 marshal,开发者在修改数据的时候需要额外考虑其他接口所使用的数据结构,用起来不方便。
最佳实践
gorm 是支持很多拓展特性的,通过实现Scan、Value的方法就可以省去在业务代码中序列化的操作,降低开发者的心智负担,优化后大致如下:
go
type BaseInfo struct {
ID int64 `json:"id" gorm:"column:id"`
Extra *ExtraInfo `json:"check_in_detail" gorm:"column:check_in_detail"`
}
type ExtraInfo struct {
Info1 `json:"info1"`
}
func (BaseInfo) TableName() string {
return "base_info"
}
// Value return json value, implement driver.Valuer interface
// 如果接受者是指针,那么就只能是指针来调用
// 如果接受者是值类型,则支持指针、值类型来调用
func (j ExtraInfo) Value() (driver.Value, error) {
return json.Marshal(j)
}
// Scan scan value into Jsonb, implements sql.Scanner interface
// 接受者要使用指针类型,这才才能实际赋值
func (j *ExtraInfo) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
}
result := ExtraInfo{}
var err error
if len(bytes) > 0 {
err = json.Unmarshal(bytes, &result)
}
*j = result
return err
}