Last active
June 23, 2023 00:44
-
-
Save arberg/e20db05e018c61f37f1d274a254657c3 to your computer and use it in GitHub Desktop.
Kotlin Enum Serializer which ignores unknown values (kotlinx)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.annotation.SuppressLint | |
import kotlinx.serialization.KSerializer | |
import kotlinx.serialization.SerialName | |
import kotlinx.serialization.Serializable | |
import kotlinx.serialization.descriptors.PrimitiveKind | |
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor | |
import kotlinx.serialization.descriptors.SerialDescriptor | |
import kotlinx.serialization.encoding.Decoder | |
import kotlinx.serialization.encoding.Encoder | |
import kotlinx.serialization.json.Json | |
import org.junit.Test | |
import strikt.api.* | |
import strikt.assertions.* | |
abstract class EnumIgnoreUnknownSerializer<T : Enum<T>>(values: Array<out T>, private val defaultValue: T) : KSerializer<T> { | |
// Alternative to taking values in param, take clazz: Class<T> | |
// - private val values = clazz.enumConstants | |
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(values.first()::class.qualifiedName!!, PrimitiveKind.STRING) | |
// Build maps for faster parsing, used @SerialName annotation if present, fall back to name | |
private val lookup = values.associateBy({ it }, { it.serialName }) | |
private val revLookup = values.associateBy { it.serialName } | |
private val Enum<T>.serialName: String | |
get() = this::class.java.getField(this.name).getAnnotation(SerialName::class.java)?.value ?: name | |
override fun serialize(encoder: Encoder, value: T) { | |
encoder.encodeString(lookup.getValue(value)) | |
} | |
override fun deserialize(decoder: Decoder): T { | |
// only run 'decoder.decodeString()' once | |
return revLookup[decoder.decodeString()] ?: defaultValue // map.getOrDefault is not available < API-24 | |
} | |
} | |
// ---- Usage example, put this in for instance TestEnum.kt | |
@Serializable(with = TestEnumSerializer::class) | |
enum class TestEnum { | |
One, | |
@SerialName("TWO_TWO") | |
Two, | |
Unknown | |
} | |
object TestEnumSerializer : EnumIgnoreUnknownSerializer<TestEnum>(TestEnum.values(), TestEnum.Unknown) | |
// ---- A test using Strikt framework | |
class EnumIgnoreUnknownSerializerTest { | |
// get your Json instance | |
val ksonServer: Json = Json | |
@Test | |
fun encodeAndDecode_WithoutSerialName() { | |
expectThat(encode(TestEnum.One)).isEqualTo(""""One"""") | |
expectThat(decode(""""One"""")).isEqualTo(TestEnum.One) | |
} | |
@Test | |
fun encodeAndDecode_WithSerialName() { | |
expectThat(encode(TestEnum.Two)).isEqualTo(""""TWO_TWO"""") | |
expectThat(decode(""""TWO_TWO"""")).isEqualTo(TestEnum.Two) | |
} | |
@Test | |
fun decodeUnknown() { | |
expectThat(decode(""""FooBar"""")).isEqualTo(TestEnum.Unknown) | |
expectThat(decode(""""Unknown"""")).isEqualTo(TestEnum.Unknown) | |
} | |
private fun encode(value: TestEnum): String = ksonServer.encodeToString(TestEnum.serializer(), value) | |
private fun decode(string: String) = ksonServer.decodeFromString(TestEnum.serializer(), string) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yes, that's what I started from. Serializer itself is preserved, but reflective logic inside it fails.