【发布时间】:2010-10-27 15:40:18
【问题描述】:
是否可以在 Java 中以通用方式解析 protobuf?
我查看了 GeneratedMessage,但找不到将任何 PB 字节缓冲区解析为 GeneratedMessage 的方法。
本质上,我正在尝试将 PB 字节缓冲区解析为 GeneratedMessage,然后我会使用反射来检测其中的字段。
【问题讨论】:
标签: java protocol-buffers
是否可以在 Java 中以通用方式解析 protobuf?
我查看了 GeneratedMessage,但找不到将任何 PB 字节缓冲区解析为 GeneratedMessage 的方法。
本质上,我正在尝试将 PB 字节缓冲区解析为 GeneratedMessage,然后我会使用反射来检测其中的字段。
【问题讨论】:
标签: java protocol-buffers
首先,不知道架构是无法解析 PB 数据的。架构最初来自“.proto”文件,通常嵌入在protoc 生成的代码中。但是,您也可以告诉 protoc 以 Java Protobuf 库可用的格式存储架构:
protoc --descriptor_set_out=mymessages.desc mymessages.proto
然后将其加载到您的 Java 代码中:
FileInputStream fin = new FileInputStream("mymessages.desc");
Descriptors.FileDescriptorSet set =
Descriptors.FileDescriptorSet.parseFrom(fin);
Descriptors.Descriptor md = set.getFile(0).getMessageType(0);
一旦您有了消息的架构 (Descriptor.Descriptor),解析消息就很容易了:
byte[] data = ...;
DynamicMessage m = DynamicMessage.parseFrom(md, data);
DynamicMessage 有一个反射 API,可让您查看字段。
混乱的部分是调用protoc 工具将“.proto”文件转换为可用格式。 C++ Protobuf 库有办法直接加载“.proto”文件,但不幸的是 Java Protobuf 库没有。
【讨论】:
您可以使用 UnknownFieldSet 解析通用 protobuf 消息。
然后您可以使用提供的方法获取各个字段(例如asMap()、hasField()、getField())
例如(数据取自this question):
byte[] msg = new byte[] { 0x0a, 0x10, 0x08, 0x7f, (byte)0x8a, 0x01, 0x04, 0x08, 0x02, 0x10, 0x03, (byte)0x92, 0x01, 0x04, 0x08, 0x02, 0x10, 0x03, 0x18, 0x01};
UnknownFieldSet eee = UnknownFieldSet.parseFrom(msg);
System.out.println(eee.toString());
给予:
1: {
1: 127
17: {
1: 2
2: 3
}
18: {
1: 2
2: 3
}
}
3: 1
【讨论】:
这是正确的例子:
private static DynamicMessage parseData(byte[] data) throws IOException, DescriptorValidationException {
FileInputStream fin = new FileInputStream("test.desc");
DescriptorProtos.FileDescriptorSet set = DescriptorProtos.FileDescriptorSet.parseFrom(fin);
Descriptor md = Descriptors.FileDescriptor.buildFrom(set.getFile(0), new Descriptors.FileDescriptor[] {}).findMessageTypeByName("Person");
return DynamicMessage.parseFrom(md, data);
}
【讨论】:
我已经使用最后一个 protobuf v.3.1.0 测试了有效的解决方案
这是根据以前的答案提出的升级解决方案。感谢两位作者。
import com.example.address.AddressBookManager;
import com.example.address.AddressBookProtos.AddressBook;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.DynamicMessage;
import java.io.File;
import java.io.InputStream;
public class DynamicMessageDemo {
private static final String ADDRESS_BOOK_SOURCE_FILENAME = "test.ab";
private static final String ADDRESS_BOOK_DESC_FILENAME =
File.separator + "address.desc";
public static void main(String[] args) throws Exception {
InputStream is = DynamicMessageDemo.class
.getResourceAsStream(ADDRESS_BOOK_DESC_FILENAME);
FileDescriptorSet set = FileDescriptorSet.parseFrom(is);
FileDescriptor fd = Descriptors.FileDescriptor.buildFrom(
set.getFile(0),
new Descriptors.FileDescriptor[]{}
);
// "AddressBook" is the second message in my *.proto
// so index must be '1'
Descriptor messageType = fd.getMessageTypes().get(1);
// for testing purpose
AddressBook book = AddressBookManager.readFromFile(ADDRESS_BOOK_SOURCE_FILENAME);
byte[] data = book.toByteArray();
DynamicMessage message = DynamicMessage.parseFrom(messageType, data);
System.out.println("\n Dynamic message:\n" + message);
}
}
【讨论】:
这是另一种通用解析 .desc 文件的方法:
FileInputStream fin = new FileInputStream(descPath);
DescriptorProtos.FileDescriptorSet set = DescriptorProtos.FileDescriptorSet.parseFrom(fin);
List<FileDescriptor> dependencyFileDescriptorList = new ArrayList<>();
for(int i=0; i<set.getFileCount()-1;i++) {
dependencyFileDescriptorList.add(Descriptors.FileDescriptor.buildFrom(set.getFile(i), new Descriptors.FileDescriptor[] {}));
}
FileDescriptor desc = Descriptors.FileDescriptor.buildFrom(set.getFile(set.getFileCount()-1), dependencyFileDescriptorList.toArray(new FileDescriptor[0]));
【讨论】: