ユーザ定義クラスのカスタム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(列挙型)
  • ・データクラス

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

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

ユーザー定義クラス
1
2
3
4
5
6
7
class LogTime(hour: Int, min: Int, sec: Int) {
    val hh: Int = hour
    val mm: Int = min
    val ss: Int = sec
 
    override fun toString() = "%s:%s:%s".format(hh, mm, ss)
}

一般クラスをパースするには、そのクラスのカスタムSerializerが必要です。

スポンサーリンク

Serializerの種類

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

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

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

スポンサーリンク

カスタムSerializerの作成

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

Serializerクラスのサンプル(KSerializer.ktより抜粋)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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へ実装します。

カスタムSerializer(Primitive版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object LogTimeAsStringSerializer : KSerializer<LogTime> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor(
            "com.example.LogTimeAsStringSerializer", // Serializer名(一意に識別)
            PrimitiveKind.STRING                     // シリアル化の構成
        )
 
    // Obj->JSONの変換
    override fun serialize(encoder: Encoder, value: LogTime) {
        val _timeStr = "%s:%s:%s".format(value.hh, value.mm, value.ss)
        encoder.encodeString(_timeStr)
    }
 
    // JSON->Objの変換
    override fun deserialize(decoder: Decoder): LogTime {
        val _value = decoder.decodeString()
        val _hhmmss = _value.split(":")
        val _hh = _hhmmss[0].toInt()
        val _mm = _hhmmss[1].toInt()
        val _ss = _hhmmss[2].toInt()
       return LogTime(_hh, _mm, _ss)
    }
}
スポンサーリンク

SerialDescriptor(シリアル化の構成)

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

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

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

シリアル化の構成を定義
1
2
3
4
5
override val descriptor: SerialDescriptor =
    PrimitiveSerialDescriptor(
        "com.example.LogTimeAsStringSerializer", // Serializer名(一意に識別)
        PrimitiveKind.STRING                     // シリアル化の構成
    )

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

PrimitiveKind.XXXの種類(SerialKinds.ktより抜粋)
1
2
3
4
5
6
7
8
9
10
11
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#decodeString( )により文字列(value)を取り出し、文字列より得られた値(_hh,_mm,_ss)からオブジェクトを作ります。

JSON⇒Objの変換方法
1
2
3
4
5
6
7
8
override fun deserialize(decoder: Decoder): LogTime {
    val _value = decoder.decodeString()
    val _hhmmss = _value.split(":")
    val _hh = _hhmmss[0].toInt()
    val _mm = _hhmmss[1].toInt()
    val _ss = _hhmmss[2].toInt()
   return LogTime(_hh, _mm, _ss)
}

serialize(Obj⇒JSONの変換方法)

オブジェクトを文字列に変換し、Encoder#encodeString( )によりJSONへ文字列(value)として書き出します。

Obj⇒JSONの変換方法
1
2
3
4
override fun serialize(encoder: Encoder, value: LogTime) {
    val _timeStr = "%s:%s:%s".format(value.hh, value.mm, value.ss)
    encoder.encodeString(_timeStr)
}
スポンサーリンク

カスタムSerializerの使用

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

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

ユーザ定義クラスのプロパティを持つ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Serializable(LogTimeJsonSerializer::class)
class LogTime(hour: Int, min: Int, sec: Int) {
    val hh: Int = hour
    val mm: Int = min
    val ss: Int = sec
 
    override fun toString() = "%s:%s:%S".format(hh, mm, ss)
}
 
@Serializable
data class Person(
    val id: Int,
    val name: String,
    @SerialName("position") val post: String,  // JSONのキーに別名を定義
    val times: LogTime
)

JSON⇒Obj変換

Json#decodeFromStringを呼び出します。

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

JSON⇒Obj変換
1
2
3
4
5
6
7
8
9
10
11
val _jPerson = """
        {
            "id":1,
            "name":"Android",
            "position":"Manager",
            "times":"8:55:22"
        }
        """.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=8:55:22)

Obj⇒JSON変換

Json#encodeToStringを呼び出します。

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

Obj⇒JSON変換
1
2
3
4
5
6
7
val _oPerson = Person(
    id = 2, name = "Droid", post = "Staff",
    times = LogTime(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":"12:2:0"}
スポンサーリンク

関連記事:

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 ...
RetrofitはRESTに準拠したWeb APIです。 このRetrofitとKotlin Serializationを使って、Webサービスへアクセスする方法を、まとめました。 サンプルはAndroidのコードラボと同じ内容です。私はコードラボが理解し難かったので、不要な部分をそぎ落として、Retrofitに的を絞って説明しています。 ※環境:Android Studio Meerkat | 2024.3.1     Kotlin 2.0.0     Kotlin serialization json 1.7.1     Retrofit2 2.11.0     Coil 3.1.0 ...
スポンサーリンク