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;
}
Другие вопросы по тегам