Scott

去除 protobuf Any 类型中的 @type 字段 2 months ago

微服务
2816个字符
共有22人围观

去除 protobuf Any 类型中的 @type 字段

在 Protocol Buffers 的 Any 类型中,@type 字段是默认包含的类型标识信息,用于反序列化时识别原始类型。如果你想去掉这个字段,有以下几种解决方案:

方案1:使用自定义 JSON 序列化(推荐)

修改你的 ResponseMessage 定义,添加自定义 JSON 序列化选项:

import "google/protobuf/any.proto";
import "google/protobuf/struct.proto";

message ResponseMessage {
  int32 code = 1;
  string message = 2;
  oneof data {
    google.protobuf.Any any_data = 3;  // 保留原始 Any 功能
    google.protobuf.Value json_data = 4; // 使用更灵活的 JSON 结构
  }
}

然后在代码中:

func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.ResponseMessage, error) {
    // ...业务逻辑
    
    // 使用 Value 而不是 Any
    data, _ := structpb.NewValue(map[string]interface{}{
        "id":       u.ID,
        "username": u.Username,
    })
    
    return &pb.ResponseMessage{
        Code:    200,
        Message: "创建用户成功",
        Data: &pb.ResponseMessage_JsonData{
            JsonData: data,
        },
    }, nil
}

方案2:使用自定义结构体直接序列化

修改 ResponseMessage 定义:

message ResponseMessage {
  int32 code = 1;
  string message = 2;
  bytes data = 3;  // 存储原始 JSON 数据
}

实现代码:

func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.ResponseMessage, error) {
    // ...业务逻辑
    
    jsonData, _ := json.Marshal(map[string]interface{}{
        "id":       u.ID,
        "username": u.Username,
    })
    
    return &pb.ResponseMessage{
        Code:    200,
        Message: "创建用户成功",
        Data:    jsonData,
    }, nil
}

方案3:在网关层转换(适用于有 API 网关的场景)

如果你使用 API 网关,可以在网关层对响应进行转换:

// 网关中间件
func ResponseTransformer() middleware.Middleware {
    return func(handler middleware.Handler) middleware.Handler {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            resp, err := handler(ctx, req)
            if err != nil {
                return nil, err
            }
            
            if msg, ok := resp.(*pb.ResponseMessage); ok {
                return transformResponse(msg), nil
            }
            return resp, nil
        }
    }
}

func transformResponse(msg *pb.ResponseMessage) map[string]interface{} {
    result := map[string]interface{}{
        "code":    msg.Code,
        "message": msg.Message,
    }
    
    if msg.Data != nil {
        var data map[string]interface{}
        _ = json.Unmarshal([]byte(msg.Data.Value), &data)
        result["data"] = data
    }
    
    return result
}

方案4:修改 proto 定义(最简单)

如果你可以接受修改 proto 定义,最简单的方法是直接包含响应结构:

message ResponseMessage {
  int32 code = 1;
  string message = 2;
  UserCreateResponse data = 3;  // 直接使用具体类型
}

注意事项

  1. 去除 @type 会影响客户端反序列化能力,确保客户端不需要类型信息
  2. 方案1和方案2保持了最大的灵活性,同时去除了类型信息
  3. 方案4最简单但灵活性最低,适合响应结构固定的场景

我推荐使用方案1,它既保持了 protobuf 的类型安全,又提供了灵活的 JSON 输出控制。