I'm trying to deserialize a JSON file to a Kotlin data class I cannot control using kotlinx.serialization
.
The class looks something along the lines of:
public data class Lesson(
val uid: String,
val start: Instant,
val end: Instant,
val module: String,
val lecturers: List<String>,
val room: String?,
val type: String?,
val note: String?
)
The JSON I try to parse looks like this:
{
"lessons": [
{
"uid": "sked.de956040",
"start": "2020-11-02T13:30:00Z",
"end": "2020-11-02T16:45:00Z",
"module": "IT2101-Labor SWE I: Gruppe 1 + 2",
"lecturers": [
"Kretzmer"
],
"room": "-",
"type": "La",
"note": "Prüfung Online"
}
]
}
This is tried via:
@Serializable
data class ExpectedLessons(
val lessons: List<Lesson>
)
val decoded = Json.decodeFromString<ExpectedLessons>(text)
First, make a class serializable by annotating it with @Serializable . You can now serialize an instance of this class by calling Json. encodeToString() . You can also serialize object collections, such as lists, in a single call.
This is still the case in Kotlin 1.2! Indeed, both String an Int are internally transformed into appropriate Java classes which are serializeable.
Using Google's Gson library We can even perform serialization and deserialization of Kotlin objects using the Gson library. Gson provide functions toJson() and fromJson() to convert Kotlin objects to their JSON representation and vice-versa.
As the class Lesson
cannot be modified, one cannot add a @Serializable
annotation to make (de)serialization work.
So you may create two custom serializers to make it work.
@OptIn(ExperimentalSerializationApi::class)
@Serializer(forClass = Lesson::class)
object LessonSerializer : KSerializer<Lesson> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Lesson") {
element<String>("uid")
element<String>("start")
element<String>("end")
element<String>("module")
element<List<String>>("lecturers")
element<String?>("room", isOptional = true)
element<String?>("type", isOptional = true)
element<String?>("note", isOptional = true)
}
override fun serialize(encoder: Encoder, value: Lesson) {
encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, 0, value.uid)
encodeSerializableElement(descriptor, 1, InstantSerializer, value.start)
encodeSerializableElement(descriptor, 2, InstantSerializer, value.end)
encodeStringElement(descriptor, 3, value.module)
encodeSerializableElement(descriptor, 4, ListSerializer(String.serializer()), value.lecturers)
encodeNullableSerializableElement(descriptor, 5, String.serializer(), value.room)
encodeNullableSerializableElement(descriptor, 6, String.serializer(), value.type)
encodeNullableSerializableElement(descriptor, 7, String.serializer(), value.note)
}
}
override fun deserialize(decoder: Decoder): Lesson {
return decoder.decodeStructure(descriptor) {
var uid: String? = null
var start: Instant? = null
var end: Instant? = null
var module: String? = null
var lecturers: List<String> = emptyList()
var room: String? = null
var type: String? = null
var note: String? = null
loop@ while (true) {
when (val index = decodeElementIndex(descriptor)) {
DECODE_DONE -> break@loop
0 -> uid = decodeStringElement(descriptor, 0)
1 -> start = decodeSerializableElement(descriptor, 1, InstantSerializer)
2 -> end = decodeSerializableElement(descriptor, 2, InstantSerializer)
3 -> module = decodeStringElement(descriptor, 3)
4 -> lecturers = decodeSerializableElement(descriptor, 4, ListSerializer(String.serializer()))
5 -> room = decodeNullableSerializableElement(descriptor, 5, String.serializer().nullable)
6 -> type = decodeNullableSerializableElement(descriptor, 6, String.serializer().nullable)
7 -> note = decodeNullableSerializableElement(descriptor, 7, String.serializer().nullable)
else -> throw SerializationException("Unexpected index $index")
}
}
Lesson(
requireNotNull(uid),
requireNotNull(start),
requireNotNull(end),
requireNotNull(module),
lecturers,
room,
type,
note
)
}
}
}
@OptIn(ExperimentalSerializationApi::class)
@Serializer(forClass = Instant::class)
object InstantSerializer : KSerializer<Instant> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeString("$value")
}
override fun deserialize(decoder: Decoder): Instant {
return Instant.parse(decoder.decodeString())
}
}
You may configure the serializers before using them like this:
@file:UseSerializers(InstantSerializer::class, LessonSerializer::class)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With