← 中文资源

简体中文版本

Go — json:无法将 string 反序列化进结构体字段

通过读取报错中确切的字段与值类型,修复结构体标签或为联合类型编写自定义 UnmarshalJSON,解决 Go 的 json: cannot unmarshal 错误。

阅读英文原文

症状

  这个错误是一行写完的事故报告:json: cannot unmarshal string into Go struct field Project.tags of type []string——同时点名负载类型(string)、结构体字段路径(Project.tags)与声明的 Go 类型([]string)。下一步是调查真实负载的形状分布:jq 统计发现 812 个样本是数组、31 个是逗号字符串——生产者改过约定(或从来没有约定),两种形状都在发,只有少数派失败。这也是“部分 payload 报错、部分正常”的原因。

诊断思路

  先从报错读出字段路径与类型对,立即定位修复点;再统计该字段在样本中的全部实际形状,决定是改标签就够还是需要自定义 Unmarshaler;最后检查结构体标签拼写与缺失——拼错或漏写的 json 标签会把 Go 字段绑到错误的键上,在邻近字段制造类型失配。

根因

  encoding/json 要求 JSON 值类型与 Go 字段类型精确对应(除少数已知强制转换)。某字段以不同类型到达时,整个 Unmarshal 以字段专属消息失败。

修复与验证

  真实联合类型字段用自定义类型接管解析:为 StringList 实现 UnmarshalJSON,先试单值再试数组,分别处理两种形状;变体需要先读鉴别字段的负载,用 json.RawMessage 延迟解析,等 Kind 明确后再解构;无趣的情况用标签修正——对齐名称、生产者省略时配 omitempty、负载不携带的字段标 json:“-”。验证时把真实样本语料全部过一遍 Unmarshal,断言零错误且每种已观察的字段形状都得到正确值。把字段一律改成 interface{} 来消错会丢掉类型安全、把失败推迟到运行期属性访问——用有针对性的自定义类型。

代码示例

json: cannot unmarshal string into Go struct field
Project.tags of type []string
jq '.tags' samples/*.json | sort | uniq -c
# 812 ["go","json"]
# 31  "go,json"          <- the producer sometimes flattens to a comma string
type StringList []string

func (s *StringList) UnmarshalJSON(b []byte) error {
    var single string
    if err := json.Unmarshal(b, &single); err == nil {
        *s = strings.Split(single, ",")
        return nil
    }
    var many []string
    if err := json.Unmarshal(b, &many); err != nil {
        return fmt.Errorf("StringList: %w", err)
    }
    *s = many
    return nil
}

type Project struct {
    Tags StringList `json:"tags"`
}
type Event struct {
    Kind string          `json:"kind"`
    Data json.RawMessage `json:"data"` // parse later, once Kind is known
}