You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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值:

  1. 定义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
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 12:12:17