Skip to content

Instantly share code, notes, and snippets.

@vshivam
Last active November 15, 2022 02:08
Show Gist options
  • Save vshivam/5fd90e87be8958f5c9e1d1ff0c55f798 to your computer and use it in GitHub Desktop.
Save vshivam/5fd90e87be8958f5c9e1d1ff0c55f798 to your computer and use it in GitHub Desktop.
When a mobile client interacts with backend, api contracts are subject to change heavily during the development phase. With this custom serializer we can ensure that it is "okay" to break contracts. Properties can be added, removed and updated without the fear of "older clients" breaking.
class AlphaPropertySerializer<T>(
private val valueSerializer: KSerializer<T>
) : KSerializer<AlphaProperty<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): AlphaProperty<T> {
decoder as JsonDecoder
val jsonElement: JsonElement = decoder.decodeJsonElement()
return try {
AlphaProperty.Known(decoder.json.decodeFromJsonElement(valueSerializer, jsonElement))
} catch (e: Exception) {
AlphaProperty.Unknown
}
}
override fun serialize(encoder: Encoder, value: AlphaProperty<T>) {
when (value) {
AlphaProperty.Unknown -> throw SerializationException(
"Tried to serialize an optional property that had no value present."
)
is AlphaProperty.Known -> valueSerializer.serialize(encoder, value.value)
}
}
}
sealed class AlphaProperty<out T> {
object Unknown : AlphaProperty<Nothing>()
data class Known<T>(val value: T) : AlphaProperty<T>()
}
class AlphaPropertySerializerTest {
@Test
fun verifyParsingWhenJsonIsCorrect() {
val actual = json.decodeFromString(
deserializer = SampleWidget.serializer(),
string = """{"customerId" : "customer_id","tip" : {"amount": 3, "note" : "Thank you!"}}"""
)
assertThat(actual).isEqualTo(
SampleWidget(
tip = AlphaProperty.Known(Tip(3, "Thank you!")),
customerId = AlphaProperty.Known("customer_id")
)
)
}
@Test
fun verifyParsingWhenTipChangesToPrimitive() {
val actual = json.decodeFromString(
deserializer = SampleWidget.serializer(),
string = """{"tip" : 3}"""
)
assertThat(actual).isEqualTo(
SampleWidget(
customerId = null,
tip = AlphaProperty.Unknown
)
)
}
@Test
fun verifyParsingWhenCustomerIdChangesToObject() {
val actual = json.decodeFromString(
deserializer = SampleWidget.serializer(),
string = """{"customerId": { "uuid" : "uuid"}, "tip" : {"amount": 10, "note" : "Thank you!"}}"""
)
assertThat(actual).isEqualTo(
SampleWidget(
customerId = AlphaProperty.Unknown,
tip = AlphaProperty.Known(Tip(10, "Thank you!"))
)
)
}
}
private val json = Json(
builderAction = {
coerceInputValues = true
ignoreUnknownKeys = true
explicitNulls = false
this.isLenient = isLenient
this.encodeDefaults = false
}
)
@Serializable
data class Tip(val amount: Int, val note: String)
@Serializable
data class SampleWidget(
@Serializable(with = AlphaPropertySerializer::class)
val customerId: AlphaProperty<String>?,
@Serializable(with = AlphaPropertySerializer::class)
val tip: AlphaProperty<Tip>?,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment