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は、表のような種類(書き方)があります。
| 特徴 | シリアル化の構成 | |
|---|---|---|
| Primitive | Primitive型(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()
}
}
「クラスのオブジェクト⇒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}
関連記事:
