kotlin braid client doesn't cope with jackson type information
Currently, the BraidProxy
class serialises JsonRequest
s using vertx's Json
class which wraps ObjectMapper
.
Today i've been struggling with serialisation and deserialisation of a few kotlin classes. There are two halves to this the net result of which is:
- we need to switch to using the kotlin object mapper (for deserialisation of some data classes with defaults)
- we need to not convert things to a list to serialise as the type information is lost, along with the the corresponding serialisation type information - particularly in JsonRequests (where we do this with the parameters)
The first part is because the java ObjectMapper blows up if you have data classes with defaults.
The second part is quite interesting. I get errors like:
java.lang.RuntimeException: Missing type id when trying to resolve subtype of [simple type, class io.bluebank.braid.server.service.ModelData]: missing type id property 'type'
at [Source: UNKNOWN; line: -1, column: -1]
This is due to the type information put in by JsonTypeInfo not being preserved if we have a list of params of type Object (which we do when we create he JsonReqest). I have something like the below:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes(
JsonSubTypes.Type(value = MeteringModelData::class, name = "MeteringModelData")
)
interface ModelData
data class MeteringModelData(val someString: String) : ModelData {
// BART PUT YOUR STUFF HERE...
}
And the following tests explain what's going on:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.vertx.core.json.Json
import org.junit.Test
class JsonTests {
val mapper = jacksonObjectMapper()
val om = ObjectMapper()
@Test
fun wtfIsGoingOn() {
val mdma = MeteringModelData("wibbble")
println(om.writeValueAsString(mdma)) // {"type":"MeteringModelData","someString":"wibbble"}
println(Json.encode(mdma)) // {"type":"MeteringModelData","someString":"wibbble"}
val daftList = listOf("something", mdma)
// all of these output ["something",{"someString":"wibbble"}]
println(om.writeValueAsString(daftList))
println(Json.encode(daftList))
println(mapper.writeValueAsString(daftList))
// this outputs
// "something"
// {"type":"MeteringModelData","someString":"wibbble"}
print("something", mdma)
// the below blows up if we deserialise using anything other than mapper
val daoState = DaoState("ben", emptySet(), DaoKey("bendao"))
daoState.addOrReplaceModelData(MeteringModelData("hello"))
val dsJson = mapper.writeValueAsString(daoState)
println(dsJson)
val dsRehydrated = mapper.readValue<DaoState>(dsJson)
println(dsRehydrated.containsModelData(MeteringModelData::class))
}
fun print(vararg params: Any?) {
params.forEach { println(om.writeValueAsString(it)) }
}
}
Edited by ben