パルカワ2

PerlとRubyとイチャラブ

grpc-java/protobuf-lite では JsonFormat がない

Unable to find JsonFormat class in Android · Issue #276 · google/protobuf-gradle-plugin · GitHub

Androidprotobuf-liteを利用したまま、JSONをオブジェクトにする必要があったので、Gsonで頑張ることにした。InternalなAPIを呼ぶ必要があり困っている。ちなみに grpc/grpc-swiftは頑張る必要もなくシュッと出来ます。

  • protoで定義されたフィールドは、生成されたクラスのメンバー変数では最後に_ がつく
    • 例えば、 name というフィールドを持ってるとすると生成されたクラスのメンバー変数では name_ になる
  • protoで定義されたフィールドでリピートするものは、ただのListではなくInternal.ProtobufList<T>で表現されている
    • TypeAdapter がないとエラーになる

以上をふまえて、以下のようなコードを書いた。

package com.google.protobuf

open class ProtobufListTypeAdapter<T>(val parser: (JsonElement) -> Collection<T>): TypeAdapter<Internal.ProtobufList<T>>() {
    override fun write(out: JsonWriter?, value: Internal.ProtobufList<T>?) {
        // 利用していないので実装していない
    }

     override fun read(jsonReader: JsonReader): Internal.ProtobufList<T> {
        val list = ProtobufArrayList<T>()
        list.addAll(parser(JsonParser().parse(jsonReader)))
        return list
    }
}

// 型ごとに作ることになるが、リフレクションで頑張ればどうにかできそう(試していない)
 class DeliveryTimeListTypeAdapter: ProtobufListTypeAdapter<DeliveryTimeOuterClass.DeliveryTime>({
    it.asJsonArray.map { element ->
        DeliveryTimeOuterClass.DeliveryTime.newBuilder()
            .build()
    }
})

ProtobufArrayListが、com.google.protobuf内でしか利用できないので、package com.google.protobufにしている。

class ProtoExclusionStrategy: ExclusionStrategy {
    override fun shouldSkipClass(clazz: Class<*>?): Boolean {
        return false
    }

    override fun shouldSkipField(f: FieldAttributes?): Boolean {
        if (f == null) {
            return true
        }
        // proto で定義されたフィールド以外は、JSON化しない
        return !isProtoField(f.name)
    }

    fun isProtoField(name: String): Boolean {
        // filedに _ があると proto で定義されたフィールドとする
        return name.contains("_")
    }
}

val gson = GsonBuilder()
    .setFieldNamingStrategy { it.name.replace("_", "") }
    .setExclusionStrategies(ProtoExclusionStrategy())
    .registerTypeAdapter(
        object : TypeToken<Internal.ProtobufList<DeliveryTimeOuterClass.DeliveryTime>>(){}.type,
        DeliveryTimeListTypeAdapter()
    )
    .create()

gson.fromJson(...)

もっといい方法があったら知りたい。