Hello :wave: I got a requirement for my server to ...
# jackson-kotlin
d
Hello 👋 I got a requirement for my server to accept either a single JSON object or an array of those so I modeled it using a sealed class
Copy code
sealed class MyServerRequest

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
data class SingleRequest(val request: String) : MyServerRequest()

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
class BatchRequest @JsonCreator constructor(@get:JsonValue val requests: List<SingleRequest>) : MyServerRequest()
Between those two there is no extra
type
field that I could use to detect correct type using
@JsonTypeInfo/@JsonSubTypes
. Instead, I parse the incoming requests to a
JsonNode
and check whether it is an array to determine appropriate target, i.e.
Copy code
val jsonNode = rawRequest.bodyToMono(JsonNode::class.java).awaitFirst()
if (jsonNode.isArray) {
  objectMapper.treeToValue(jsonNode, BatchRequest::class.java)
} else {
  objectMapper.treeToValue(jsonNode, SingleRequest::class.java)
}
the above works fine but I'd like to simplify it and use custom deserializer to handle it, i.e.
Copy code
@JsonDeserialize(using = MyRequestDeserializer::class)
sealed class MyServerRequest

class MyRequestDeserializer : JsonDeserializer<MyServerRequest>() {
    override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): MyServerRequest {
        val codec = parser.codec
        val jsonNode = codec.readTree<JsonNode>(parser)
        return if (jsonNode.isArray) {
            codec.treeToValue(jsonNode, SingleRequest::class.java)
        } else {
            codec.treeToValue(jsonNode, BatchRequest::class.java)
        }
    }
}
this though fails with stackoverflow error as sealed class implementations look up the root deserializer... any ideas how to make it work?
https://github.com/FasterXML/jackson-databind/pull/2813 addresses this -> below works without custom desereializer
Copy code
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes(value = [JsonSubTypes.Type(SingleRequest::class), JsonSubTypes.Type(BatchRequest::class)])
sealed class MyServerRequest
d
Interesting, yes, I would expect this to have some connection to jackson-databind
Would you make a test case to demonstrate? You can either make one in an issue or a pull request against https://github.com/fasterxml/jackson-module-kotlin
👍 1
d
actually the above only works for single type, it blows up on the list
Copy code
Unexpected token (`JsonToken.START_OBJECT`), expected `JsonToken.VALUE_STRING`: need JSON String that contains type id
so it looks like it doesn't like the
@JsonCreator
annotation
fyi -> using
@JsonTypeInfo/@JsonSubTypes
messes up with custom serialization/deserialization using
@JsonCreator/@JsonValue
as it attempts to add type information during serialization and read type during deserialization
so cannot use those annotations together
went back with the custom serializer -> in order to avoid infinite loop that causes stack overflow had to add
@JsonDeserialize(using = JsonDeserializer.None::class)
on both implementing classes