web-dev-qa-db-ja.com

Kotlinの密封されたクラスをシリアライズ/デシリアライズする方法は?

私は次の密封されたクラスを持っています:

sealed class ViewModel {

  data class Loaded(val value : String) : ViewModel()
  object Loading : ViewModel()

}

ViewModelクラスのインスタンスをシリアル化/逆シリアル化するにはどうすればよいですか、JSON形式との間で言いましょう。

Gensonシリアライザー/デシリアライザーライブラリを使用しようとしました-Kotlinデータクラスを処理でき、多相型をサポートすることもできます(たとえば、メタデータを使用して具象型を指定する)。

ただし、Kotlin object型はパブリックコンストラクターがないシングルトンであるため、ライブラリは失敗します。カスタムGensonコンバーターを作成して処理できると思いますが、もっと簡単な方法はありますか?

8

Gensonに適切に接続するために、カスタムコンバーターとファクトリーを実装することになりました。

Gensonのメタデータ規約を使用して、オブジェクトを次のように表します。

{ 
  "@class": "com.example.ViewModel.Loading" 
}

コンバーターはseClassMetadataフラグが設定されていると想定しているため、シリアル化では空のオブジェクトをマークするだけで済みます。逆シリアル化では、メタデータからクラス名を解決し、それをロードして、objectInstanceを取得します。

object KotlinObjectConverter : Converter<Any> {

override fun serialize(objectData: Any, writer: ObjectWriter, ctx: Context) {
    with(writer) {
        // just empty JSON object, class name will be automatically added as metadata
        beginObject()
        endObject()
    }
}

override fun deserialize(reader: ObjectReader, ctx: Context): Any? =
    Class.forName(reader.nextObjectMetadata().metadata("class"))
        .kotlin.objectInstance
        .also { reader.endObject() }
}

このコンバーターが実際のobjectsにのみ適用されることを確認するために、Gensonにいつ使用し、いつデフォルトの実装にフォールバックするかを指示するファクトリーを使用して登録します。

object KotlinConverterFactory : Factory<Converter<Any>> {

    override fun create(type: Type, genson: Genson): Converter<Any>? =
        if (TypeUtil.getRawClass(type).kotlin.objectInstance != null) KotlinObjectConverter
        else null

}

このファクトリーを使用して、ビルダーを介してGensonを構成できます。

GensonBuilder()
        .withConverterFactory(KotlinConverterFactory)
        .useClassMetadata(true) // required to add metadata during serialization
        // some other properties
        .create()

チェーン化されたコンバーター機能を使用すると、コードはおそらくもっと良くなるかもしれませんが、まだチェックする時間はありませんでした。

3

あなたはおそらくカスタムシリアライザの作成について正しいでしょう。

Jackson ライブラリとKotlinを使用してクラスをシリアル化および逆シリアル化しようとしました。

これらは、JacksonのMaven依存関係です。

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.8.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.8</version>
</dependency>

このライブラリを使用して、追加のカスタムシリアライザーなしでシールされたクラスをJSONにシリアル化できますが、非シリアル化にはカスタムデシリアライザーが必要です。

以下は、封印されたクラスをシリアライズおよびデシリアライズするために使用したおもちゃのコードです。

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule

sealed class ViewModel {
    data class Loaded(val value: String) : ViewModel()
    object Loading : ViewModel()
}

// Custom serializer
class ViewModelDeserializer : JsonDeserializer<ViewModel>() {
    override fun deserialize(jp: JsonParser?, p1: DeserializationContext?): ViewModel {
        val node: JsonNode? = jp?.getCodec()?.readTree(jp)
        val value = node?.get("value")
        return if (value != null) ViewModel.Loaded(value.asText()) else ViewModel.Loading
    }
}

fun main(args: Array<String>) {
    val m = createCustomMapper()
    val ser1 = m.writeValueAsString(ViewModel.Loading)
    println(ser1)
    val ser2 = m.writeValueAsString(ViewModel.Loaded("test"))
    println(ser2)
    val deserialized1 = m.readValue(ser1, ViewModel::class.Java)
    val deserialized2 = m.readValue(ser2, ViewModel::class.Java)
    println(deserialized1)
    println(deserialized2)
}

// Using mapper with custom serializer
private fun createCustomMapper(): ObjectMapper {
    val m = ObjectMapper()
    val sm = SimpleModule()
    sm.addDeserializer(ViewModel::class.Java, ViewModelDeserializer())
    m.registerModule(sm)
    return m
}

このコードを実行すると、出力は次のようになります。

{}
{"value":"test"}
ViewModel$Loading@1753acfe
Loaded(value=test)
4
gil.fernandes

最近、同様の問題が発生しました(ただし、GensonではなくJacksonを使用しています)。

私が以下を持っていると仮定します:

sealed class Parent(val name: String)

object ChildOne : Parent("ValOne")
object ChildTwo : Parent("ValTwo")

次に、JsonCreator関数をシールクラスに追加します。

sealed class Parent(val name: String) {

    private companion object {
        @JsonCreator
        @JvmStatic
        fun findBySimpleClassName(simpleName: String): Parent? {
            return Parent::class.sealedSubclasses.first {
                it.simpleName == simpleName
            }.objectInstance
        }
    }
}

これで、jsonプロパティでChildOneまたはChildTwokeyとして使用して逆シリアル化できます。

3
SergioLeone