如何解决逐字段读取Protobuf消息数据
对于我的基于 Java 的应用程序,我需要将 Protobuf 消息即时转换为 Avro 记录。消息和记录的模式在编译时是未知的,但在运行时是已知的。这个流程的 Avro 端已经完成,但我在 Protobuf 端挣扎。如何将 Protobuf 消息作为字节数组,并使用消息类型的 DescriptorProto 将其转换为可以逐字段读取的内容?我花了很多时间来快速了解 Protobuf API 并寻找相关示例,但我的用例似乎并不常见。
这是我目前能做的:
- 从磁盘加载一个 FileDescriptorSet,它描述了我将在网络上遇到的消息类型,并为特定的消息类型选择合适的 DescriptorProto
- 遍历 DescriptorProto 的字段,以便我在原则上知道如何解释特定消息的数据
- 使用生成的类构建测试消息,生成字节数组,这些数组代表了我对网络的期望
我还不能做的:
- 给定字节数组,并给定 DescriptorProto 中的一个字段,选择该字段的消息值
我尝试将字节数组合并到基于 DescriptorProto 的各种构建器中,但它们似乎没有给我所需的东西。我也无法从这些构建器中重现原始字节数组。显然,我需要克服一些概念上的差距。
编辑:这里有一些关于我正在尝试做的事情的额外细节。为简洁起见,已简化代码。
从磁盘加载一个或多个DescriptorProto
:
InputStream is = new FileInputStream("/path/to/mydescriptors.pb");
FileDescriptorSet set = FileDescriptorSet.parseFrom(is);
FileDescriptorProto geoposition = set.getFile(0);
DescriptorProto PositionEvent = geoposition.getMessageType(0);
填充 protoc 生成的域类的对象,用于测试:
Meters horizAcc = Meters.newBuilder().setValue(4.9f).build();
PositionEvent pe = PositionEvent.newBuilder()
.setPoint(point)
.setTime(time)
.setHorizontalAccuracy(horizAcc)
...
.build();
将域对象转换为字节数组(模拟线路上的数据):
byte[] bytes = pe.toByteArray();
将字节数组转换回域对象:
PositionEvent pe2 = PositionEvent.newBuilder().mergeFrom(bytes).build();
上面的 mergeFrom
允许我将字节数组转换为在编译之前生成的域类的实例。但是,在实际应用中,这个生成的类在编译时是不可用的。我需要将消息解析为一个通用类的实例,这将使我能够访问消息中各个字段的值,以及消息值字段、它们的字段等。
我认为 Descriptor
或 DescriptorProto
是我需要使用的类,但以下似乎不起作用:
DescriptorProtos.DescriptorProto p = positionEvent.toBuilder().mergeFrom(bytes).build();
至少,我不知道如何检索此 DescriptorProto
中“horizontal_accuracy”字段的值以确定该值是 4.9
。
我可以想象一个基于反射的解决方案,其中提前知道所有的消息类型,我使用 protoc 生成所有域类,然后我使用反射来填充适当的任何给定消息的域对象,并逐步遍历其字段。但是,这感觉像是一种黑客攻击,不适用于有成百上千种频繁更改的消息类型的场景(如我公司的情况)。
解决方法
到目前为止,您拥有的是一个 FileDescriptorProto
,它是描述符的序列化、与语言无关的版本。首先,您需要从它们构建它的 Java 对象,FileDescriptor
and Descriptor
。查看 API 以了解如何使用它们来获取特定类型的 Descriptor
:
InputStream is = new FileInputStream("/path/to/mydescriptors.pb");
FileDescriptorSet set = FileDescriptorSet.parseFrom(is);
FileDescriptorProto geopositionProto = set.getFile(0);
FileDescriptor geopositionDescriptor = FileDescriptor.buildFrom(
set.getFile(0),set.getFileList().toArray(new FileDescriptor[0]));
Descriptor positionEventDescriptor = geopositionDescriptor.findMessageTypeByName("<package_name>.PositionEvent");
然后,DynamicMessage
可用于创建和解析抽象类型的原型对象,给定它的描述符(您已经拥有)。
Message instance = DynamicMessage.parseFrom(positionEventDescriptor,bytes);
然后您可以使用反射 API(例如 getAllFields()
)以通用方式获取内容。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。