ライブラリ提供クラスのカスタムSerializer(Primitive版)

投稿日:  更新日:

Kotlin serializationでJSONをパース(JSONの記述⇔クラスのオブジェクト)する場合に、一般クラスは未対応です。

例えば、データクラス(コンストラクタの引数でプロパティを指定するクラス)以外の、ユーザ定義のクラスはパース出来ません。また、ライブラリ提供のクラスもパース出来ません。

対応させるためには、そのクラスのカスタムSerializerを作成します。

そして、相互変換する方法をプログラマー側で定義します。

カスタムSerializer(ライブラリ提供クラス、Primitive版)の作成方法をまとめます。

※環境:Android Studio Meerkat | 2024.3.1
    Kotlin 2.0.0
    Kotlin serialization json 1.7.1

スポンサーリンク

標準で対応する型、しない型

Kotlin serializationでJSONをパース(JSONの記述⇔クラスのオブジェクト)する場合に、標準で対応する型は次の通りです。

  • ・Int,Long,Float,Double,Booleanなど(プリミティブ型)
  • ・String(文字列)※プリミティブ型へ含める場合もある
  • ・Array(配列)
  • ・List,Map,Set(コレクション型)
  • ・Enum(列挙型)
  • ・データクラス

上記以外の一般クラス(ユーザ定義のクラス、ライブラリ提供のクラス)は未対応です。

例えば、次のようなクラスは標準でパース出来ません。

public final class LocalTime
    ...
    private final byte hour;
    private final byte minute;
    private final byte second;
    private final int nano;
	...
	@Override
    public String toString() {
        StringBuilder buf = new StringBuilder(18);
        int hourValue = hour;
        int minuteValue = minute;
        int secondValue = second;
        int nanoValue = nano;
        buf.append(hourValue < 10 ? "0" : "").append(hourValue)
            .append(minuteValue < 10 ? ":0" : ":").append(minuteValue);
        if (secondValue > 0 || nanoValue > 0) {
            buf.append(secondValue < 10 ? ":0" : ":").append(secondValue);
            if (nanoValue > 0) {
                buf.append('.');
                if (nanoValue % 1000_000 == 0) {
                    buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1));
                } else if (nanoValue % 1000 == 0) {
                    buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1));
                } else {
                    buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1));
                }
            }
        }
        return buf.toString();
    }
	...
}

ライブラリ提供クラスをパースするには、そのクラスのカスタムSerializerが必要です。
※LocalTimeについては「現在の日付と時刻を取得」を参照

スポンサーリンク

Serializerの種類

Serializerは、表のような種類(書き方)があります。

特徴シリアル化の構成
PrimitivePrimitive型(Stringを含む)限定PrimitiveSerialDescriptor
Delegating定義済みのSerializerへ委任するSerialDescriptor
Surrogate代理クラスを立て(作成し)、
そのクラスのSerializerに代行させる
SerialDescriptor
Handwritten全ての要素のシリアル化を細かく定義buildClassSerialDescriptor

この記事で取り上げるのは、Primitive版です。
※種類は「Custom serializers」に紹介されています。

スポンサーリンク

カスタムSerializerの作成

Serializer(KSerializerインターフェース)は「Kotlin serializerがJSONをパースする際に呼び出される関数」を持ちます。

object XXXSerializer : KSerializer<XXX> {
    override val descriptor: SerialDescriptor
        get() = TODO("識別子を実装")

    override fun serialize(encoder: Encoder, value: XXX) {
        // Json.encodeToString( )で呼び出される
        // Obj->JSONの変換を実装
    }

    override fun deserialize(decoder: Decoder): XXX {
        // Json.decodeFromString( )で呼び出される
        // JSON->Objの変換を実装
        return XXX()
    }
}
 KSerializer#serialize( ) 

「クラスのオブジェクト⇒JSONの記述」で呼び出し

 KSerializer#deserialize( ) 

「JSONの記述⇒クラスのオブジェクト」で呼び出し

ですので、カスタムSerializerは、このKSerializerインターフェースを継承して、相互変換の方法をserialiseとdesirializeへ実装します。

object LocalTimeAsLongSerializer : KSerializer<LocalTime> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor(
            "com.example.LocalTimeAsLongSerializer", // Serializer名(一意に識別)
            PrimitiveKind.LONG                       // シリアル化の構成
        )

    // Obj->JSONの変換
    override fun serialize(encoder: Encoder, value: LocalTime) {
        val _timeInNanos = value.toNanoOfDay()
        encoder.encodeLong(_timeInNanos)
    }

    // JSON->Objの変換
    override fun deserialize(decoder: Decoder): LocalTime {
        val _timeInNanos = decoder.decodeLong()
       return LocalTime.ofNanoOfDay(_timeInNanos)
    }
}
スポンサーリンク

