Golang/Gin/GORM中类似Django序列化器的方案问询
Go中控制序列化/反序列化字段的最优方案
背景
我平时用Python/Django技术栈,习惯了用Serializer灵活控制请求体的字段处理和响应体的返回字段,但转到Golang/Gin/GORM后,没找到类似的工具。同时看到不少人不推荐用DTO(数据传输对象),所以想请教Go里有没有合适的序列化/反序列化库,或者靠谱的实践模式。
业务场景
/user/:id/update/接口:必须忽略用户传入的password字段;/user列表接口:响应里绝对不能包含password字段。
已尝试的方案
- 用
json:"-"标签:但因为有单独更新密码的接口,这个方案直接废掉了; - 写了个泛型函数
ToStruct:把指定字段设为零值,配合omitempty标签来隐藏字段,但不确定这是不是最优解。函数代码如下:
func ToStruct[T any](s T, flds []string) T { elem := reflect.ValueOf(s).Elem() for i:=0; i<elem.NumField(); i++ { if slices.Contains(flds, elem.Type().Field(i).Name) { f := elem.Field(i) if f.Kind() == reflect.Ptr { // 把字段设为nil f.Set(reflect.Zero(f.Type())) } else { // 把字段设为对应类型的零值 f.SetZero() } } } return s }
推荐解决方案
1. 用JSON标签+指针类型做条件序列化
Go的json标签结合指针类型和omitempty,能轻松实现不同场景的字段控制:
- 定义User结构体时,把
Password设为指针类型:
type User struct { ID uint `json:"id"` Username string `json:"username"` Password *string `json:"password,omitempty"` }
- 处理
/user列表接口时,直接把Password设为nil,序列化的时候就会自动忽略这个字段; - 处理
/user/:id/update/接口时,反序列化后如果Password是nil,就跳过更新密码的逻辑;单独更新密码的接口则正常接收字段值就行。
2. 用第三方序列化库做灵活过滤
有些第三方库支持直接指定序列化/反序列化时要包含或排除的字段,不用改结构体:
go-json:序列化时可以直接指定排除的字段:
// 序列化时排除password字段 data, err := json.MarshalWithOptions(user, json.Exclude("Password"))
goccy/go-json:不仅支持字段过滤,序列化性能也比标准库好。
3. 合理用DTO(别被网上的说法吓到)
虽然很多人吐槽DTO会重复代码,但在复杂业务场景下,它是最清晰、最不容易出错的方案:
- 给不同接口单独定义DTO结构体:
// 更新用户的请求DTO(不含password,自然忽略前端传入的值) type UpdateUserRequest struct { ID uint `json:"id"` Username string `json:"username"` } // 用户列表响应DTO(不含password) type UserListResponse struct { ID uint `json:"id"` Username string `json:"username"` } // 单独更新密码的DTO type UpdatePasswordRequest struct { ID uint `json:"id"` Password string `json:"password"` }
这种方式代码可读性拉满,后期维护也省心,业务逻辑复杂的时候特别好用。
4. 优化你的泛型函数
如果不想用DTO或第三方库,可以优化你写的泛型函数,比如改成按JSON标签名来排除字段,适配更灵活:
import "strings" func ExcludeFields[T any](s T, excludeJSONTags []string) T { elem := reflect.ValueOf(s).Elem() typ := elem.Type() for i := 0; i < elem.NumField(); i++ { field := typ.Field(i) // 解析JSON标签的主名称 jsonTag := strings.Split(field.Tag.Get("json"), ",")[0] if slices.Contains(excludeJSONTags, jsonTag) { f := elem.Field(i) if f.Kind() == reflect.Ptr { f.Set(reflect.Zero(f.Type())) } else { f.SetZero() } } } return s }
调用的时候直接传JSON标签名就行:
// 排除password字段 user = ExcludeFields(user, []string{"password"})
内容的提问来源于stack exchange,提问作者fady malak
相关产品推荐
相关产品推荐

