如何用protobuf-net序列化时兼容proto3包装类型字段格式?
解决protobuf-net序列化proto3包装类型格式匹配问题
问题概述
与协作方基于proto3协议对接,对方定义的RemoteDOutCommand消息中使用了google.protobuf.UInt32Value、BoolValue等包装类型。但在.NET 6中用protobuf-net(v3.22.0)将uint?/bool?类型的字段序列化后,生成的字节流不符合proto3包装类型的嵌套结构,导致protoc解码时显示为直接数值而非预期的包装类型结构。
对方的proto3核心定义:
syntax = "proto3"; import "google/protobuf/wrappers.proto"; package RemoteDOutCommandConfig; message RemoteDOutCommand { google.protobuf.UInt32Value command_id = 1; //transaction ID google.protobuf.BoolValue command_value = 2; uint32 command_trigger_time_utc = 3; google.protobuf.UInt32Value acceptable_execution_latency_secs = 4; google.protobuf.BoolValue disable_latency_check = 5; }
原.NET类定义(存在问题):
public partial class RemoteDOutCommand { [global::ProtoBuf.ProtoMember(1, Name = @"command_id")] public uint? CommandId { get; set; } [global::ProtoBuf.ProtoMember(2, Name = @"command_value")] public bool? CommandValue { get; set; } [global::ProtoBuf.ProtoMember(3, Name = @"command_trigger_time_utc")] public uint CommandTriggerTimeUtc { get; set; } [global::ProtoBuf.ProtoMember(4, Name = @"acceptable_execution_latency_secs")] public uint? AcceptableExecutionLatencySecs { get; set; } [global::ProtoBuf.ProtoMember(5, Name = @"disable_latency_check")] public bool? DisableLatencyCheck { get; set; } }
解决方案
protobuf-net默认会把nullable值类型序列化为proto3的普通可选字段,而非Google包装类型的嵌套结构。要匹配对方的格式,需让字段对应到包装类型的结构,有两种可行方式:
方式1:使用官方Google.Protobuf包装类型(推荐)
- 安装NuGet包
Google.Protobuf,该包包含官方定义的UInt32Value、BoolValue等包装类型。 - 修改.NET类,将
nullable值类型替换为对应的Google包装类型:
public partial class RemoteDOutCommand { [global::ProtoBuf.ProtoMember(1, Name = @"command_id")] public Google.Protobuf.WellKnownTypes.UInt32Value CommandId { get; set; } [global::ProtoBuf.ProtoMember(2, Name = @"command_value")] public Google.Protobuf.WellKnownTypes.BoolValue CommandValue { get; set; } [global::ProtoBuf.ProtoMember(3, Name = @"command_trigger_time_utc")] public uint CommandTriggerTimeUtc { get; set; } [global::ProtoBuf.ProtoMember(4, Name = @"acceptable_execution_latency_secs")] public Google.Protobuf.WellKnownTypes.UInt32Value AcceptableExecutionLatencySecs { get; set; } [global::ProtoBuf.ProtoMember(5, Name = @"disable_latency_check")] public Google.Protobuf.WellKnownTypes.BoolValue DisableLatencyCheck { get; set; } }
这样protobuf-net会自动按照proto3包装类型的嵌套结构序列化,生成的字节流与对方示例完全匹配。
方式2:自定义包装类并配置protobuf-net属性
若不想依赖Google.Protobuf包,可手动定义与proto包装类型结构一致的类:
// 自定义UInt32Value包装类,匹配proto定义 [global::ProtoBuf.ProtoContract(Name = @"UInt32Value")] public class UInt32Value { [global::ProtoBuf.ProtoMember(1, Name = @"value")] public uint Value { get; set; } } // 自定义BoolValue包装类,匹配proto定义 [global::ProtoBuf.ProtoContract(Name = @"BoolValue")] public class BoolValue { [global::ProtoBuf.ProtoMember(1, Name = @"value")] public bool Value { get; set; } } // 修改RemoteDOutCommand类 public partial class RemoteDOutCommand { [global::ProtoBuf.ProtoMember(1, Name = @"command_id")] public UInt32Value CommandId { get; set; } [global::ProtoBuf.ProtoMember(2, Name = @"command_value")] public BoolValue CommandValue { get; set; } [global::ProtoBuf.ProtoMember(3, Name = @"command_trigger_time_utc")] public uint CommandTriggerTimeUtc { get; set; } [global::ProtoBuf.ProtoMember(4, Name = @"acceptable_execution_latency_secs")] public UInt32Value AcceptableExecutionLatencySecs { get; set; } [global::ProtoBuf.ProtoMember(5, Name = @"disable_latency_check")] public BoolValue DisableLatencyCheck { get; set; } }
需确保自定义类的ProtoContract名称、ProtoMember的编号和名称与proto定义完全一致。
验证效果
修改后重新序列化,使用protoc解码将得到与对方示例一致的输出:
command_id { value: 12 } command_value { } command_trigger_time_utc: 1675325420 acceptable_execution_latency_secs { value: 200 } disable_latency_check { value: true }
内容的提问来源于stack exchange,提问作者fishberg5051
相关产品推荐
相关产品推荐

