Decoding nested generics data class.
See original GitHub issueHi 👋
I’m trying to decode the following Json using kotlinx.serialization
and I am running into some issues.
The JSON:
{
"fill": 0.0
}
Can also be represented as:
{
"fill": [0.0, 0.0, 0.0, 0.0]
}
The data classes:
/// Fill.kt
@Serializable(FillSerializer::class)
public data class Fill<T>(@SerialName("fill") val insets: Inset<T>) {
companion object
}
@Serializer(forClass = Fill::class)
class FillSerializer<T>(private val fillSerializer: KSerializer<T>): KSerializer<Fill<T>> {
override val descriptor: SerialDescriptor = object : SerialClassDescImpl("FillSerializer") {
init {
addElement("fill")
}
}
override fun deserialize(decoder: Decoder): Fill<T> {
val inp = decoder.beginStructure(descriptor)
val key = inp.decodeStringElement(descriptor, 0)
val insets = inp.decodeSerializableElement(descriptor, 0, fillSerializer)
inp.endStructure(descriptor)
return Fill(insets)
}
override fun serialize(encoder: Encoder, obj: Fill<T>) {
print(obj)
}
}
/// Inset.kt
@Serializable(InsetSerializer::class)
data class Inset<T>(val start: T,
val end: T,
val top: T,
val bottom: T): WithDefault {
constructor(array: Array<T>) : this(array[0], array[1], array[2], array[3])
}
@Serializer(forClass = Inset::class)
class InsetSerializer<T>(private val serializer: KSerializer<T>): KSerializer<Inset<T>> {
override val descriptor: SerialDescriptor
get() = StringDescriptor.withName("InsetSerializer")
override fun deserialize(decoder: Decoder): Inset<T> {
val decoded = decoder.decode(serializer)
.guard {
throw Exception("Some exception sonny.")
}
return when (decoded) {
is List<*> -> {
Inset(decoded.toTypedArray() as Array<T>)
}
is Float -> {
Inset(decoded, decoded, decoded, decoded)
}
else -> throw Exception("WTF.")
}
}
override fun serialize(encoder: Encoder, obj: Inset<T>) {
}
}
I wrote the above following code to get something workable, Inset<T>
by itself parses fine. The following tests pass:
final class InsetTests {
val insetString = "0.0"
val insetArrayString = "[0.0, 0.0, 0.0, 0.0]"
val variedInsetArrayString = "[0.0, 12.0, 24.0, 18]"
@Test
fun testInsetString() {
val json = Json(JsonConfiguration.Stable)
val insets = json.parse(Inset.serializer(FloatSerializer), insetString)
assert(insets == Inset(0.0f, 0.0f, 0.0f, 0.0f))
}
@Test
fun testInsetArrayString() {
val json = Json(JsonConfiguration.Stable)
val insets = json.parse(Inset.serializer(ArrayListSerializer(FloatSerializer)), insetArrayString)
assert(insets == Inset(0.0f, 0.0f, 0.0f, 0.0f))
}
@Test
fun testVariedInsetArrayString() {
val json = Json(JsonConfiguration.Stable)
val insets = json.parse(Inset.serializer(ArrayListSerializer(FloatSerializer)), variedInsetArrayString)
assert(insets == Inset(0.0f, 12.0f, 24.0f, 18.0f))
}
}
Tests for Fill
fail however:
final class FillTests {
val fillJSONExample1: String = """
{
"fill": 0.0
}
""".trimIndent()
val fillJSONExample2: String = """
{
"fill": [0.0, 0.0, 0.0, 0.0]
}
""".trimIndent()
@Test
fun testParsingExample1() {
val json = Json(JsonConfiguration.Stable)
val parsedObject = json.parse(
Fill.serializer(
Inset.serializer(FloatSerializer)
),
fillJSONExample1
)
print(parsedObject)
}
}
With the following exception:
kotlinx.serialization.json.JsonParsingException: Invalid JSON at 12: Expected string or non-null literal
at kotlinx.serialization.json.internal.JsonReader.takeString(JsonReader.kt:337)
at kotlinx.serialization.json.internal.StreamingJsonInput.decodeString(StreamingJsonInput.kt:111)
at kotlinx.serialization.ElementValueDecoder.decodeStringElement(ElementWise.kt:139)
at com.ancestry.layoutcomposer.values.FillSerializer.deserialize(Layout.Fill.kt:48)
at com.ancestry.layoutcomposer.values.FillSerializer.deserialize(Layout.Fill.kt:36)
at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:33)
at kotlinx.serialization.json.internal.StreamingJsonInput.decodeSerializableValue(StreamingJsonInput.kt:29)
at kotlinx.serialization.CoreKt.decode(Core.kt:79)
at kotlinx.serialization.json.Json.parse(Json.kt:152)
at com.ancestry.layoutcomposer.FillTests.testParsingExample1(Fill.Tests.kt:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
What is the correct way to parse that dynamic json?
Issue Analytics
- State:
- Created 4 years ago
- Comments:7 (3 by maintainers)
Top Results From Across the Web
Swift - JSONDecoder - decoding generic nested classes
I want to use JSONEncoder and JSONDecoder to send and receive objects. I have an issue when decoding generic classes inside another object...
Read more >Using generics to load any kind of Codable data
Using generics to load any kind of Codable data ... This is called a nested struct, and is simply one struct placed inside...
Read more >Decoding nested values with property wrappers - Ilya Puchka
So let's see how property wrappers can be used to solve one decoding edge case as an example - decoding deeply nested values....
Read more >Using generics to load any kind of Codable data - YouTube
Download the completed project here: https://github.com/twostraws/hackingwithswiftOther parts in Project 8:Introduction: ...
Read more >Encoding and decoding - circe
It also provides instances for List[A] , Option[A] , and other generic types, but only if A has an Encoder instance. Encoding data...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I believe it would easier to use cast in deserializer to Json-specific
JsonInput
to accomplish you goal. Because, as @pdvrieze correctly mentioned,kotlinx.serialization
is a format-agnostic framework and catching exceptions may break the internal state of some parsers.JsonInput
has specificdecodeJson
method to read json into abstract tree. See this example withEither
: https://github.com/kotlin/kotlinx.serialization/blob/45aa7c7ab97f25f205408b46a7069886408bce6b/runtime/commonTest/src/kotlinx/serialization/json/JsonTreeAndMapperTest.kt#L36So, your deserializer will look like this:
That is my implementation