
本文介绍如何在 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("%+v\n", rows) // [{Ooid:ooid4 Score:3.1415 Text:pi} {Ooid:ooid5 Score:2.7182 Text:euler}]关键总结:
- 不要试图用 []interface{} 直接构建原始数据——它牺牲类型安全且易出错;
- 优先定义清晰结构体(Row),再通过 MarshalJSON/UnmarshalJSON 控制序列化形态;
- 始终在 UnmarshalJSON 中加入长度校验与类型断言错误处理,避免 panic;
- 此模式适用于 API 响应优化、与弱类型前端交互、或兼容遗留 JSON 格式等场景。










