'How to use @kotlinx.serialization.Serializable with java.time.Instant?
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
Solution 1:[1]
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)
Solution 2:[2]
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)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Anonsage |
| Solution 2 |
