Hrafn Thorvaldsson
09/30/2022, 5:13 PMString
and JsonObject
for target type classes not marked with the @Serializable annotation, and if this behavior difference is expected.
Please observe the following data class:
@Serializable
data class Test(val value: Int)
and the following manual construction of a JsonObject:
val jsonObject = buildJsonObject {
put("version", 1)
}
As expected when executing either Json.decodeFromJsonElement<Test>(jsonObject)
or Json.decodeFromString<Test>(jsonObject.toString())
the output is an instance of Test(value=1)
.
If the @Serializable
annotation is removed from the class Test
and a manual KSerializer
for the class is created to handle the mapping:
class TestSerializer: KSerializer<Test> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("test") {
element<Int>("version")
}
override fun deserialize(decoder: Decoder): Test {
return decoder.decodeStructure(descriptor) {
val version = decodeIntElement(
descriptor,
descriptor.getElementIndex("version")
)
Test(version)
}
}
// .... omitting serialize() method
}
then executing Json.decodeFromJsonElement(TestSerializer(), jsonObject)
will work like before and the output is an instance of Test(value=1)
just as before.
However executing Json.decodeFromString(TestingSerializer(), jsonObject.toString())
will result in an exception:
Unexpected JSON token at offset 1: Unexpected symbol 'v' in numeric literal at path: $
kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 1: Unexpected symbol 'v' in numeric literal at path: $
JSON input: {"version":1}
Is this the expected behavior, and if so what is causing the string traversal to fail while the JsonObject
is not affected?class DeSerializationTest: DescribeSpec( {
describe("decode of JsonObject via annotation: Works") {
@Serializable
data class Test(val version: Int)
val jsonPayload = buildJsonObject {
put("version", 1)
}
val decoded = Json.decodeFromJsonElement<Test>(jsonPayload)
decoded shouldBe Test(1)
}
describe("decode of String via annotation: Works") {
@Serializable
data class Test(val version: Int)
val jsonPayload = buildJsonObject {
put("version", 1)
}
val decoded = Json.decodeFromString<Test>(jsonPayload.toString())
decoded shouldBe Test(1)
}
describe("decode of JsonObject via custom serializer: Works") {
data class Test(val version: Int)
class TestSerializer: KSerializer<Test> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("testing") {
element<Int>("version")
}
override fun deserialize(decoder: Decoder): Test {
return decoder.decodeStructure(descriptor) {
val version = decodeIntElement(
descriptor,
descriptor.getElementIndex("version")
)
Test(version)
}
}
override fun serialize(encoder: Encoder, value: Test) {
TODO("Not yet implemented")
}
}
val jsonPayload = buildJsonObject {
put("version", 1)
}
val decoded = Json.decodeFromJsonElement(TestSerializer(), jsonPayload)
decoded shouldBe Test(1)
}
describe("decode of String via custom serializer: Fails!") {
data class Test(val version: Int)
class TestSerializer: KSerializer<Test> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("testing") {
element<Int>("version")
}
override fun deserialize(decoder: Decoder): Test {
return decoder.decodeStructure(descriptor) {
val version = decodeIntElement(
descriptor,
descriptor.getElementIndex("version")
)
Test(version)
}
}
override fun serialize(encoder: Encoder, value: Test) {
TODO("Not yet implemented")
}
}
val jsonPayload = buildJsonObject {
put("version", 1)
}
val decoded = Json.decodeFromString(TestSerializer(), jsonPayload.toString())
decoded shouldBe Test(1)
}
})