1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin Serialization】子クラスでプロパティ override すると Serializable にできない

Posted at

はじめに

Kotlin Serialization の継承クラスの挙動を確認しようとしたところ、継承先のクラスで Serializable を有効にできない事象が発生しました。

@Serializable
open class Parent {
    open val name: String = "Parent"
}

@Serializable // <-- NG
class Child : Parent() {
    override val name: String = "Child"
}
Error when overriding property: serializable class has duplicate serial name of property 'name', either in the class itself or its supertypes

これは既知の問題で、すでに2020年に報告されていますが解決していないようです。

発生環境

  • kotlin 2.0.21
  • kotlinx-serialization-json 1.7.2

なぜなのか

この現象の解決が困難であることについて考察してみました。

プロパティ override はどのように実現されているか

ます、プロパティ override の成り立ちはどのようになっているかというと、フィールド getter メソッドの override によって実現されています。
これは Java には無い機能ですが、もし Java で記述すると下記のようになります。

public class Parent {
    private final String name = "Parent";

    public String getName() {
        return name;
    }
}
public class Child extends Parent {
    private final String name = "Child";

    @Override
    public String getName() {
        return name;
    }
}

serializable class has duplicate serial name of property 'name' なのが明確になったかと思います。

他のコンバータの挙動

Gson と Moshi で以下の挙動を確認してみます。

  • Parent インスタンス化したオブジェクトを Json 文字列化して、オブジェクトへ戻す
  • Child インスタンス化したオブジェクトを Json 文字列化して、オブジェクトへ戻す
  • 変換処理後のダウンキャスト、ポリモーフィズムの確認

Gson の場合

  • gson 2.11.0
    fun testParentGsonParse() {
        val gson = Gson()

        val parent = Parent()
        val json = gson.toJson(parent)
        println(json) // {"name":"Parent"}

        val parsedParent = gson.fromJson(json, Parent::class.java)
        println(parsedParent.name) // Parent
    }
    fun testChildGsonParse() {
        val gson = Gson()

        val child = Child()
        val json = gson.toJson(child) // フィールド名の重複で IllegalArgumentException
        println(json)
    }

Gson は同名のプロパティ名の問題を解決できない。

Moshi の場合

  • ksp 2.0.21-1.0.28
  • moshi-kotlin 1.15.2
    fun testParentMoshiParse() {
        val moshi = Moshi.Builder().build()
        val adapter = moshi.adapter<Parent>(Parent::class.java)

        val parent = Parent()
        val json = adapter.toJson(parent)
        println(json) // {}

        val parsedParent = adapter.fromJson(json)
        println(parsedParent?.name) // Parent
    }
    fun testChildMoshiParse() {
        val moshi = Moshi.Builder().build()
        val adapter = moshi.adapter<Child>(Child::class.java)

        val child = Child()
        val json = adapter.toJson(child)
        println(json) // {}

        val parsedChild = adapter.fromJson(json)
        println(parsedChild?.name) // Child
    }

同名のプロパティ名があっても失敗しない。
デフォルト値の不変プロパティは json に出力されない。

元がどのクラスでインスタンス化したかは関係なく、json から復元したオブジェクトは Adapter のクラス情報に基づいている。
Child インスタンスを json 化して、Parent の Adapter で復元すると Parent としてインスタンス化されたオブジェクトになるため、Child に ダウンキャストすることはできない。

    fun testChildParentMoshiPolymorphism() {
        val moshi = Moshi.Builder().build()
        val childAdapter = moshi.adapter<Child>(Child::class.java)

        val child = Child()
        val json = childAdapter.toJson(child)
        println(json) // {}

        val parsedByChild = childAdapter.fromJson(json)
        println(parsedByChild?.name) // Child

        val childPolymorphic = parsedByChild as? Parent
        println(childPolymorphic?.name) // Child

        // Parent型のAdapterで復元する
        val parentAdapter = moshi.adapter<Parent>(Parent::class.java)
        val parsedByParent = parentAdapter.fromJson(json)
        println(parsedByParent?.name) // Parent

        val parentPolymorphic = parsedByParent as? Child
        println(parentPolymorphic?.name) // null ダウンキャスト不可
    }
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?