如何在Go中序列化同时包含普通结构体与protobuf的嵌套结构体为JSON?
处理混合普通结构体与Protobuf Message的JSON序列化
要实现对所有嵌套对象自动区分处理,你可以通过自定义JSON marshaler结合反射递归遍历来实现,不用给每个结构体单独写MarshalJSON。核心思路是在序列化时递归检查每个字段的类型,对proto.Message用protojson.Marshal,其他类型用标准库的序列化逻辑。
具体实现步骤
- 定义一个包装类型,实现
json.Marshaler接口 - 在
MarshalJSON方法中,用反射遍历目标结构体的所有字段,根据类型分情况处理:- 字段实现
proto.Message:调用protojson.Marshal序列化 - 字段是嵌套结构体/指针/切片/映射:递归调用自定义序列化逻辑
- 普通基础类型:直接用标准JSON序列化
- 字段实现
代码示例
先假设你有这样的结构:
- 普通结构体
User、Address - Protobuf生成的
Order(实现proto.Message)
package main import ( "encoding/json" "fmt" "reflect" "strings" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) // 普通结构体 type Address struct { Street string `json:"street"` City string `json:"city"` } type User struct { Name string `json:"name"` Age int `json:"age"` Addr Address `json:"addr"` Order *Order `json:"order"` // Protobuf生成的Message Friends []User `json:"friends"` } // 假设这是proto生成的Order结构体(实现proto.Message) type Order struct { OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3"` Amount int64 `protobuf:"varint,2,opt,name=amount,proto3"` } // 实现proto.Message的必要方法(示例用,实际由protoc生成) func (o *Order) Reset() {} func (o *Order) String() string { return fmt.Sprintf("Order{Id: %s, Amount: %d}", o.OrderId, o.Amount) } func (o *Order) ProtoMessage() {} // 自定义包装类型,用于实现自定义MarshalJSON type CustomMarshaler struct { Data interface{} } // 实现json.Marshaler接口 func (cm CustomMarshaler) MarshalJSON() ([]byte, error) { return marshalValue(reflect.ValueOf(cm.Data)) } // 递归处理任意类型的序列化 func marshalValue(v reflect.Value) ([]byte, error) { // 处理指针:取指向的实际值 if v.Kind() == reflect.Ptr { if v.IsNil() { return json.Marshal(nil) } return marshalValue(v.Elem()) } // 处理切片/数组 if v.Kind() == reflect.Slice || v.Kind() == reflect.Array { var elements [][]byte for i := 0; i < v.Len(); i++ { elemBytes, err := marshalValue(v.Index(i)) if err != nil { return nil, err } elements = append(elements, elemBytes) } return []byte(fmt.Sprintf("[%s]", strings.Join(elements, ","))), nil } // 处理映射 if v.Kind() == reflect.Map { var pairs []string for _, key := range v.MapKeys() { keyBytes, err := marshalValue(key) if err != nil { return nil, err } valBytes, err := marshalValue(v.MapIndex(key)) if err != nil { return nil, err } pairs = append(pairs, fmt.Sprintf("%s:%s", keyBytes, valBytes)) } return []byte(fmt.Sprintf("{%s}", strings.Join(pairs, ","))), nil } // 处理结构体 if v.Kind() == reflect.Struct { // 先检查是否是proto.Message if msg, ok := v.Interface().(proto.Message); ok { return protojson.Marshal(msg) } // 普通结构体:遍历字段,处理json标签 var fields []string t := v.Type() for i := 0; i < t.NumField(); i++ { field := t.Field(i) // 跳过未导出字段 if field.PkgPath != "" { continue } // 解析json标签 jsonTag := field.Tag.Get("json") if jsonTag == "-" { continue // 忽略该字段 } fieldName := jsonTag if fieldName == "" { fieldName = field.Name } // 序列化字段值 valBytes, err := marshalValue(v.Field(i)) if err != nil { return nil, err } fields = append(fields, fmt.Sprintf("\"%s\":%s", fieldName, valBytes)) } return []byte(fmt.Sprintf("{%s}", strings.Join(fields, ","))), nil } // 基础类型:直接用标准json.Marshal return json.Marshal(v.Interface()) } // 使用示例 func main() { user := User{ Name: "Alice", Age: 30, Addr: Address{ Street: "Main St", City: "New York", }, Order: &Order{ OrderId: "ORD-123", Amount: 999, }, Friends: []User{ {Name: "Bob", Age: 28}, }, } // 使用自定义marshaler序列化 data, err := json.Marshal(CustomMarshaler{Data: user}) if err != nil { panic(err) } fmt.Println(string(data)) }
注意事项
- 上述代码处理了指针、切片、映射、结构体等常见类型,你可以根据自己的需求扩展(比如处理chan、func等,但一般序列化不需要)
- 对于Protobuf对象,
protojson.Marshal会遵循Protobuf的JSON映射规则(比如字段名转为驼峰、枚举值转字符串等) - 未导出字段会被自动跳过,和标准
json.Marshal行为一致 - 如果你的嵌套结构里有循环引用,需要额外处理避免无限递归(可以加一个visited集合记录已处理的指针)
内容的提问来源于stack exchange,提问作者epb
相关产品推荐
相关产品推荐

