【问题标题】:Reading Protobuf message data field-by-field逐字段读取 Protobuf 消息数据
【发布时间】:2021-07-10 15:16:08
【问题描述】:

对于我的基于 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 生成所有域类,然后使用反射填充适当的任何给定消息的域对象,并逐步遍历其字段。但是,这感觉像是一种 hack,不适合有成百上千的消息类型频繁变化的场景(就像我公司的情况)。

【问题讨论】:

  • 嗨(来自校友)。只是好奇,您是如何“将字节数组合并到各种构建器中”的?如果您可以编辑您的问题以显示一些代码。
  • 谢谢你,slackwing。我添加了一些示例代码来说明我想要做什么。还更新了我的个人资料:-)

标签: protocol-buffers protobuf-java


【解决方案1】:

到目前为止,您拥有的是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 可用于创建和解析抽象类型的 proto 对象,给定它的描述符(您已经拥有)。

Message instance = DynamicMessage.parseFrom(positionEventDescriptor, bytes);

然后您可以使用反射 API(例如getAllFields())以通用方式获取内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-07
    • 1970-01-01
    相关资源
    最近更新 更多