Jsonとの相互変換

Protocol Buffersのversion 3からは、Jsonとの相互変換の規則が仕様として定められています。 仕様で定められているということは、どのプログラミング言語を使っているのか?に関係なく、使うライブラリなどが仕様通りに実装されていればその変換規則を使っているということで、相互運用性が上がるなどのメリットがあるので、その仕様に従うように(仕様に準拠しているライブラリを使うように)しましょう。

https://developers.google.com/protocol-buffers/docs/proto3#json

このページでは、その方法について説明します。

ここではJavaのProtocol Buffersのライブラリ経由で変換する方法で説明しますscalapb-json。 まず、JavaのProtocol BuffersのJson変換部分には、追加で依存ライブラリが必要ですgsonbuild.sbtに以下のように設定してください。

libraryDependencies += "com.google.protobuf" % "protobuf-java-util" % "3.5.1"

一旦Javaのオブジェクトに変換する都合上、javaConversionsの設定も追加しておいてください。

どちらの変換の場合もcom.google.protobuf.util.JsonFormatというclassを使用するので、さきにそのソースコードへのリンクを貼っておきます。

https://github.com/google/protobuf/blob/v3.5.1/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java

また、Jsonとの相互変換するための例として、さきほど別のページで出したUserのcase classを使います。

case classからJsonへ変換

import com.google.protobuf.util.JsonFormat

val user = example.user.User(id = 1, name = "foo")
// user: User = User(1L, "foo", UnknownFieldSet(Map()))

val userJson = JsonFormat.printer.print(example.user.User.toJavaProto(user))
// userJson: String = """{
//   "id": "1",
//   "name": "foo"
// }"""

変換用の便利なclass

以下のようなclassを定義しておくと、enrich my libraryパターンで、 ScalaPBで生成されたどのcase classでも toJsonString というメソッド1つで変換できるようになるのでおすすめです。

import com.google.protobuf.MessageOrBuilder
import com.google.protobuf.util.JsonFormat
import scalapb.JavaProtoSupport

implicit class GeneratedMessageOps[A](val self: A) {
  def toJsonString[B <: MessageOrBuilder](implicit A: JavaProtoSupport[A, B]): String =
    JsonFormat.printer.print(A.toJavaProto(self))
}
User(id = 2, name = "bar").toJsonString
// res1: String = """{
//   "id": "2",
//   "name": "bar"
// }"""

Json文字列からcase classへの変換

def jsonStringToUser(json: String): example.user.User = {
  val parser = JsonFormat.parser()
  val builder = example.UserOuterClass.User.newBuilder()
  parser.merge(json, builder)
  example.user.User.fromJavaProto(builder.build())
}
jsonStringToUser(userJson)
// res2: User = User(1L, "foo", UnknownFieldSet(Map()))

JSON に Any 型のメッセージが含まれている場合には、その Any で使われているメッセージの Descriptor を注入する必要があります。例えば、Any に User を格納している場合は以下のようにしてパーサーを構築します。type-registry

val registry = JsonFormat.TypeRegistry.newBuilder().add(example.user.User.javaDescriptor).build()
val parser = JsonFormat.parser().usingTypeRegistry(registry)
scalapb-json. 2016年4月頃に https://github.com/scalapb/scalapb-json4s というものが出来ましたが、個人的にjson4sは非公式なリフレクションAPI使っていたりするなどの理由でお勧めしたくないので、説明しません。筆者がplay-jsonなど、他のjsonのライブラリを使った変換用のもの( https://github.com/scalapb-json/scalapb-playjson )は作っているので、もし使うなら scalapb-json4s よりも scalapb-playjson などのほうがおすすめです。(scalapb-playjsonがしっかりメンテナンスされている限りにおいて) https://xuwei-k.hatenablog.com/entry/2018/01/03/234858
gson. このprotobuf-java-utilは、googleのgsonやguavaというライブラリに依存します。依存が衝突しないように注意してください https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util/3.5.1/protobuf-java-util-3.5.1.pom
type-registry. あるメッセージの DescriptorTypeRegistry に追加すると、そのメッセージが定義されている .proto ファイルと、その .proto ファイルが(直接・間接的に)インポートしている .proto ファイルに定義されている全てのメッセージの Descriptor が同時に追加されます。

results matching ""

    No results matching ""