Home > database >  How to use @kotlinx.serialization.Serializable with java.time.Instant?
How to use @kotlinx.serialization.Serializable with java.time.Instant?

Time:03-27

I'm learning how to use Ktor's HttpClient. And, I'd like it to automatically convert a JSON response into a data class. I think I have all the setup correct (pasted below), but unfortunately, java.time.Instant uses import java.io.Serializable;, which I guess isn't compatible with kotlinx's @kotlinx.serialization.Serializable.

So, how can I get Ktor to recognize Instant as serializable?

val httpClient = HttpClient(CIO) {
    install(JsonFeature) {
        serializer = KotlinxSerializer(Json {
            prettyPrint = true
            isLenient = true
        })
    }
}
val response: MyResponse = httpClient.get(baseUrl()   "/example/path") {
    contentType(ContentType.Application.Json)
}

@kotlinx.serialization.Serializable
data class MyResponse(
    val name: String,
    val time: Instant // ERROR: "Serializer has not been found for type 'Instant'. To use context serializer as fallback, explicitly annotate type or property with @Contextual"
)

Sidenote: Other answers using Gson or Jackson or other serializers could be useful too since they may not have to explicitly add @Serializable

CodePudding user response:

One solution is to use the kotlinx-datetime library (https://github.com/Kotlin/kotlinx-datetime), which gives you a Kotlin version of Instant with the @kotlinx.serialization.Serializable that you are looking for.

So, instead of using java.time.Instant, you could use kotlinx.datetime.Instant in MyResponse.

Note that the library is experimental, and the API is subject to change. (Source: https://github.com/Kotlin/kotlinx-datetime#using-in-your-projects)

CodePudding user response:

Another solution, if you want to keep using java.time, is to create your own serializer for java.time.Instant (note experiemental APIs are used):

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import java.time.Instant

@Serializer(forClass = Instant::class)
@OptIn(ExperimentalSerializationApi::class)
object InstantSerializer : KSerializer<Instant> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.LONG)
    override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeLong(value.toEpochMilli())
    override fun deserialize(decoder: Decoder) = Instant.ofEpochMilli(decoder.decodeLong())
}

Then you'd write the serializable class like this:

import kotlinx.serialization.*
import java.time.Instant

@Serializable
data class Event(
    val name: String, 
    @Serializable(with=InstantSerializer::class) val instant: Instant
)

Note: There are other approaches (e.g., @UseSerializers).

With this setup, the following code:

import kotlinx.serialization.json.*
import java.time.Instant

fun main() {
    val event = Event("Test Event", Instant.now())
    val json = Json.encodeToString(event)
    println("Encoded to JSON  : $json")
    println("Decoded from JSON: ${Json.decodeFromString<Event>(json)}")
}

Gives this output:

Encoded to JSON  : {"name":"Test Event","instant":1648329502803}
Decoded from JSON: Event(name=Test Event, instant=2022-03-26T21:18:22.803Z)
  • Related