kotlinx.serialization
を使って、JSONのシリアライズとデシリアライズを試してみたよ。
デシリアライズ&シリアライズ
JSONに対応するdata classを作成する
JSONに対応するdata classには、@Serializable
を設定してあげます。ネストオブジェクトに対応するクラスにも同様に@Serializable
が必要になります。
@Serializable data class Test( val str: String, val int: Int, val double: Double, val nullable: String?, val defaultValue: String = "default", val lists: List<Nest>, ) @Serializable data class Nest( val value: String )
シリアライズを試してみる
コード
Json.encodeToString
でシリアライズが行えます。
val test = Test("str", 100, 9.9, null, "value", listOf(Nest("1"), Nest("2"))) val json = Json.encodeToString(test) println("json = ${json}")
実行結果
ネストオブジェクト構造やnullもシリアライズできていることが確認できます。
json = {"str":"str","int":100,"double":9.9,"nullable":null,"defaultValue":"value","lists":[{"value":"1"},{"value":"2"}]}
デシリアライズを試してみる
Json.decodeFromStringでデシリアライズが行なえます。JSONに対応するdata classは型パラメータとして指定してあげます。
コード
val test = Json.decodeFromString<Test>( """ { "str": "str", "int": 100, "double": 99.99, "nullable": null, "lists": [ {"value": "1"}, {"value": "2"} ] } """.trimIndent() ) println("test = ${test}")
実行結果
ネストオブジェクト構造もデシリアライズできていることが確認できます。 また、デフォルト値をしているdefaultValueプロパティは、JSON内にキーが存在していないため自動的にdefaultで指定されている値が設定されていることも確認できます。
test = Test(str=str, int=100, double=99.99, nullable=null, defaultValue=default, lists=[Nest(value=1), Nest(value=2)])
設定をカスタマイズしてみる
data classに未定義のキーが存在していた場合の動作を変更する
コード
ignoreUnknownKeys
をtrue
に設定することで、data class上に未定義のキーがJSONに存在していても無視して動作するようになります。
なお、この設定値をfalse
(デフォルト値がfalse
)にした場合は、実行時にkotlinx.serialization.json.internal.JsonDecodingException
が送出されます。
(例外メッセージの中で、ignoreUnknownKeys
をtrue
にしなよと親切に教えてくれます。)
@Serializable data class Test(val value: String) fun main() { val json = Json { ignoreUnknownKeys = true } val test = json.decodeFromString<Test>("""{"value": "a", "unknown": null}""") println("test = ${test}") }
実行結果
data class上に存在しているキーのみデシリアライズされていることが確認できます。
test = Test(value=a)
JSONのキー名を指定する
コード
シリアライズ時のキー名は、@SerialName
で指定します。デシリアライズ時のキー名は@JsonNames
で指定します。
@Serializable data class Test( @JsonNames("key") @SerialName("key") val value: String) fun main() { val jsonString = Json.encodeToString(Test("ほげ")) println("json = ${jsonString}") val test = Json.decodeFromString<Test>(jsonString) println("test = ${test}") }
実行結果
プロパティ名ではなくアノテーションで指定したキー名でやり取りできていることが確認できます。
json = {"key":"ほげ"} test = Test(value=ほげ)
Date and Time APIのクラスを使ってみる
デフォルトでは、Date and Time APIのクラス群の変換には対応していないので、Serializerを作ることで対応してあげる必要があります。
Serializerの実装
KSerializer
を実装して、シリアライズとデシリアライズの実装をしてあげます。
この例では、LocalDateを文字列変換してシリアライズし、デシリアライズではその逆を行っています。
object LocalDateSerializer: KSerializer<LocalDate> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): LocalDate { return LocalDate.parse(decoder.decodeString(), DateTimeFormatter.ISO_DATE) } override fun serialize(encoder: Encoder, value: LocalDate) { encoder.encodeString(value.format(DateTimeFormatter.ISO_DATE)) } }
シリアライズ・デシリアライズの実装
Serializerを、serializersModule
に登録してあげます。登録時には、変換対象のKClassとそれに対応するSerializer形式で登録します。
@Serializable data class DateTime( @Contextual val date: LocalDate ) fun main() { val json = Json { serializersModule = SerializersModule { contextual(LocalDate::class, LocalDateSerializer) } } val jsonString = json.encodeToString(DateTime(LocalDate.now())) println("jsonString = ${jsonString}") val dateTime = json.decodeFromString<DateTime>(jsonString) println("dateTime = ${dateTime}") }
実行結果
自作したSerializerを使ったシリアライズとデシリアライズが動いていることが確認できますね。
jsonString = {"date":"2021-06-16"} dateTime = DateTime(date=2021-06-16)