Deserialize JSON array containing instance of sealed class
See original GitHub issueHello,
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:
- Created 6 years ago
- Comments:5 (3 by maintainers)
@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.Just to clarify, we are using this function declared in com.fasterxml.jackson.module.kotlin.Extensions:
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 genericList
interface, which would explain the results we are seeing.The solution we went with is this:
Not the most elegant, but we didn’t see a better way