SerialDescriptor(シリアル化の構成)

SerialDescriptorでシリアル化の構成を定義しています。つまり、「JSONでどのように表現するか!」です。

ここでは、対象クラスをプリミティブ型のLong(PrimitiveKind.LONG)で表現します。

定義にPrimitiveSerialDescriptor( )を使います。

    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor(
            "com.example.LocalTimeAsLongSerializer", // Serializer名(一意に識別)
            PrimitiveKind.LONG                       // シリアル化の構成
        )

ちなにみ、次にあげるLong以外のプリミティブ型が用意されています。

 public sealed class PrimitiveKind : SerialKind() {
    public object BOOLEAN : PrimitiveKind()
    public object BYTE : PrimitiveKind()
    public object CHAR : PrimitiveKind()
    public object SHORT : PrimitiveKind()
    public object INT : PrimitiveKind()
    public object LONG : PrimitiveKind()
    public object FLOAT : PrimitiveKind()
    public object DOUBLE : PrimitiveKind()
    public object STRING : PrimitiveKind()
 }

deserialize(JSON⇒Objの変換方法)

Decoder#decodeLong( )によりLong(value)を取り出し、Long値(_timeInNanos)からオブジェクトを作ります。

    override fun deserialize(decoder: Decoder): LocalTime {
        val _timeInNanos = decoder.decodeLong()
       return LocalTime.ofNanoOfDay(_timeInNanos)
    }

serialize(Obj⇒JSONの変換方法)

オブジェクトからLong値を取り出し、Encoder#encodeLong( )によりJSONへLong(value)として書き出します。

    override fun serialize(encoder: Encoder, value: LocalTime) {
        val _timeInNanos = value.toNanoOfDay()
        encoder.encodeLong(_timeInNanos)
    }
スポンサーリンク

カスタムSerializerの使用

カスタムSerializerはアノテーション@Serializableの引数で登録します。

Android Studioはビルドをする際に、@Serializable付きのクラスを認識して、対象クラスのJavaバイナリへ、引数のカスタムSerializerクラスを埋め込みます。

@Serializable
data class Person(
    val id: Int,
    val name: String,
    @SerialName("position") val post: String,  // JSONのキーに別名を定義
    @Serializable(LocalTimeAsLongSerializer::class)
    val times: LocalTime
)

JSON⇒Obj変換

Json#decodeFromStringを呼び出します。

内部で対象クラスのdeserialize( )が呼び出されます。

private val totalNano = (8 * 3600 + 55 * 60 + 22) * 1000_000_000L

fun json2obj_LibClass() {
    val _jPerson = """
            {
                "id":1,
                "name":"Android",
                "position":"Manager",
                "times":${totalNano}
            }
            """.replace("\\s+".toRegex(), "") // 空白の削除
    val _oPerson = Json.decodeFromString<Person>(_jPerson)

    Log.i(TAG, "(Serialization) Person Obj  = ${_oPerson}")
}
(Serialization) Person Obj  = Person(id=1, name=Android, post=Manager, times=08:55:22)

Obj⇒JSON変換

Json#encodeToStringを呼び出します。

内部で対象クラスのserialize( )が呼び出されます。

    val _oPerson = Person(
        id = 2, name = "Droid", post = "Staff",
        times = LocalTime.of(12, 2, 0)
    )
    val _jPerson = Json.encodeToString(_oPerson)

    Log.i(TAG, "(Serialization) Person Json = ${_jPerson}")
(Serialization) Person Json = {"id":2,"name":"Droid","position":"Staff","times":43320000000000}
スポンサーリンク

関連記事:

Kotlinで利用可能なJSONライブラリーには「GSON, Jackson, Moshi, Kotlin serializationなど」があります。 始めの3つはJavaがベースです。その中のMoshiは、拡張機能によりKotlinとの親和性が高められています。 Kotlin serializationはJSON以外(Protobuf, CBOR, Hocon, Properties )のフォーマットも扱えます。フォーマットを扱うというよりも、シリアル化の機能を重視したライブラリーのようです。 プログラム間でデータを受け渡す際に用いるのであれば、Kotlin serializationが最も適しているかも知れません。 Kotlin serializationに興味を引かれますが、後の機会に置いといて... 今回は、Moshiについて、まとめます。 ※環境:Android Studio Ladybug Feature Drop | 2024.2.2     Kotlin 2.0.0     Moshi 1.15.2 ...
MoshiでJSONをパース(JSONの記述⇔データクラスのオブジェクト)する場合に、一般クラスは未対応です。 例えば、データクラス以外の、ユーザ定義のクラスはパース出来ません。 対応させるためには、そのクラスのカスタムJsonAdapterを作成します。 そして、相互変換する方法をプログラマー側で定義します。 カスタムJsonAdapterの作成方法をまとめます。 ※環境:Android Studio Ladybug Feature Drop | 2024.2.2     Kotlin 2.0.0     Moshi 1.15.2 ...
RetrofitはRESTに準拠したWeb APIです。 このRetrofitとMoshiを使って、Webサービスへアクセスする方法を、まとめました。 サンプルはAndroidのコードラボと同じ内容です。私はコードラボが理解し難かったので、不要な部分をそぎ落として、Retrofitに的を絞って説明しています。 ※環境:Android Studio Ladybug Feature Drop | 2024.2.2     Kotlin 2.0.0     Moshi 1.15.2     Retrofit2 2.11.0     Coil 3.1.0 ...
Kotlinで利用可能なJSONライブラリーには「GSON, Jackson, Moshi, Kotlin serializationなど」があります。 Kotlin Serializationは、Kotlinがベース(前の3つはJavaがベース)です。 2020.10にVer 1.0.0がリリースされており、比較的新しいライブラリです。 今回は、Kotlin serializationについて、まとめます。 ※環境:Android Studio Ladybug Feature Drop | 2024.2.2 Patch 1     Kotlin 2.0.0     Kotlin serialization json 1.7.1 ...
Kotlin serializationでJSONをパース(JSONの記述⇔クラスのオブジェクト)する場合に、一般クラスは未対応です。 例えば、データクラス(コンストラクタの引数でプロパティを指定するクラス)以外の、ユーザ定義のクラスはパース出来ません。また、ライブラリ提供のクラスもパース出来ません。 対応させるためには、そのクラスのカスタムSerializerを作成します。 そして、相互変換する方法をプログラマー側で定義します。 カスタムSerializer(ユーザ定義クラス、Primitive版)の作成方法をまとめます。 ※環境:Android Studio Meerkat | 2024.3.1     Kotlin 2.0.0     Kotlin serialization json 1.7.1 ...
Kotlin serializationでJSONをパース(JSONの記述⇔クラスのオブジェクト)する場合に、一般クラスは未対応です。 例えば、データクラス(コンストラクタの引数でプロパティを指定するクラス)以外の、ユーザ定義のクラスはパース出来ません。また、ライブラリ提供のクラスもパース出来ません。 対応させるためには、そのクラスのカスタムSerializerを作成します。 そして、相互変換する方法をプログラマー側で定義します。 カスタムSerializer(ユーザ定義クラス、Delegating版)の作成方法をまとめます。 ※環境:Android Studio Meerkat | 2024.3.1     Kotlin 2.0.0     Kotlin serialization json 1.7.1 ...
Kotlin serializationでJSONをパース(JSONの記述⇔クラスのオブジェクト)する場合に、一般クラスは未対応です。 例えば、データクラス(コンストラクタの引数でプロパティを指定するクラス)以外の、ユーザ定義のクラスはパース出来ません。また、ライブラリ提供のクラスもパース出来ません。 対応させるためには、そのクラスのカスタムSerializerを作成します。 そして、相互変換する方法をプログラマー側で定義します。 カスタムSerializer(ライブラリ提供クラス、Surrogate版)の作成方法をまとめます。 ※環境:Android Studio Meerkat | 2024.3.1     Kotlin 2.0.0     Kotlin serialization json 1.7.1 ...
Kotlin serializationでJSONをパース(JSONの記述⇔クラスのオブジェクト)する場合に、一般クラスは未対応です。 例えば、データクラス(コンストラクタの引数でプロパティを指定するクラス)以外の、ユーザ定義のクラスはパース出来ません。また、ライブラリ提供のクラスもパース出来ません。 対応させるためには、そのクラスのカスタムSerializerを作成します。 そして、相互変換する方法をプログラマー側で定義します。 カスタムSerializer(ユーザ定義クラス、Handwritten版)の作成方法をまとめます。 ※環境:Android Studio Meerkat | 2024.3.1     Kotlin 2.0.0     Kotlin serialization json 1.7.1 ...
スポンサーリンク