
本文介绍在 go 中处理 api 返回的“json 中嵌套转义 json 字符串”这一常见场景:先标准反序列化外层结构,再对字段中已转义的 json 字符串二次解析为结构体,确保类型安全与可维护性。
本文介绍在 go 中处理 api 返回的“json 中嵌套转义 json 字符串”这一常见场景:先标准反序列化外层结构,再对字段中已转义的 json 字符串二次解析为结构体,确保类型安全与可维护性。
在实际开发中,某些 REST API 为兼容性或历史原因,会将子对象以 JSON 字符串形式(即双重编码)嵌入外层 JSON 的某个字段中,例如 "stats": "{\"id\":545,\"lastUpdated\":\"2015-01-09T19:16:04.535Z\",...}"。此时若直接将该字段映射为结构体,json.Unmarshal 会因类型不匹配而失败;若仅定义为 string,又丧失结构化访问能力。最优解是分两步解析:先解出外层结构,再对转义字段单独调用 json.Unmarshal。
✅ 推荐实现方式:两阶段解码
首先定义清晰的结构体。注意修正原始示例中的小错误(如字段名首字母需大写才能导出、name 和 stats 字段应为导出字段):
type RunStatus struct {
Duration string `json:"duration"`
StartTime time.Time `json:"startTime"`
Total int `json:"total"`
Content []Item `json:"content"`
}
type Item struct {
DateCreated time.Time `json:"dateCreated"`
LastUpdated time.Time `json:"lastUpdated"`
Name string `json:"name"` // 修正:导出字段,首字母大写
StatsStr string `json:"stats"` // 暂存原始转义字符串
}
// Stats 是内嵌 JSON 对应的目标结构
type Stats struct {
ID int `json:"id"`
LastUpdated time.Time `json:"lastUpdated"`
All struct {
Runs struct {
Count int `json:"count"`
} `json:"runs"`
} `json:"all"`
}
然后执行两阶段解析:
func ParseRunStatus(data []byte) (*RunStatus, error) {
var rs RunStatus
if err := json.Unmarshal(data, &rs); err != nil {
return nil, fmt.Errorf("failed to unmarshal outer JSON: %w", err)
}
// 遍历 Content,对每个 Item.StatsStr 二次解析
for i := range rs.Content {
item := &rs.Content[i]
if item.StatsStr == "" {
continue
}
var stats Stats
if err := json.Unmarshal([]byte(item.StatsStr), &stats); err != nil {
return nil, fmt.Errorf("failed to unmarshal stats (item %d): %w", i, err)
}
// 可选择扩展字段(如添加 Stats 字段),或通过方法封装访问逻辑
item.Stats = stats // 假设 Item 结构体已添加 Stats Stats 字段
}
return &rs, nil
}
? 进阶建议:如需频繁使用,可为 Item 实现自定义 UnmarshalJSON 方法,将二次解析逻辑封装,使调用方无感知:
func (i *Item) UnmarshalJSON(data []byte) error { type Alias Item // 防止递归调用 aux := &struct { StatsStr string `json:"stats"` *Alias }{ Alias: (*Alias)(i), } if err := json.Unmarshal(data, aux); err != nil { return err } if aux.StatsStr != "" { return json.Unmarshal([]byte(aux.StatsStr), &i.Stats) } return nil }
⚠️ 注意事项
- 空值与错误处理:务必检查 StatsStr 是否为空或无效 JSON,避免 json.Unmarshal panic;
- 性能考量:若数据量极大,重复 []byte() 转换和多次 Unmarshal 会有开销,可考虑预分配缓冲区或使用 json.RawMessage 延迟解析;
- 安全性:来自不可信源的嵌套 JSON 字符串需警惕恶意构造(如超深嵌套、超大 payload),建议设置 json.Decoder.DisallowUnknownFields() 及限流校验;
- 时间格式一致性:确保内嵌 JSON 中的时间字符串格式与 time.Time 解析器兼容(如 RFC3339),必要时注册自定义 UnmarshalJSON。
通过这种清晰、可控的两阶段解析策略,你既能保持代码的可读性与健壮性,又能精准还原嵌套结构的语义,是处理此类“JSON-in-JSON”场景最实用、最符合 Go 语言哲学的方案。











