web-dev-qa-db-ja.com

Java:JSON-> Protobufおよび逆変換

GUIとサーバー間でprotobuf-based通信プロトコルを使用している既存のシステムがあります。ここで永続性を追加したいと思いますが、現時点ではprotobufメッセージはサードパーティのカスタムオブジェクトに直接変換されます。

protoメッセージをjsonに変換する方法はありますか?その後、データベースに保存されました。

N.B.:バイナリprotobufをデータベースに書き込むという考えは、いつかは新しいバージョンとの下位互換性がなくなり、システムをそのように壊してしまう可能性があるため、あまり好きではありません。

36
Denis Kulagin

現在、 protobuf-Java-format を使用して、Protobufメッセージ(Messageのサブクラス)をJSON形式に変換し、Web APIで送信します。

単純に:

  JsonFormat.printToString(protoMessage)
38
jas_raj

同様の質問への回答 で述べたように、 v3.1. であるため、これはProtocolBuffersのサポートされている機能です。 Javaの場合、拡張モジュール com.google.protobuf:protobuf-Java-util を含め、 JsonFormat を次のように使用します。

JsonFormat.parser().ignoringUnknownFields().merge(json, yourObjectBuilder);
YourObject value = yourObjectBuilder.build();
35
Ophir Radnitz

バイナリprotobufをデータベースに書き込むという考えはあまり好きではありません。それは、いつか新しいバージョンと後方互換性がなくなり、システムをそのように壊してしまう可能性があるからです。

ストレージでprotobufをJSONに変換し、ロード時にprotobufに戻すと、互換性の問題が発生する可能性が高くなりますmore

  • 変換を実行するプロセスがprotobufスキーマの最新バージョンで構築されていない場合、変換はプロセスが知らないフィールドを静かにドロップします。これは、保存とロードの両方の終わりに当てはまります。
  • 最新のスキーマが利用可能であっても、JSON <-> Protobuf変換は、不正確な浮動小数点値や同様のコーナーケースが存在する場合、損失が生じる可能性があります。
  • 実際、Protobufsには、JSONよりも後方互換性の保証が(わずかに)強力です。 JSONと同様に、新しいフィールドを追加すると、古いクライアントはそれを無視します。 JSONとは異なり、Protobufsではデフォルト値を宣言することができます。これにより、新しいクライアントがフィールドが欠落している古いデータを簡単に処理できるようになります。これはわずかな利点ですが、それ以外の場合、ProtobufとJSONには同等の後方互換性プロパティがあるため、JSONに格納しても後方互換性の利点は得られません。

とはいえ、protobufをJSONに変換するための多くのライブラリがあり、通常はProtobufリフレクションインターフェイス上に構築されます(Javaリフレクションインターフェイスと混同しないでください。Protobufリフレクションはcom.google.protobuf.Messageインターフェイス)。

34
Kenton Varda

Ophir sの回答に追加すると、JsonFormatはprotobuf 3.0以前でも使用できます。ただし、その方法は少し異なります。

Protobuf 3.0+では、JsonFormatクラスはシングルトンであるため、以下のようなことを行います

String jsonString = "";
JsonFormat.parser().ignoringUnknownFields().merge(json,yourObjectBuilder);

Protobuf 2.5+では、以下が機能するはずです

String jsonString = "";
JsonFormat jsonFormat = new JsonFormat();
jsonString = jsonFormat.printToString(yourProtobufMessage);

GsonBuilderオブジェクトに登録できるTypeAdapterでJsonFormatクラスを使用する tutorial へのリンクを次に示します。その後、GsonのtoJsonおよびfromJsonメソッドを使用して、プロトデータをJavaに変換したり、その逆に変換したりできます。

jean に返信します。ファイルにprotobufデータがあり、それを解析してprotobufメッセージオブジェクトにしたい場合は、mergeメソッド TextFormat クラスを使用します。以下のスニペットを参照してください。

// Let your proto text data be in a file MessageDataAsProto.prototxt
// Read it into string  
String protoDataAsString = FileUtils.readFileToString(new File("MessageDataAsProto.prototxt"));

// Create an object of the message builder
MyMessage.Builder myMsgBuilder = MyMessage.newBuilder();

// Use text format to parse the data into the message builder
TextFormat.merge(protoDataAsString, ExtensionRegistry.getEmptyRegistry(), myMsgBuilder);

// Build the message and return
return myMsgBuilder.build();
12
Moses

