
本文介绍如何在 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 格式等场景。