Golang中区分JSON字段null/未提供,限制仅Optional类型接受null
处理PATCH API中非Optional字段的Null值校验问题
问题背景
我有一个接受PATCH方法的API,用于更新用户指定字段,例如请求体{"age": 10}可修改用户年龄。
原User结构体定义如下:
type User struct { Name string Age int Schedule *Schedule } type Schedule struct { Time int Weekdays []int }
若要删除用户的schedule字段,请求体应为{"schedule": null},但原结构体无法区分字段是被设为null还是未提供,因此改用带Optional泛型的结构体:
type User struct { Name string Age int Schedule Optional[Schedule] } type Optional[T any] struct { Value *T Defined bool } func (t *Optional[T]) UnmarshalJSON(data []byte) error { t.Defined = true return json.Unmarshal(data, &t.Value) } func (t *Optional[T]) IsNullDefined() bool { return t.Defined && t.Value == nil } func (t *Optional[T]) HasValue() bool { return t.Defined && t.Value != nil }
现在出现新问题:{"age": null}这类请求会被忽略(Go默认将null转为对应类型零值),而{"schedule": null}会正常删除字段。需要实现拒绝所有非Optional类型字段被显式设为null的请求,触发类型错误。
解决方案
方案一:自定义非Null基础类型(推荐)
给需要校验的非Optional基础类型(如string、int)定义自定义类型,并实现UnmarshalJSON方法,直接拒绝null值:
- 定义NonNull类型及反序列化逻辑
import ( "encoding/json" "errors" ) // 非空字符串类型 type NonNullString string func (s *NonNullString) UnmarshalJSON(data []byte) error { if string(data) == "null" { return errors.New("field cannot be null") } var str string if err := json.Unmarshal(data, &str); err != nil { return err } *s = NonNullString(str) return nil } // 非空整数类型 type NonNullInt int func (i *NonNullInt) UnmarshalJSON(data []byte) error { if string(data) == "null" { return errors.New("field cannot be null") } var num int if err := json.Unmarshal(data, &num); err != nil { return err } *i = NonNullInt(num) return nil }
- 修改User结构体字段类型
type User struct { Name NonNullString `json:"name"` Age NonNullInt `json:"age"` Schedule Optional[Schedule] `json:"schedule"` }
当请求体中name或age字段为null时,反序列化会直接返回错误,API可据此返回400 Bad Request。
方案二:先校验JSON映射再反序列化
无需修改结构体类型,先将JSON反序列化为map[string]interface{},遍历检查非Optional字段是否存在且为null:
import ( "encoding/json" "fmt" ) func ValidateAndUnmarshalUser(rawJSON []byte, user *User) error { // 先将JSON解析为临时map var tempMap map[string]interface{} if err := json.Unmarshal(rawJSON, &tempMap); err != nil { return err } // 定义需要校验的非Optional字段列表 nonOptionalFields := map[string]bool{ "name": true, "age": true, } // 检查每个非Optional字段是否被设为null for field := range nonOptionalFields { if val, exists := tempMap[field]; exists && val == nil { return fmt.Errorf("field %s cannot be set to null", field) } } // 校验通过后,反序列化为目标User结构体 return json.Unmarshal(rawJSON, user) }
在API处理逻辑中调用ValidateAndUnmarshalUser方法,即可提前拦截非法的null请求。
注意:此方案需维护非Optional字段列表,若结构体字段变更需同步更新该列表,适合不想修改现有结构体类型的场景。
内容的提问来源于stack exchange,提问作者osflw
相关产品推荐
相关产品推荐

