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

如何在gRPC拦截器中正确序列化Google.Protobuf.Any为JSON日志

解决gRPC Interceptor中Google.Protobuf.WellKnownTypes.Any类型日志序列化问题

问题根源

System.Text.Json默认无法识别Protobuf的Any类型,会直接将其value字段序列化为字节数组,而不会自动解析为对应的具体消息类型。要生成可读日志,需要手动根据typeUrl将Any解析为具体消息后再序列化。

解决方案步骤

1. 维护Protobuf类型注册表

创建全局字典,映射Any的typeUrl到对应的Protobuf消息类型,方便后续解析时快速查找:

// 根据业务实际情况,添加所有可能被Any包装的Protobuf类型
private static readonly Dictionary<string, Type> ProtobufTypeRegistry = new()
{
    {"type.googleapis.com/CardDeactivation.CardCloseParams", typeof(CardCloseParams)},
    {"type.googleapis.com/CardDeactivation.CardInfo", typeof(CardInfo)},
    {"type.googleapis.com/CardDeactivation.AccountInfo", typeof(AccountInfo)}
};

2. 自定义JsonConverter处理Any类型

实现JsonConverter<Any>,在序列化阶段自动将Any解析为具体消息类型,再输出可读JSON:

public class ProtobufAnyJsonConverter : JsonConverter<Any>
{
    public override Any Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // 日志场景以序列化为主,反序列化可根据需求实现,此处暂不处理
        throw new NotImplementedException("反序列化逻辑未实现");
    }

    public override void Write(Utf8JsonWriter writer, Any value, JsonSerializerOptions options)
    {
        if (value == null || string.IsNullOrEmpty(value.TypeUrl))
        {
            JsonSerializer.Serialize(writer, value, options);
            return;
        }

        // 根据typeUrl查找对应的Protobuf类型
        if (ProtobufTypeRegistry.TryGetValue(value.TypeUrl, out var targetType))
        {
            // 通过反射调用Any.Unpack方法解析为具体消息
            var unpackMethod = typeof(Any).GetMethod(nameof(Any.Unpack)).MakeGenericMethod(targetType);
            var message = unpackMethod.Invoke(value, null);
            
            // 将解析后的具体消息序列化为可读JSON
            JsonSerializer.Serialize(writer, message, targetType, options);
        }
        else
        {
            // 未找到对应类型时,回退到默认序列化(输出typeUrl和字节数组)
            JsonSerializer.Serialize(writer, value, options);
        }
    }
}

3. 更新序列化配置

将自定义转换器加入到原有的DefaultOptionsToLog中:

private static readonly JsonSerializerOptions DefaultOptionsToLog = new()
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    IgnoreNullValues = true,
    PropertyNameCaseInsensitive = true,
    Converters = { new ProtobufAnyJsonConverter() } // 新增自定义转换器
};

4. 原Interceptor代码无需修改

你的InterceptCall和ToJsonLog方法保持原样即可,序列化时会自动触发自定义转换器处理Any类型。

效果验证

修改后,日志中的screenParams会被序列化为具体的CardCloseParams结构,示例如下:

GRPC CLIENT REQUEST: Method: /CardDeactivation.ScreenService/GetScreenInfoForOperation {"screenType":2,"screenParams":{"cardToClose":{"cardNumber":"123456xxxx"},"cardsToChoose":[],"accountsToChoose":[],"isLast":false}}

注意事项

  • 所有可能被Any包装的Protobuf类型都需要注册到ProtobufTypeRegistry,否则会回退到字节数组输出
  • 若项目中Protobuf类型较多,可通过反射自动扫描程序集中实现IMessage的类型,自动生成typeUrl映射,避免手动维护
  • 如果需要支持反序列化,可自行实现Read方法,逻辑为:先解析JSON到具体类型,再调用Any.Pack完成包装

内容的提问来源于stack exchange,提问作者MyakishHentai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 14:37:37