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)