登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go jsonnull 怎么处理JSON 数组

来源:17golang原创

时间:2026-09-13 10:12:01 266浏览 收藏

Go 里处理 JSON 数组时,最容易踩坑的是把 null[] 当成同一种“没有数据”。用标准库 encoding/json 解码到切片时,null 会得到 nil 切片,空数组会得到非 nil 但长度为 0 的切片;重新编码时,前者默认输出 null,后者输出 []。如果接口契约要求“数组字段永远是数组”,就要在输出边界主动归一化。

要点速览
  • null[] 都能解码成功,但 nil 判断结果不同。
  • 普通结构体切片无法直接区分“字段缺失”和“字段为 null”,需要 RawMessage 或指针。
  • omitempty 会同时忽略 nil 切片和零长度切片,不能拿它表达三态业务语义。

先把 Go jsonnull 和空数组的差异看清

假设接口返回一个名为 items 的数组。最小实验不需要引入第三方包,直接让两个变量分别接收 null[]

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    var fromNull []string
    var fromEmpty []string

    // null 表示没有切片值,[] 表示已经存在但没有元素的切片。
    _ = json.Unmarshal([]byte(`null`), &fromNull)
    _ = json.Unmarshal([]byte(`[]`), &fromEmpty)

    // 用 nil 和 len 同时观察两种状态,避免只看元素数量。
    fmt.Println(fromNull == nil, len(fromNull))   // true 0
    fmt.Println(fromEmpty == nil, len(fromEmpty)) // false 0

    // 重新编码时,两种状态会保留到 JSON 表示中。
    nullJSON, _ := json.Marshal(fromNull)
    emptyJSON, _ := json.Marshal(fromEmpty)
    fmt.Println(string(nullJSON))  // null
    fmt.Println(string(emptyJSON)) // []
}

这里的关键不是长度,而是 fromNull == nil。两者都能安全地进行 lenrange 和追加,但它们表达的协议语义不同。标准库文档也明确说明,JSON null 解码到切片时会把 Go 值设为 nil;空数组则替换为一个新的空切片。

Go JSON null 与空数组解码关系的静态技术框图
图1:Go jsonnull 读取关系示意图,区分 JSON 输入、解码器和两种切片状态;这是结构示意图,不是运行截图。

字段缺失、null 和 [] 为什么还不一样

把切片直接放进结构体时,字段缺失和显式 null 最后都可能留下 nil,因此普通写法只能解决“数组还是空数组”的一部分问题:

type Payload struct {
    Items []string `json:"items"`
}

func decodePayload(data []byte) (Payload, error) {
    var payload Payload

    // 解码结果需要返回给调用方,不能只依赖后续 len 判断。
    if err := json.Unmarshal(data, &payload); err != nil {
        return Payload{}, err
    }
    return payload, nil
}

如果业务只关心“有没有可迭代的元素”,这样足够;如果要区分字段没传、传了 null、传了空数组,则可以先保留原始字段:

type RawPayload struct {
    Items json.RawMessage `json:"items"`
}

func inspectItems(data []byte) (string, error) {
    var payload RawPayload

    // RawMessage 让字段存在性先被保留下来,再决定如何转换。
    if err := json.Unmarshal(data, &payload); err != nil {
        return "invalid-json", err
    }
    if payload.Items == nil {
        return "missing", nil
    }
    if string(payload.Items) == "null" {
        return "null", nil
    }

    var items []string
    if err := json.Unmarshal(payload.Items, &items); err != nil {
        return "invalid-items", err
    }
    if items == nil {
        return "null", nil
    }
    return "array", nil
}

生产代码里不建议只用字符串比较来处理复杂 JSON;示例的目的只是突出三态判断。正式转换时可以把 RawMessage 交给第二次 json.Unmarshal,并把类型错误返回给上层,而不是静默把对象或字符串当数组。

回写接口时,先决定要输出 null 还是 []

很多“Go jsonnull”问题其实发生在响应编码阶段:数据库查询没有结果时切片保持 niljson.Marshal 就会返回 null。若前端协议约定数组字段固定为数组,可以在组装响应时归一化:

type Response struct {
    Items []string `json:"items"`
}

func normalizeItems(items []string) Response {
    // 对外协议要求数组时,把 nil 转成非 nil 的空切片。
    if items == nil {
        items = make([]string, 0)
    }
    return Response{Items: items}
}

反过来,如果 null 有“尚未计算”“不适用”之类的业务含义,就不要无条件转成 []。这时应在领域模型中保留一个明确的存在状态,或使用指针/自定义类型,让响应层能表达这个含义。

Go 切片编码为 JSON null 或空数组的静态关系图
图2:Go jsonnull 输出边界示意图,展示 nil 切片、空切片、Marshal 和接口字段之间的静态关系;这是结果示意图,不是运行证据。

omitempty 不是三态字段开关

json:"items,omitempty" 会在切片为 nil 或长度为 0 时省略字段。它适合“空集合不需要传输”的接口,但不适合同时表达“缺失、null、空数组”三种状态。

可以按下面的规则做选择:

输入或内部状态默认解码结果默认编码结果适合场景
nullnil 切片null未提供、未知或不适用
[]非 nil 空切片[]明确表示集合为空
字段缺失通常保持零值按标签决定兼容旧客户端或可选字段

最后检查四件事:解码后是否需要 nil 判断;接口是否允许 null;写响应前是否统一初始化空切片;omitempty 是否会误删字段。只要这四个问题先定下来,Go 对 JSON 数组的处理就不会被“长度都是 0”带偏。

相关问题

Go 的 nil 切片能直接 range 吗?

可以。对 nil 切片使用 rangelenappend 都是安全的,但编码结果仍可能与空切片不同。

为什么接口返回的数组偶尔变成 null?

常见原因是响应模型中的切片从未初始化,仍是 nil;在编码前把它归一化为空切片,或按协议保留 null 的业务含义即可。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>