Given a data class like:
@Serializable
data class Person(
val name: String,
val birthDate: Long,
) {
val age get() = /* calculate age */
}
How do I include age
in serialization? I know that I can use Transient
to exclude a property.
According to the Basic Serialization guide only backing fields are serializable in the example. But it does not mention how to make non-backing fields (properties) serializable. Is there any way to include age
in serialization without a hugely overkill solution that requires writing a custom serializer?
An alternative would be to use a backing field and assign it on init {}
or pass it on instantiation-site but this seems quite hacky to me.
To include the age property in serialization without resorting to writing a custom serializer or using a backing field, you can utilize the @Contextual annotation provided by kotlinx.serialization library. This annotation allows you to define custom serializers in a concise and flexible manner.
Here’s how you can modify your Person class to include the age property in serialization using @Contextual:
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
@Serializable
data class Person(
val name: String,
val birthDate: Long,
) {
@Transient
val age: Int = calculateAge(birthDate)
private fun calculateAge(birthDate: Long): Int {
// Calculate age based on birthDate
// Example implementation:
val currentYear = java.time.LocalDate.now().year
val birthYear = java.time.LocalDate.ofEpochDay(birthDate).year
return currentYear - birthYear
}
@Serializer(forClass = Person::class)
companion object : KSerializer<Person> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Person") {
element<String>("name")
element<Long>("birthDate")
element<Int>("age")
}
override fun serialize(encoder: Encoder, value: Person) {
val compositeOutput = encoder.beginStructure(descriptor)
compositeOutput.encodeStringElement(descriptor, 0, value.name)
compositeOutput.encodeLongElement(descriptor, 1, value.birthDate)
compositeOutput.encodeIntElement(descriptor, 2, value.age)
compositeOutput.endStructure(descriptor)
}
override fun deserialize(decoder: Decoder): Person {
val compositeInput = decoder.beginStructure(descriptor)
lateinit var name: String
var birthDate: Long = 0
var age: Int = 0
loop@ while (true) {
when (val index = compositeInput.decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> break@loop
0 -> name = compositeInput.decodeStringElement(descriptor, index)
1 -> birthDate = compositeInput.decodeLongElement(descriptor, index)
2 -> age = compositeInput.decodeIntElement(descriptor, index)
else -> throw SerializationException("Unknown index: $index")
}
}
compositeInput.endStructure(descriptor)
return Person(name, birthDate)
}
}
}
fun main() {
val person = Person("John", java.time.LocalDate.of(1990, 5, 15).toEpochDay())
val jsonString = Json.encodeToString(Person.serializer(), person)
println(jsonString)
}