微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

逐字段读取Protobuf消息数据

如何解决逐字段读取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 允许我将字节数组转换为在编译之前生成的域类的实例。但是,在实际应用中,这个生成的类在编译时是不可用的。我需要将消息解析为一个通用类的实例,这将使我能够访问消息中各个字段的值,以及消息值字段、它们的字段等。

我认为 DescriptorDescriptorProto 是我需要使用的类,但以下似乎不起作用:

DescriptorProtos.DescriptorProto p = positionEvent.toBuilder().mergeFrom(bytes).build();

至少,我不知道如何检索此 DescriptorProto 中“horizo​​ntal_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 举报,一经查实,本站将立刻删除。