GuilhE
GuilhE

Reputation: 11861

Ktor custom json object deserialization

I'm new to Ktor, I come from a Retrofit background, and I want to map this json:

{
  "key1": "value1",
  "key2": "value2",
  ...
}

into (actually I don't need to map the json itself only the deserialized version):

[
  {"key1": "value1"},
  {"key2": "value2"},
  ...
]

@Serializable
data class MyObject(
    val member1: String,
    val member2: String
)

The samples I've seen in official docs didn't help much, so I was trying something like:

@InternalSerializationApi
object CustomDeserializer : DeserializationStrategy<List<MyObject>> {

    @ExperimentalSerializationApi
    override val descriptor = buildSerialDescriptor("MyObject", PolymorphicKind.OPEN){
        element("key", String.serializer().descriptor)
        element("value", String.serializer().descriptor)
    }

    @ExperimentalSerializationApi
    override fun deserialize(decoder: Decoder): List<MyObject> = decoder.decodeStructure(descriptor) {
        val result = ArrayList<MyObject>()
        loop@ while (true) {
            val index = decodeElementIndex(descriptor)
            if (index == DECODE_DONE) {
                break@loop
            } else if (index > 1) {
                throw SerializationException("Unexpected index $index")
            } else {
                result.add(MyObject(decodeStringElement(descriptor, index = 0), decodeStringElement(descriptor, index = 1)))
            }
        }
        return result
    }
}

Questions:

  1. Am I in the right path, or there are better ways to achieve it?
  2. How do I add this to my client? (it could be only for a specific request)

ps: this is how I would do it with Gson (ignore the fact this is in Java):

public class MyObjectConverterFactory implements JsonDeserializer<List<MyObject>> {

    @Override
    public List<MyObject> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        List<MyObject> res = new ArrayList<>();
        if (json != null && json.getAsJsonObject() != null) {
            JsonObject object = json.getAsJsonObject();
            Set<String> keys = object.keySet();
            for (String key : keys) {
                res.add(new MyObject(key, object.get(key).getAsString()));
            }
        }
        return res;
    }
}

Upvotes: 1

Views: 4068

Answers (1)

As far as I get it from GSON implementation, you need to deserialize from JSON

{"key1":"value1","key2":"value2", ...}

into

listOf(MyObject(member1="key1", member2="value1"), MyObject(member1="key2", member2="value2"), ...)

It's possible with kotlinx.serialization too:

object MyObjectListSerializer : JsonTransformingSerializer<List<MyObject>>(ListSerializer(MyObject.serializer())) {
    override fun transformDeserialize(element: JsonElement) =
        JsonArray((element as JsonObject).entries.map { (k, v) ->
            buildJsonObject {
                put("member1", k)
                put("member2", v)
            }
        })
}

Usage (plain kotlinx.serialization):

val result = Json.decodeFromString(MyObjectListSerializer, "{\"key1\":\"value1\",\"key2\":\"value2\"}")

Usage (with Ktor client):

val client = HttpClient {
    install(JsonFeature) {
        serializer = KotlinxSerializer(Json {
            serializersModule = SerializersModule { contextual(MyObjectListSerializer) }
        })
    }
}

val result = client.get<List<MyObject>>("http://localhost:8000/myObj")

Upvotes: 4

Related Questions