当前位置:首页 > 文章列表 > Golang > Go问答 > Go JSON null、缺失字段和零值怎样区分

Go JSON null、缺失字段和零值怎样区分

来源:17golang原创 2026-10-31 19:11:51 0浏览 收藏

在 Go 里,JSON 的“字段缺失”、显式 null 和数字 0 不是同一件事;但如果直接解码到普通的 int 字段,三者很容易都表现成 0。解决办法取决于接口是否需要保留字段存在性:只关心 null 与非 null 时用指针,需要判断键是否出现时用 json.RawMessage,需要完整保留缺失、null、有效零值时用带状态的自定义类型。

核心区分规则
  • 新建的普通值结构体中,缺失字段保持零值;null 解码到普通值类型通常不改变原值;JSON 数字 0 则明确写入零值。
  • *int 可以把有效的 0 与 null 区分开,但缺失和 null 在全新结构体里都会是 nil。
  • 要表达三态,必须额外记录“键是否出现”,不能只看字段的最终值。

默认解码为什么会丢掉状态

先看最小结构体。下面的代码分别解码三个 JSON 对象,并打印普通 int 字段:

package main

import (
    "encoding/json"
    "fmt"
)

type Request struct {
    Count int `json:"count"`
}

func main() {
    inputs := []string{`{}`, `{"count":null}`, `{"count":0}`}
    for _, input := range inputs {
        var req Request
        // 每次都新建结构体,避免上一次请求留下字段状态。
        if err := json.Unmarshal([]byte(input), &req); err != nil {
            // 输入不是合法 JSON 时立即报告错误。
            panic(err)
        }
        fmt.Printf("%-18s => Count=%d\n", input, req.Count)
    }
}

在“全新结构体”的前提下,三次输出都可能是 Count=0。缺失键没有可写入的目标,字段保持 Go 零值;null 对普通数值字段没有效果;数字 0 才是一次明确的赋值。最终值相同,并不代表输入语义相同。

JSON 缺失、null 和 0 进入 Go int 字段后汇聚为零值的静态结构说明图
图1:静态结构说明图,展示 JSON 缺失字段、null 与 0 在普通 Go 值字段上的信息汇聚,不是运行截图。

先用 *int 区分有效零值和 null

如果业务只需要知道“客户端是否明确传入了一个数”,可以把字段改成指针。非 null 的 JSON 数字会分配目标值,即使数字是 0;显式 null 会把指针设为 nil。

type PointerRequest struct {
    Count *int `json:"count"`
}

func decodePointer(input string) (string, error) {
    var req PointerRequest
    // 全新结构体下,缺失键和 null 都从 nil 开始。
    if err := json.Unmarshal([]byte(input), &req); err != nil {
        return "", err
    }
    if req.Count == nil {
        // nil 可能表示缺失,也可能表示显式 null,当前类型无法再细分。
        return "nil(缺失或 null)", nil
    }
    // 指针非 nil 时,0 仍然是一个明确传入的有效值。
    return fmt.Sprintf("value=%d", *req.Count), nil
}

指针的优点是能表达“没有数值”和“数值为 0”。它的边界也很重要:在全新结构体里,字段缺失不会调用任何解码动作,显式 null 又会把指针设为 nil,所以二者仍然无法仅靠 *int 区分。

用 json.RawMessage 先判断键是否存在

要区分缺失与显式 null,可以先把对象解码成 map[string]json.RawMessage。map 的第二个返回值记录键是否出现,而 RawMessage 保留该字段的原始 JSON。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
)

func readCount(input string) (string, error) {
    var fields map[string]json.RawMessage
    // 先保留字段级原文,不急着把 null 转成 Go 零值。
    if err := json.Unmarshal([]byte(input), &fields); err != nil {
        return "", err
    }

    raw, exists := fields["count"]
    if !exists {
        // map 的第二个返回值表示 JSON 键根本没有出现。
        return "missing", nil
    }
    if bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
        // 键存在但值为 null,这与 missing 是两种输入。
        return "null", nil
    }

    var value int
    // 只有确认不是 null 后才按业务类型解码具体数值。
    if err := json.Unmarshal(raw, &value); err != nil {
        return "", fmt.Errorf("count 类型错误: %w", err)
    }
    return fmt.Sprintf("value=%d", value), nil
}

这种方式适合少量可选字段或需要根据原文决定后续类型的接口。缺点是调用方需要自己维护字段解析逻辑;如果每个请求都有很多三态字段,继续堆叠 map 判断会让业务代码变得重复。

用包装类型完整保留缺失、null 和有效零值

更稳定的接口模型是把三种状态直接放到一个类型里:Set 表示键出现过,Valid 表示出现的值不是 null,Value 才保存具体数字。注意,缺失字段不会调用字段类型的 UnmarshalJSON;因此新建结构体时 Set=false 就自然代表缺失。

type OptionalInt struct {
    Value int
    Set   bool
    Valid bool
}

func (o *OptionalInt) UnmarshalJSON(data []byte) error {
    o.Set = true
    data = bytes.TrimSpace(data)
    if bytes.Equal(data, []byte("null")) {
        // null 是已出现但没有有效数值,不把它误判成缺失。
        o.Value = 0
        o.Valid = false
        return nil
    }

    var value int
    // 具体类型仍交给 encoding/json,保留其类型错误语义。
    if err := json.Unmarshal(data, &value); err != nil {
        return err
    }
    o.Value = value
    o.Valid = true
    return nil
}

type StrictRequest struct {
    Count OptionalInt `json:"count"`
}

使用这个类型时可以得到明确的三态结果:

JSON 输入SetValidValue语义
{}falsefalse0缺失
{"count":null}truefalse0显式 null
{"count":0}truetrue0有效零值
通过键存在性与 OptionalInt 的 Set、Valid、Value 恢复 JSON 三态语义的静态流程说明图
图2:静态流程说明图,展示键存在性、null 判断与有效零值的分流关系,不是运行截图。

复用结构体、默认值和 omitempty 的边界

如果一个结构体会被重复解码,必须特别小心“缺失字段不会覆盖旧值”。例如第一次请求把 Count 设为有效值,第二次请求缺失 count,普通解码并不会自动把它清零;自定义包装类型的 Set 和 Valid 也不会因为字段缺失而自动复位。最简单的做法是每次请求使用新结构体,或在复用前显式重置整个对象。

omitempty 只影响 Go 值编码为 JSON 时是否省略字段,不是反序列化阶段的存在性检测器。需要向外输出三态时,通常还应明确约定:缺失是否省略、null 是否保留、有效零值是否必须输出,而不是把这些决定交给默认标签行为。

按需求选择实现方式

需求建议类型能区分什么
只要普通默认值int、string 等值类型不保留输入存在性
区分有效 0 与 null*int、*string非 null 与 null;缺失和 null 仍可能相同
少量字段需要检查原文map[string]json.RawMessage键是否出现、是否为 null、原始值
大量接口字段需要稳定三态自定义包装类型缺失、null、有效零值全部保留

判断顺序可以固定成一句话:先问接口是否关心“键有没有出现”,再问是否要区分 null,最后才选择普通值、指针、RawMessage 或包装类型。这样就不会把“最后读到的 Go 零值”误当成完整的输入语义。

常见问题

普通 int 字段能判断客户端有没有传 0 吗?

不能。缺失字段和显式 0 都可能得到 0;需要把字段改成指针或保存原始字段。

把 int 改成 *int 就能区分缺失和 null 吗?

不能保证。全新结构体中两者都可能是 nil;要区分它们,需要键存在性记录或三态包装类型。

为什么自定义 UnmarshalJSON 没有处理缺失字段?

因为缺失字段没有进入解码流程,字段类型的方法不会被调用。把 Set 初始为 false,正好可以利用这一点表示缺失。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go HTTP Server 流式下载如何判断客户端断开Go HTTP Server 流式下载如何判断客户端断开
上一篇
Go HTTP Server 流式下载如何判断客户端断开
Go json.Decoder.Buffered 接回预读字节的流式处理方案
下一篇
Go json.Decoder.Buffered 接回预读字节的流式处理方案
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    814次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    805次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    895次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    868次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    691次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码