Hi I'm new to kotlinx serialization. the json is snake case, and kotlin data class uses camel case. is there a way to parse snake case by using custom serilizer and deserializer?
I know how to do...
val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
val project = format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""")
assertThat(project.projectName).isEqualTo("kotlinx.coroutines")
assertThat(project.projectOwner).isEqualTo("Kotlin")
but I want to override deserializer and place this logic inside of companion object. is this possible?
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encodeToString
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
@kotlinx.serialization.ExperimentalSerializationApi
private val json = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
@Serializable(PaymentInfo.Companion::class)
data class PaymentInfo(
val paymentNo: String,
val paymentDate: String,
val paymentService: String,
) {
companion object: KSerializer<PaymentInfo> {
// what descriptor should I use?
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("PaymentInfo", PrimitiveKind.)
override fun serialize(encoder: Encoder, value: PaymentInfo) {
// what to do ?
}
override fun deserialize(decoder: Decoder): PaymentInfo {
// what to do ?
}
}
}
I want to avoid adding @SerialName
to every member!
@SerialName
First, I just want to mention that I don't understand your wish to avoid @SerialName
. You mentioned:
I'm afraid that that's too much labor.
But as you'll see in the next section of this answer, the custom serializer is quite a bit of code. And the same thing can be accomplished with just:
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class PaymentInfo(
@SerialName("payment_no") val paymentNo: String,
@SerialName("payment_date") val paymentDate: String,
@SerialName("payment_service") val paymentService: String
)
For something as simple as changing the serial name of a property, I strongly recommend you use @SerialName
instead of writing a completely custom serializer.
If you really want to avoid @SerialName
, then you can customize the serialized names via a custom serializer. If you have:
import kotlinx.serialization.Serializable
@Serializable(PaymentInfoSerializer::class)
data class PaymentInfo(
val paymentNo: String,
val paymentDate: String,
val paymentService: String
)
And you want the following custom names:
Class property | Serial Name |
---|---|
paymentNo |
payment_no |
paymentDate |
payment_date |
paymentService |
payment_service |
Then the custom PaymentInfoSerializer
might look like this:
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.descriptors.element
import kotlinx.serialization.encoding.*
// You can move this to the companion object of PaymentInfo if
// you really want to.
object PaymentInfoSerializer : KSerializer<PaymentInfo> {
override val descriptor = buildClassSerialDescriptor("PaymentInfo") {
element<String>("payment_no")
element<String>("payment_date")
element<String>("payment_service")
}
override fun serialize(encoder: Encoder, value: PaymentInfo) {
encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, 0, value.paymentNo)
encodeStringElement(descriptor, 1, value.paymentDate)
encodeStringElement(descriptor, 2, value.paymentService)
}
}
override fun deserialize(decoder: Decoder): PaymentInfo {
return decoder.decodeStructure(descriptor) {
var paymentNo = ""
var paymentDate = ""
var paymentService = ""
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> paymentNo = decoder.decodeString()
1 -> paymentDate = decoder.decodeString()
2 -> paymentService = decoder.decodeString()
CompositeDecoder.DECODE_DONE -> break
else -> error("Unknown index: $index")
}
}
PaymentInfo(paymentNo, paymentDate, paymentService)
}
}
}
See the Serializers chapter of the Kotlin Serialization Guide for more information.
Here is the above serializer in use:
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
fun main() {
val original = PaymentInfo("42", "2023-11-12", "foo")
val encoded = Json.encodeToString(original)
val decoded = Json.decodeFromString<PaymentInfo>(encoded)
println(original)
println(encoded)
println(decoded)
}
Which gives the following output:
PaymentInfo(paymentNo=42, paymentDate=2023-11-12, paymentService=foo)
{"payment_no":"42","payment_date":"2023-11-12","payment_service":"foo"}
PaymentInfo(paymentNo=42, paymentDate=2023-11-12, paymentService=foo)