Protobuf 3.0 Any Type упаковать / распаковать
Я хотел бы знать, как преобразовать Protobuf Any Type в исходный тип сообщения Protobuf и наоборот. В Java от сообщения до любого легко:
Any.Builder anyBuilder = Any.newBuilder().mergeFrom(protoMess.build());
Но как я могу разобрать, что Any возвращается к исходному сообщению (например, к типу "protoMess")? Я мог бы, вероятно, проанализировать все в потоке, чтобы просто прочитать его обратно, но это не то, что я хочу. Я хочу, чтобы у меня было такое преобразование:
ProtoMess.MessData.Builder protoMessBuilder = (ProtoMess.MessData.Builder) transformToMessageBuilder(anyBuilder)
Как я могу этого достичь? Это уже реализовано для Java? В руководстве по языку Protobuf говорится, что были методы упаковки и распаковки, но в Java их нет. Заранее спасибо:)
4 ответа
Ответ может быть немного поздно, но, возможно, это все еще кому-то помогает.
В текущей версии протокола Buffers 3 pack
а также unpack
доступны на Java.
В вашем примере упаковка может быть сделана так:
Any anyMessage = Any.pack(protoMess.build()));
И распаковка вроде:
ProtoMess protoMess = anyMessage.unpack(ProtoMess.class);
Вот также полный пример для обработки сообщений протокола Buffers с вложенными Any
Сообщения:
ProtocolBuffers Files
Простой файл Protocol Buffers с вложенным Any
сообщение может выглядеть так:
syntax = "proto3";
import "google/protobuf/any.proto";
message ParentMessage {
string text = 1;
google.protobuf.Any childMessage = 2;
}
Возможное вложенное сообщение может быть:
syntax = "proto3";
message ChildMessage {
string text = 1;
}
упаковка
Для создания полного сообщения можно использовать следующую функцию:
public ParentMessage createMessage() {
// Create child message
ChildMessage.Builder childMessageBuilder = ChildMessage.newBuilder();
childMessageBuilder.setText("Child Text");
// Create parent message
ParentMessage.Builder parentMessageBuilder = ParentMessage.newBuilder();
parentMessageBuilder.setText("Parent Text");
parentMessageBuilder.setChildMessage(Any.pack(childMessageBuilder.build()));
// Return message
return parentMessageBuilder.build();
}
Распаковка
Чтобы прочитать дочернее сообщение из родительского сообщения, можно использовать следующую функцию:
public ChildMessage readChildMessage(ParentMessage parentMessage) {
try {
return parentMessage.getChildMessage().unpack(ChildMessage.class);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
return null;
}
}
РЕДАКТИРОВАТЬ:
Если ваши упакованные сообщения могут иметь разные типы, вы можете прочитать typeUrl
и используйте отражение, чтобы распаковать сообщение. Предполагая, что у вас есть дочерние сообщения ChildMessage1
а также ChildMessage2
Вы можете сделать следующее:
@SuppressWarnings("unchecked")
public Message readChildMessage(ParentMessage parentMessage) {
try {
Any childMessage = parentMessage.getChildMessage();
String clazzName = childMessage.getTypeUrl().split("/")[1];
String clazzPackage = String.format("package.%s", clazzName);
Class<Message> clazz = (Class<Message>) Class.forName(clazzPackage);
return childMessage.unpack(clazz);
} catch (ClassNotFoundException | InvalidProtocolBufferException e) {
e.printStackTrace();
return null;
}
}
Для дальнейшей обработки вы можете определить тип сообщения с помощью instanceof
, что не очень эффективно. Если вы хотите получить сообщение определенного типа, вы должны сравнить typeUrl
непосредственно:
public ChildMessage1 readChildMessage(ParentMessage parentMessage) {
try {
Any childMessage = parentMessage.getChildMessage();
String clazzName = childMessage.getTypeUrl().split("/")[1];
if (clazzName.equals("ChildMessage1")) {
return childMessage.unpack("ChildMessage1.class");
}
return null
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
return null;
}
}
Просто чтобы добавить информацию на случай, если у кого-то такая же проблема... В настоящее время вам нужно распаковать (C# .netcore 3.1 Google.Protobuf 3.11.4)
Foo myobject = anyMessage.Unpack<Foo>();
Я знаю, что этот вопрос очень старый, но он все еще возникал, когда я искал ответ. Используя ответ @sundance, я должен был ответить немного иначе. Проблема в том, что реальное сообщение было подклассом фактического класса. Так что требуется $.
for(Any x : in.getDetailsList()){
try{
String clazzName = x.getTypeUrl().split("/")[1];
String[] split_name = clazzName.split("\\.");
String nameClass = String.join(".", Arrays.copyOfRange(split_name, 0, split_name.length - 1)) + "$" + split_name[split_name.length-1];
Class<Message> clazz = (Class<Message>) Class.forName(nameClass);
System.out.println(x.unpack(clazz));
} catch (Exception e){
e.printStackTrace();
}
}
При том, что это определение моих прото сообщений
syntax = "proto3";
package cb_grpc.msg.Main;
service QueryService {
rpc anyService (AnyID) returns (QueryResponse) {}
}
enum Buckets {
main = 0;
txn = 1;
hxn = 2;
}
message QueryResponse{
string content = 1;
string code = 2;
}
message AnyID {
Buckets bucket = 1;
string docID = 2;
repeated google.protobuf.Any details = 3;
}
а также
syntax = "proto3";
package org.querc.cb_grpc.msg.database;
option java_package = "org.querc.cb_grpc.msg";
option java_outer_classname = "database";
message TxnLog {
string doc_id = 1;
repeated string changes = 2;
}
Это то, что я сделал в Android Java в своем проекте в качестве функции пакета:
String name = message.getClass().getSimpleName();
String packageName = message.getClass().getPackage().getName(); //needs to be the same as your proto package
String fullName = packageName + '.'+ name;
String typeURL = GetTypeUrl(fullName);
Any any = Any.newBuilder()
.setValue(message.toByteString())
.setTypeUrl(typeURL)
.build();
private String GetTypeUrl(String fullName) {
String prefix = "type.googleapis.com";
return prefix + "/" + fullName;
}