In an application I am serializing Kotlin objects to Json, sending them through HTTP and deserializing them on the other end back to Kotlin objects. To do so, I am using the jackson-dataind library. In one particular case, I am having trouble with deserializing a list. Consider the following classes and code:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")
interface ServerInteraction
data class GetJobResultResponse<T>(
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.CLASS)
val result: T?
) : ServerInteraction
data class TotalStationPoint(val pointName: String, val location: Point3, val timestamp: Instant)
class LoadTotalStationDataSerializationTest {
@Test
fun loadTotalStationDataIsSerializedCorrectly() {
val result: List<TotalStationPoint> = loadTotalStationData()
val getJobResultResponse: GetJobResultResponse<List<TotalStationPoint>> = GetJobResultResponse(result)
val serializedResponse: String = JsonSerializer.serialize(getJobResultResponse)
/*
Serializes to:
{
"type": "de.uni_freiburg.inatech.streem.image_converter.batch_job_executor.common.responses.GetJobResultResponse",
"result": {
"java.util.ArrayList": [
{
"pointName": "V1_22_0000000956",
"location": {
"x": 10012.21984,
"y": 10002.558907,
"z": 2.342047000000001
},
"timestamp": 1670328663.04
},
{
"pointName": "V1_22_0000000955",
"location": {
"x": 10012.219816,
"y": 10002.559031,
"z": 2.3418279999999996
},
"timestamp": 1670328662.91
}
// ...
]
}
}
*/
val deserializedResponse: GetJobResultResponse<*> = JsonSerializer.deserialize<GetJobResultResponse<*>>(serializedResponse)
assertTrue((deserializedResponse.result as List<*>).first() is TotalStationPoint)
assertEquals(getJobResultResponse, deserializedResponse)
}
private fun loadTotalStationData(): List<TotalStationPoint> {
// Loads and parses a CSV file to List<TotalStationData>
// ...
}
}
The issue is that deserializedResponse.result
is deserialized as ArrayList<Map<String, String>>
where each former TotalStationPoint
is now a map of attributes:
Instead, I want the list to be deserialized as List<TotalStationPoint>
again, like the input object was:
I know that I can build a custom solution to this problem, but before resorting to a custom solution, I would like to know if Jackson-databind has a more idiomatic way to solve this.
2
Answers
So, I ended up modifying the JSON a little to include more type information about the
result
object. To explain the solution, I need to explain some more project-related context:The
result
value inGetJobResultResponse
is actually generated by processing jobs which run over a long time (possibly hours) on the server and eventually return a value, in this case aList<TotalStationPoint>
. For different reasons, eachJob
has to specify theClass<*>
of the object it returns and if it returns aList
, it also has to specify theClass<*>
of the elements contained in the list. So, the server already knows theClass<*>
of the elements in the list, it merely needs to be transmitted to the client and taken into account during deserialization. I therefore modified the request and server like so:GetJobResultResponse
therefore now also includes the canonical class name of the list element class. I then modified the deserialization method like so, such that it creates aTypeReference
based on the additional information and then usesmapper.convertValue(result, typeReference)
to convert theresult
properly:You lose too much type information, that’s the main problem.
However, if you can allow a subclass on
GetJobResultResponse
then sufficient type information is passed:working solution: