question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Deserialize JSON array containing instance of sealed class

See original GitHub issue

Hello,

I am getting unusual behavior when I deserialize a JSON array that contains an instance of a sealed class. I expect an exception to be thrown since a sealed class cannot be instantiated, but instead it is being deserialized as a LinkedHashMap.

I have the following class definitions:

val json = ObjectMapper().registerKotlinModule()

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "schema",
        visible = true
)
@JsonSubTypes(value = *arrayOf(
        JsonSubTypes.Type(value = Child::class, name = "Child")
))
private sealed class AbstractParent {
    abstract val schema: String
    abstract val a: String
}

@JsonTypeName("Child")
private data class Child(
        override val schema: String,
        override val a: String,
        val b: String
): AbstractParent()

I have posted a Gist that contains all tests I am running related to this here. All are successful, except for one. The specific test that exposes the behavior in question is copied below.

@Test(expected = JsonMappingException::class)
fun testSerializeDeserializeParentList() {
    val source = json.readTree("""
    [{
        "schema": "AbstractParent",
        "a": "foo"
    }]
    """.trimIndent())

    val deserialized: List<AbstractParent> = json.treeToValue(source)
    val serialized = json.readTree(json.writeValueAsString(deserialized))

    assertEquals(source, serialized)
}

As you can see, the JSON object within the array follows the model of the AbstractParent class, which should not be able to be deserialized since AbstractParent is a sealed class. In a test where I try to deserialize a standalone instance of AbstractParent, a JsonMappingException is thrown. I would expect to get that same exception when it is contained in an array, but instead no exception is thrown. When I inspect deserialized in a debugger, I see that the object it contains is a LinkedHashMap which is very unusual. When I try to access deserialized.first() I get a ClassCastException because LinkedHashMap is not a sub-type of AbstractParent. It seems like a fix is needed to make sure that there is an immediate exception when deserializing the JSON, instead of the late exception we see here.

Thanks

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
cowtowncodercommented, Jul 26, 2017

@alexrwasserman Ok good. This makes sense. Glad you have a solution that works.

Fwtw, use of generic types as root values has been (… and, will be) a big source of problems, so I recommend users to consider avoiding it when possible. As you have found it can be made to work, but there’s bit extra code, and can be non-obvious to tackle. Work-around include use of helper class that binds type (class AbstractParentList extends List<AbstractParent>), to achieve the end result by passing non-generic type.

0reactions
alexrwassermancommented, Jul 25, 2017

Just to clarify, we are using this function declared in com.fasterxml.jackson.module.kotlin.Extensions:

inline fun <reified T: Any> ObjectMapper.treeToValue(n: TreeNode): T = treeToValue(n, T::class.java)

Your response helped us pinpoint the issue and resolve it. We thought that the value being passed for T was List<AbstractParent>, but it actually was just the generic List interface, which would explain the results we are seeing.

The solution we went with is this:

val deserialized = json.readValue<List<AbstractParent>>(json.writeValueAsString(source), object : TypeReference<List<AbstractParent>>(){})

Not the most elegant, but we didn’t see a better way

Read more comments on GitHub >

github_iconTop Results From Across the Web

kotlinx.serialization: Deserialize JSON array as sealed class
This is done by simple mapping the indicies of the JsonArray to its corresponding sealed class representation.
Read more >
Deserialize JSON arrays from object type ... - TechiesWeb.NET
Imagine working with a class like this for serialization/deserialization. public sealed class Person { public string FullName { get; set; } ...
Read more >
How to serialize properties of derived classes with System ...
In this article, you will learn how to serialize properties of derived classes with the System.Text.Json namespace.
Read more >
SealedClassSerializer - Kotlin
This class provides support for multiplatform polymorphic serialization of sealed classes. In contrary to PolymorphicSerializer, all known subclasses with ...
Read more >
Kotlin with Jackson: Deserializing Kotlin Sealed Classes
We have our Parameter class, which is a sealed class and two objects implementing it FirstName and FullName . This might not be...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found