JsonFormat.printer().print(MessageOrBuilder)を試してください。proto3に適しています。しかし、実際のprotobufメッセージ(.protoファイルで定義された私の選択のJavaパッケージとして提供される)をcom.google.protbuf.Messageオブジェクトに変換する方法は不明です。 。

4
jean

Protobuf 2.5の場合、依存関係を使用します。

"com.googlecode.protobuf-Java-format" % "protobuf-Java-format" % "1.2"

次に、コードを使用します。

com.googlecode.protobuf.format.JsonFormat.merge(json, builder)
com.googlecode.protobuf.format.JsonFormat.printToString(proto)
4
Henry

まあ、私の発見によると、それを行うためのショートカットはありませんが、どういうわけかあなたは
いくつかの簡単なステップでそれを達成する

まず、タイプ 'ProtobufJsonFormatHttpMessageConverter'のBeanを宣言する必要があります

@Bean  
@Primary  
public ProtobufJsonFormatHttpMessageConverter protobufHttpMessageConverter() {  
  return new ProtobufJsonFormatHttpMessageConverter(JsonFormat.parser(), JsonFormat.printer());  
}  

次に、ResponseBuilderのようなユーティリティクラスを記述することができます。これは、デフォルトで要求を解析できるが、これらの変更がないとJson応答を生成できないためです。そして、いくつかのメソッドを記述して、応答タイプを関連するオブジェクトタイプに変換できます。

public static <T> T build(Message message, Class<T> type) {
  Printer printer = JsonFormat.printer();
  Gson gson = new Gson();
  try {
    return gson.fromJson(printer.print(message), type);
  } catch (JsonSyntaxException | InvalidProtocolBufferException e) {
    throw new ApiException(HttpStatus.INTERNAL_SERVER_ERROR, "Response   conversion Error", e);
  }
}

次に、次のような最後の行としてコントローラクラスからこのメソッドを呼び出すことができます-

return ResponseBuilder.build(<returned_service_object>, <Type>);

これが、protobufをjson形式で実装するのに役立つことを願っています。

2
Bharat

ジェネリックソリューション

Jsonコンバーターの一般的なバージョンを次に示します

package com.intuit.platform.util;

import Java.io.IOException;
import Java.lang.reflect.InvocationTargetException;
import com.google.protobuf.AbstractMessage.Builder;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;

/**
 * Generic ProtoJsonUtil to be used to serialize and deserialize Proto to json
 * 
 * @author [email protected]
 *
 */
public final class ProtoJsonUtil {

  /**
   * Makes a Json from a given message or builder
   * 
   * @param messageOrBuilder is the instance
   * @return The string representation
   * @throws IOException if any error occurs
   */
  public static String toJson(MessageOrBuilder messageOrBuilder) throws IOException {
    return JsonFormat.printer().print(messageOrBuilder);
  }

  /**
   * Makes a new instance of message based on the json and the class
   * @param <T> is the class type
   * @param json is the json instance
   * @param clazz is the class instance
   * @return An instance of T based on the json values
   * @throws IOException if any error occurs
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  public static <T extends Message> T fromJson(String json, Class<T> clazz) throws IOException {
    // https://stackoverflow.com/questions/27642021/calling-parsefrom-method-for-generic-protobuffer-class-in-Java/33701202#33701202
    Builder builder = null;
    try {
      // Since we are dealing with a Message type, we can call newBuilder()
      builder = (Builder) clazz.getMethod("newBuilder").invoke(null);

    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
        | NoSuchMethodException | SecurityException e) {
      return null;
    }

    // The instance is placed into the builder values
    JsonFormat.parser().ignoringUnknownFields().merge(json, builder);

    // the instance will be from the build
    return (T) builder.build();
  }
}

次のように簡単に使用できます。

メッセージインスタンス

GetAllGreetings.Builder allGreetingsBuilder = GetAllGreetings.newBuilder();

allGreetingsBuilder.addGreeting(makeNewGreeting("Marcello", "Hi %s, how are you", Language.EN))
        .addGreeting(makeNewGreeting("John", "Today is hot, %s, get some ice", Language.ES))
        .addGreeting(makeNewGreeting("Mary", "%s, summer is here! Let's go surfing!", Language.PT));

GetAllGreetings allGreetings = allGreetingsBuilder.build();

Json Genericへ

String json = ProtoJsonUtil.toJson(allGreetingsLoaded);
log.info("Json format: " + json);

Json Genericから

GetAllGreetings parsed = ProtoJsonUtil.fromJson(json, GetAllGreetings.class);
log.info("The Proto deserialized from Json " + parsed);
0