Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ktor: Serialize/Deserialize JSON with List as root in Multiplatform

How can we use kotlin.serialize with Ktor's HttpClient to deserialize/serialize JSON with lists as root? I am creating the HttpClient as follows:

HttpClient {
       install(JsonFeature) {
           serializer = KotlinxSerializer().apply {
               setMapper(MyClass::class, MyClass.serializer())
               setMapper(AnotherClass::class, AnotherClass.serializer())
           }
       }
       install(ExpectSuccess)
   }

Appears I need to setMapper for List, however that is not possible with generics. I see I can get the serializer for it with MyClass.serializer().list, but registering it to deserialize/serialize on http requests is not straight forward. Anyone know of a good solution?

like image 590
Patrick Avatar asked Oct 24 '18 14:10

Patrick


People also ask

What is the best JSON serialization library to use with Ktor?

Although the preferred JSON serialization library used in a lot of the Ktor examples is GSON, which makes sense due to it’s simplicity and ease of use, in real-world use Jackson is probably the preferred option. It’s faster (especially when combined with the AfterBurner module) and generally more flexible.

How to serialize/deserialize JSON data in Kotlin?

To serialize/deserialize JSON data, you can choose one of the following libraries: kotlinx.serialization, Gson, or Jackson. Add the Kotlin serialization plugin, as described in the Setup section. Add the ktor-serialization-kotlinx-json artifact in the build script:

How to deserialize a class from a JSON file?

The result is a class that you can use for your deserialization target. 1 Copy the JSON that you need to deserialize. 2 Create a class file and delete the template code. 3 Choose Edit > Paste Special > Paste JSON as Classes . The result is a class that you can use for your deserialization target.

How do I serialize the content in Ktor?

Serializing/deserializing the content in a specific format. Ktor supports the following formats out-of-the-box: JSON, XML, and CBOR. To use ContentNegotiation, you need to include the ktor-server-content-negotiation artifact in the build script: Note that serializers for specific formats require additional artifacts.


2 Answers

You can write wrapper and custom serializer:

@Serializable
class MyClassList(
    val items: List<MyClass>
) {

    @Serializer(MyClassList::class)
    companion object : KSerializer<MyClassList> {

        override val descriptor = StringDescriptor.withName("MyClassList")

        override fun serialize(output: Encoder, obj: MyClassList) {
            MyClass.serializer().list.serialize(output, obj.items)
        }

        override fun deserialize(input: Decoder): MyClassList {
            return MyClassList(MyClass.serializer().list.deserialize(input))
        }
    }
}

Register it:

HttpClient {
    install(JsonFeature) {
        serializer = KotlinxSerializer().apply {
            setMapper(MyClassList::class, MyClassList.serializer())
        }
    }
}

And use:

suspend fun fetchItems(): List<MyClass> {
    return client.get<MyClassList>(URL).items
}
like image 157
Maxim Pestryakov Avatar answered Oct 19 '22 12:10

Maxim Pestryakov


Update with ktor 1.3.0:

Now you're able to receive default collections(such a list) from the client directly:

@Serializable
data class User(val id: Int)

val response: List<User> = client.get(...)
// or client.get<List<User>>(...)

Before ktor 1.3.0:

There is no way to (de)serialize such JSON in the kotlinx.serialization yet.

For serialization you could try something like this:

fun serializer(data: Any) = if (data is List<*>) {
   if (data is EmptyList) String::class.serializer().list // any class with serializer 
   else data.first()::class.serializer().list
} else data.serializer()

And there are no known ways to get the list deserializer.

like image 35
Leonid Avatar answered Oct 19 '22 13:10

Leonid