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の記述⇔クラスのオブジェクト)する場合に、標準で対応する型は次の通りです。
- ・Int,Long,Float,Double,Booleanなど(プリミティブ型)
- ・String(文字列)※プリミティブ型へ含める場合もある
- ・Array(配列)
- ・List,Map,Set(コレクション型)
- ・Enum(列挙型)
- ・データクラス
上記以外の一般クラス(ユーザ定義のクラス、ライブラリ提供のクラス)は未対応です。
例えば、次のようなクラスは標準でパース出来ません。
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は、表のような種類(書き方)があります。
特徴 | シリアル化の構成 | |
---|---|---|
Primitive | Primitive型(Stringを含む)限定 | PrimitiveSerialDescriptor |
Delegating | 定義済みのSerializerへ委任する | SerialDescriptor |
Surrogate | 代理クラスを立て(作成し)、 そのクラスのSerializerに代行させる | SerialDescriptor |
Handwritten | 全ての要素のシリアル化を細かく定義 | buildClassSerialDescriptor |
この記事で取り上げるのは、Delegating版です。
※種類は「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() } }
「クラスのオブジェクト⇒JSONの記述」で呼び出し
KSerializer#deserialize( )「JSONの記述⇒クラスのオブジェクト」で呼び出し
ですので、カスタムSerializerは、このKSerializerインターフェースを継承して、相互変換の方法をserialiseとdesirializeへ実装します。
object LogTimeAsIntArraySerializer : KSerializer<LogTime> { private val intArraySeri = IntArraySerializer() override val descriptor: SerialDescriptor = SerialDescriptor( "com.example.LogTimeAsIntArraySerializer", // Serializer名(一意に識別) intArraySeri.descriptor // シリアル化の構成 ) // Obj->JSONの変換 override fun serialize(encoder: Encoder, value: LogTime) { val _secArray = intArrayOf(value.hh, value.mm, value.ss) encoder.encodeSerializableValue(intArraySeri, _secArray) } // JSON->Objの変換 override fun deserialize(decoder: Decoder): LogTime { val _secArray = decoder.decodeSerializableValue(intArraySeri) val _hh = _secArray[0] val _mm = _secArray[1] val _ss = _secArray[2] return LogTime(_hh, _mm, _ss) } }
SerialDescriptor(シリアル化の構成)
SerialDescriptorでシリアル化の構成を定義しています。つまり、「JSONでどのように表現するか!」です。
Serializationライブラリに定義済みのSerializerが登録されています。そのSerializerへ相互変換の処理を委任します。
ここでは、対象クラスを配列のIntArray(定義済みのSerializer:IntArraySerializer)で表現します。
定義にSerialDescriptor( )を使います。
private val intArraySeri = IntArraySerializer() override val descriptor: SerialDescriptor = SerialDescriptor( "com.example.LogTimeAsIntArraySerializer", // Serializer名(一意に識別) intArraySeri.descriptor // シリアル化の構成 )
ちなにみ、次にあげる定義済みのSerializerが用意されています。※ここにあげたSerializerは一部です。
... public fun Int.Companion.serializer(): KSerializer<Int> = IntSerializer public fun IntArraySerializer(): KSerializer<IntArray> = IntArraySerializer public fun UIntArraySerializer(): KSerializer<UIntArray> = UIntArraySerializer public fun Long.Companion.serializer(): KSerializer<Long> = LongSerializer public fun LongArraySerializer(): KSerializer<LongArray> = LongArraySerializer public fun ULongArraySerializer(): KSerializer<ULongArray> = ULongArraySerializer ... public fun <A, B, C> TripleSerializer( aSerializer: KSerializer<A>, bSerializer: KSerializer<B>, cSerializer: KSerializer<C> ): KSerializer<Triple<A, B, C>> = kotlinx.serialization.internal.TripleSerializer(aSerializer, bSerializer, cSerializer) ...
deserialize(JSON⇒Objの変換方法)
Decoder#decodeSerializableValue( )の引数に指定したSerializerへ、変換を委任します。
private val intArraySeri = IntArraySerializer() override fun deserialize(decoder: Decoder): LogTime { val _secArray = decoder.decodeSerializableValue(intArraySeri) val _hh = _secArray[0] val _mm = _secArray[1] val _ss = _secArray[2] return LogTime(_hh, _mm, _ss) }
serialize(Obj⇒JSONの変換方法)
Decoder#encodeSerializableValue( )の引数に指定したSerializerへ、変換を委任します。
private val intArraySeri = IntArraySerializer() override fun serialize(encoder: Encoder, value: LogTime) { val _secArray = intArrayOf(value.hh, value.mm, value.ss) encoder.encodeSerializableValue(intArraySeri, _secArray) }
カスタムSerializerの使用
カスタムSerializerはアノテーション@Serializableの引数で登録します。
Android Studioはビルドをする際に、@Serializable付きのクラスを認識して、対象クラスのJavaバイナリへ、引数のカスタムSerializerクラスを埋め込みます。
@Serializable(LogTimeAsIntArraySerializer::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( )が呼び出されます。
val _jPerson = """ { "id":1, "name":"Android", "position":"Manager", "times":[8,55,22] } """.trimIndent() 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( )が呼び出されます。
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]}
関連記事: