如何在 Go 中序列化混合类型 JSON 数组

11次阅读

如何在 Go 中序列化混合类型 JSON 数组

本文介绍如何在 go 中将结构体切片序列化为紧凑的 json 数组(如 `[“ooid1”, 2.0, “söme text”]`),绕过 go 类型系统对同构切片的限制,通过实现 `json.marshaler` 和 `json.unmarshaler` 接口达成与 python `json.dumps()` 类似的灵活序列化效果。

在 Go 中,原生切片(如 []String 或 []Interface{})无法直接表示「每个元素是固定长度、但内部类型混杂」的数组结构(例如 [string, float64, string])。而目标 JSON 输出要求 results 是一个二维数组,每行均为三元素异构序列,而非对象字典(即不能是 {“ooid”: “…”, “score”: …, “text”: …})。此时,标准结构体导出字段的方式会生成键值对对象,不符合需求。

解决方案是自定义序列化行为:让 Row 类型实现 json.Marshaler 接口,手动控制其 JSON 编码过程。核心思路是——在 MarshalJSON() 方法中,将结构体字段按序装入 []interface{} 切片,再调用 json.Marshal() 对该泛型切片编码。这样既保持了类型安全(编译期检查 Row 字段),又输出了紧凑数组格式。

以下是完整可运行示例:

package main  import (     "encoding/json"     "fmt" )  type Row struct {     Ooid  string     Score float64     Text  string }  // MarshalJSON 实现 json.Marshaler 接口 func (r *Row) MarshalJSON() ([]byte, error) {     // 按需顺序构造异构 slice:string → float64 → string     arr := []interface{}{r.Ooid, r.Score, r.Text}     return json.Marshal(arr) }  func main() {     rows := []Row{         {"ooid1", 2.0, "Söme text"},         {"ooid2", 1.3, "Åther text"},     }      // 序列化整个切片 → 得到二维 JSON 数组     data, err := json.Marshal(map[string]interface{}{         "results": rows,     })     if err != nil {         panic(err)     }      fmt.Println(string(data))     // 输出:     // {"results":[["ooid1",2,"Söme text"],["ooid2",1.3,"Åther text"]]} }

✅ 注意:json.Marshal() 对 []interface{} 的处理天然支持混合类型,且自动处理 Unicode(如 Söme、Åther)和浮点数精度(2.0 会被简化为 2,若需保留 .0 可改用 fmt.Sprintf(“%.1f”, r.Score) 转字符串)。

如需反向解析(从 JSON 数组还原为 Row),还需实现 json.Unmarshaler 接口:

func (r *Row) UnmarshalJSON(data []byte) error {     var arr []interface{}     if err := json.Unmarshal(data, &arr); err != nil {         return err     }     if len(arr) < 3 {         return fmt.Errorf("expected 3 elements, got %d", len(arr))     }      // 类型断言 + 错误检查(生产环境务必添加)     if s, ok := arr[0].(string); ok {         r.Ooid = s     } else {         return fmt.Errorf("field 0 must be string, got %T", arr[0])     }      if f, ok := arr[1].(float64); ok {         r.Score = f     } else if f64, ok := arr[1].(float32); ok {         r.Score = float64(f64)     } else {         return fmt.Errorf("field 1 must be number, got %T", arr[1])     }      if s, ok := arr[2].(string); ok {         r.Text = s     } else {         return fmt.Errorf("field 2 must be string, got %T", arr[2])     }      return nil }

使用示例(反序列化):

text := `[     ["ooid4", 3.1415, "pi"],     ["ooid5", 2.7182, "euler"] ]` var rows []Row if err := json.Unmarshal([]byte(text), &rows); err != nil {     panic(err) } fmt.Printf("%+vn", rows) // [{Ooid:ooid4 Score:3.1415 Text:pi} {Ooid:ooid5 Score:2.7182 Text:euler}]

关键总结

  • 不要试图用 []interface{} 直接构建原始数据——它牺牲类型安全且易出错;
  • 优先定义清晰结构体(Row),再通过 MarshalJSON/UnmarshalJSON 控制序列化形态;
  • 始终在 UnmarshalJSON 中加入长度校验与类型断言错误处理,避免 panic;
  • 此模式适用于 API 响应优化、与弱类型前端交互、或兼容遗留 JSON 格式等场景。

text=ZqhQzanResources