You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 10:20:32