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の記述⇔クラスのオブジェクト)する場合に、標準で対応する型は次の通りです。
- ・Int,Long,Float,Double,Booleanなど(プリミティブ型)
- ・String(文字列)※プリミティブ型へ含める場合もある
- ・Array(配列)
- ・List,Map,Set(コレクション型)
- ・Enum(列挙型)
- ・データクラス
上記以外の一般クラス(ユーザ定義のクラス、ライブラリ提供のクラス)は未対応です。
例えば、次のようなクラスは標準でパース出来ません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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は、表のような種類(書き方)があります。
特徴 | シリアル化の構成 | |
---|---|---|
Primitive | Primitive型(Stringを含む)限定 | PrimitiveSerialDescriptor |
Delegating | 定義済みのSerializerへ委任する | SerialDescriptor |
Surrogate | 代理クラスを立て(作成し)、 そのクラスのSerializerに代行させる | SerialDescriptor |
Handwritten | 全ての要素のシリアル化を細かく定義 | buildClassSerialDescriptor |
この記事で取り上げるのは、Surrogate版です。
※種類は「Custom serializers」に紹介されています。
カスタムSerializerの作成
Serializer(KSerializerインターフェース)は「Kotlin serializerがJSONをパースする際に呼び出される関数」を持ちます。
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() } } |
「クラスのオブジェクト⇒JSONの記述」で呼び出し
KSerializer#deserialize( )「JSONの記述⇒クラスのオブジェクト」で呼び出し
ですので、カスタムSerializerは、このKSerializerインターフェースを継承して、相互変換の方法をserialiseとdesirializeへ実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | @Serializable private data class Surrogate(val hh: Int, val mm: Int, val ss: Int) object LocalTimeSurrogateSerializer : KSerializer<LocalTime> { override val descriptor: SerialDescriptor = SerialDescriptor( "com.example.LocalTimeSurrogateSerializer" , // Serializer名(一意に識別) Surrogate.serializer().descriptor // シリアル化の構成 ) // Obj->JSONの変換 override fun serialize(encoder: Encoder, value: LocalTime) { val _surrogate = Surrogate(value.hour, value.minute, value.second) encoder.encodeSerializableValue( Surrogate.serializer(), _surrogate ) } // JSON->Objの変換 override fun deserialize(decoder: Decoder): LocalTime { val _surrogate = decoder.decodeSerializableValue( Surrogate.serializer() ) val _hh = _surrogate.hh val _mm = _surrogate.mm val _ss = _surrogate.ss return LocalTime.of(_hh, _mm, _ss) } } |
代理クラス
代理クラス(@Serializable付き)を作成し、Serializerを自動生成します。
1 2 | @Serializable private data class Surrogate(val hh: Int, val mm: Int, val ss: Int) |
そして、そのSerializerへ相互変換の処理を代行させます。
SerialDescriptor(シリアル化の構成)
SerialDescriptorでシリアル化の構成を定義しています。つまり、「JSONでどのように表現するか!」です。
ここでは、対象クラスを代理クラスのシリアル化の構成で表現します。
定義にSerialDescriptor( )を使います。
1 2 3 4 5 | override val descriptor: SerialDescriptor = SerialDescriptor( "com.example.LocalTimeSurrogateSerializer" , // Serializer名(一意に識別) Surrogate.serializer().descriptor // シリアル化の構成 ) |
deserialize(JSON⇒Objの変換方法)
Decoder#decodeSerializableValue( )を使って、代理クラスのSerializerに代行を依頼すると、代理クラスのオブジェクトが返されます。
後は、代理クラス⇒対象クラスの変換を行います。
1 2 3 4 5 6 7 8 9 | override fun deserialize(decoder: Decoder): LocalTime { val _surrogate = decoder.decodeSerializableValue( Surrogate.serializer() ) val _hh = _surrogate.hh val _mm = _surrogate.mm val _ss = _surrogate.ss return LocalTime.of(_hh, _mm, _ss) } |
serialize(Obj⇒JSONの変換方法)
対象クラス⇒代理クラスの変換を行います。
後は、Encode#encodeSerializableValue( )を使って、代理クラスのSerializerに代行を依頼します。
1 2 3 4 5 6 7 | override fun serialize(encoder: Encoder, value: LocalTime) { val _surrogate = Surrogate(value.hour, value.minute, value.second) encoder.encodeSerializableValue( Surrogate.serializer(), _surrogate ) } |
カスタムSerializerの使用
カスタムSerializerはアノテーション@Serializableの引数で登録します。
Android Studioはビルドをする際に、@Serializable付きのクラスを認識して、対象クラスのJavaバイナリへ、引数のカスタムSerializerクラスを埋め込みます。
1 2 3 4 5 6 7 8 | @Serializable data class Person( val id: Int, val name: String, @SerialName ( "position" ) val post: String, // JSONのキーに別名を定義 @Serializable (LocalTimeSurrogateSerializer:: class ) val times: LocalTime ) |
JSON⇒Obj変換
Json#decodeFromStringを呼び出します。
内部で対象クラスのdeserialize( )が呼び出されます。
1 2 3 4 5 6 7 8 9 10 11 | val _jPerson = "" " { "id" : 1 , "name" : "Android" , "position" : "Manager" , "times" :{ "hh" : 8 , "mm" : 55 , "ss" : 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=08:55:22)
Obj⇒JSON変換
Json#encodeToStringを呼び出します。
内部で対象クラスのserialize( )が呼び出されます。
1 2 3 4 5 6 7 | 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":{"hh":12,"mm":2,"ss":0}}
関連記事: