
本文详解 Go 中解析 JSON 时因字段被意外字符串化(double-encoding)导致 []Spec 为空的问题,揭示根本原因并提供零字符串替换的安全解法。
本文详解 go 中解析 json 时因字段被意外字符串化(double-encoding)导致 `[]spec` 为空的问题,揭示根本原因并提供零字符串替换的安全解法。
在 Go 中使用 json.Unmarshal 解析结构化数据时,若目标 JSON 字段实际是被 JSON 编码过的字符串(即“双重编码”),直接按原始结构体解析必然失败——Specs 字段会静默为空切片,而非报错。您遇到的正是典型场景:
c := `{"product":"car","spec":["{\"name\":\"bla_bla\",\"info\":{\"color\":\"black\",\"year\":\"1991\"}}"]}`此处 "spec" 的值是一个 字符串数组,每个元素是 JSON 格式的字符串(如 "{\"name\":\"bla_bla\",...}"),而非原生 JSON 对象数组。因此,[]Spec 无法直接反序列化,因为 Go 尝试将字符串 "..." 解析为 Spec 结构体,类型不匹配。
✅ 正确解法:用 json.RawMessage 延迟解析
避免字符串替换、正则或 strings.Replacer 等易出错的预处理方式。Go 标准库提供了优雅方案:json.RawMessage —— 它将未解析的 JSON 字节流暂存为字节切片,待后续按需解码:
type Products struct {
Product string `json:"product"`
Specs []json.RawMessage `json:"spec"` // 关键:先存为 RawMessage
}
type Spec struct {
Name string `json:"name"`
Info struct {
Color string `json:"color"`
Year int `json:"year"`
} `json:"info"`
}
// 使用示例
func parseDoubleEncodedJSON() {
c := `{"product":"car","spec":["{\"name\":\"bla_bla\",\"info\":{\"color\":\"black\",\"year\":\"1991\"}}"]}`
var p Products
if err := json.Unmarshal([]byte(c), &p); err != nil {
log.Fatal("Unmarshal top-level:", err)
}
// 遍历 RawMessage 并逐个解码为 Spec
for i, raw := range p.Specs {
var spec Spec
if err := json.Unmarshal(raw, &spec); err != nil {
log.Printf("Failed to unmarshal spec[%d]: %v", i, err)
continue
}
fmt.Printf("Spec[%d].Name: %s\n", i, spec.Name) // 输出: Spec[0].Name: bla_bla
}
}⚠️ 注意事项与最佳实践
- 永远校验 json.Unmarshal 错误:您已检查错误,这是关键习惯;但注意 []json.RawMessage 解析失败时不会影响顶层解码,务必对每个子项单独 err != nil 判断。
- 字段类型一致性:示例中 year 在原始 JSON 中是字符串 "1991",而 Spec.Info.Year 定义为 int,json.Unmarshal 会自动转换(只要字符串可转为数字)。若存在非法字符串(如 "unknown"),将返回错误。
- 性能考量:json.RawMessage 零拷贝存储原始字节,比字符串拼接+strings.Replace 更高效且线程安全。
-
上游修复建议:根本解决应推动数据提供方(如 JS 后端)修正序列化逻辑——确保 spec 是真正的 JSON 数组,而非字符串数组。例如在 JavaScript 中应写:
// ✅ 正确:直接传对象数组 { product: "car", spec: [{ name: "bla_bla", info: { color: "black", year: 1991 } }] } // ❌ 错误:字符串化了对象 { product: "car", spec: [JSON.stringify({ name: "bla_bla", info: { color: "black", year: 1991 } })] }
通过 json.RawMessage,您既能稳健处理现实世界中不规范的 JSON 输入,又保持代码简洁、可维护和类型安全——这才是 Go “explicit is better than implicit” 哲学的体现。


















