KotlinSerializationのJsonパースの際に内部では固有の型として扱いたい。
イメージでは特定の動作を示すValueがStringやIntが送られてくるが、
内部ではその値がどのような挙動を示すか宣言から示したいなと思い調べた。
実装
@Serializable
data class Data(
val status : Status?
)
今回利用するデータクラス。
Status型として受け取れるようにします。
今回は0,1...などパラメータがInt型で送られてくると仮定します。
@Serializable(with = Status.Companion.Serializer::class)
enum class Status : StatusEnum{
OFF {
override val param: Int = 0
override val bool: Boolean = false
},
ON {
override val param: Int = 1
override val bool: Boolean = true
}
;
companion object {
object Serializer : StatusEnum.Companion.Serializer<Status>(Status::class)
}
}
interface StatusEnum {
val param: Int
val bool: Boolean
companion object {
abstract class Serializer<T:Enum<T>>(
private val kClass: KClass<T>
) : KSerializer<T?> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor(this::class.jvmName, PrimitiveKind.INT)
override fun serialize(encoder: Encoder, value: T?) {
when(val label = (value as StatusEnum).param){
else -> encoder.encodeInt(label)
}
}
override fun deserialize(decoder: Decoder): T? {
val decode = decoder.decodeInt()
return kClass.java.enumConstants?.firstOrNull { (it as StatusEnum).param == decode }
}
}
}
}
KotlinSerializationはカスタムクラスにシリアライズする場合は、カスタムシリアライザーを実装しなければいけません。
カスタムシリアライザーで受け取った値をEnumへと変換できます。
これは【0,1での例+想定外の値はnullとして扱う】ですが
Status.Noneなどを入れてそれ以外を加えることで想定外の値にも対応できるはず。
(Stringなどならencodeの際は適切なメソッドを使用する)
感想
ここまでやったが、(自分は)実際にはResponse用のクラスとModelクラスでは分けて実装することが多いのでModelクラスへのConvertのところでこういうのはやればいいかなとも思った。
アノテーションとかでスッと出来ないので、あまりカスタムすることは想定してないのかな?
(リフレクション不使用やTypeSafeで扱う仕様からかもしれないけど)
でもせっかく調べて、何かに役に立つかもしれないので備忘録として。
アプリケーションオリジナルのエラーコードなどのハンドリングに使えば、同時にエラーメッセージを内包できたりしそうなのでそういった部分では活用できるか。
他に最近ではenumでやっていたようなことは、sealed Class(interface)で扱うことも多いので、そちらへの変換もしたい。
追記
Sealed Classへのパースはこちらに記載がありました。
ポリモーフィズムによってSealded Classの方がEnumより柔軟に扱えるかと思います。
データ部を複雑なJsonにすること自体は実務上中々ないと思いますが、
やはり(アプリ限定)エラーハンドリングは早期に分かった方が嬉しいですかね。