How to map numbered JSON fields to a list using Kotlinx Serialization

22 hours ago 7
ARTICLE AD BOX

This is a classic example of a poorly designed Api. To handle this type of response i have 3 approaches.

By defining a extention function on JSONObject. fun JsonObject.extractNumberedPairs(keyPrefix: String, valuePrefix: String): List<Pair<String, String>> = keys.filter { it.startsWith(keyPrefix) } .mapNotNull { key -> val index = key.removePrefix(keyPrefix) val keyVal = this[key]?.jsonPrimitive?.contentOrNull?.takeIf { it.isNotBlank() } val valVal = this["$valuePrefix$index"]?.jsonPrimitive?.contentOrNull?.takeIf { it.isNotBlank() } if (keyVal != null) keyVal to (valVal ?: "") else null } Custom Serializer Class @Serializable(with = MealSerializer::class) data class Meal( val id: String,val name: String,val ingredients:List<Ingredient>) data class Ingredient(val name: String, val measure: String) object MealSerializer : KSerializer<Meal> { override val descriptor = buildClassSerialDescriptor("Meal") override fun deserialize(decoder: Decoder): Meal { val json = decoder.beginStructure(descriptor).let { (decoder as JsonDecoder).decodeJsonElement().jsonObject } val ingredients = json.extractNumberedPairs("strIngredient", "strMeasure") .map { (name, measure) -> Ingredient(name, measure) } return Meal( id = json["idMeal"]!!.jsonPrimitive.content, name = json["strMeal"]!!.jsonPrimitive.content, ingredients = ingredients ) } override fun serialize(encoder: Encoder, value: Meal) = throw UnsupportedOperationException() } Raw Manual Mapper - This is simple yet a little exhaustive // 1. Raw DTO — just captures the wire format @Serializable data class MealDto( @SerialName("idMeal") val id: String, @SerialName("strMeal") val name: String, @SerialName("strIngredient1") val ingredient1: String? = null, ... ... @SerialName("strIngredient20") val ingredient20: String? = null, @SerialName("strMeasure1") val measure1: String? = null, .... ... @SerialName("strMeasure20") val measure20: String? = null, ) fun MealDto.toMeal(): Meal { val rawIngredients = listOf(ingredient1, ingredient2, ... ,ingredient20) val rawMeasures = listOf( measure1, measure2, ... ,measure20 ) val ingredients = rawIngredients .zip(rawMeasures) .filter { (name, _) -> !name.isNullOrBlank() } .map { (name, measure) -> Ingredient(name!!, measure.orEmpty()) } return Meal(id, name, ingredients) }

I like the first approach which is similar to what you did, just not using for loop as it restricts us and it would break if there are more that 20 ingredients.

Read Entire Article