I’m writing a incoming packet deserializer. All incoming packets is encoded in JSON, and its type depends on field “post_type”.
- If it’s “meta_event”, there must be a field called “meta_event_type”, which can be “lifecycle” or “heartbeat”.
- If it’s “lifecycle”, there must be a field called “sub_type”, which can be “enable”, “disable” or “connect”.
- If it’s “heartbeat”, there must be 2 fields called “status” and “interval” (millseconds).
- If it’s “message_event”, …
- Remaining many other situations …
Here are 2 legal packet strings:
"meta_event_type": "lifecycle",
"post_type": "meta_event"
<code>{
"sub_type": "connect",
"meta_event_type": "lifecycle",
"post_type": "meta_event"
}
</code>
{
"sub_type": "connect",
"meta_event_type": "lifecycle",
"post_type": "meta_event"
}
"meta_event_type": "heartbeat",
"post_type": "meta_event"
<code>{
"meta_event_type": "heartbeat",
"interval": 5000,
"status": {
"good": true,
...
},
"post_type": "meta_event"
}
</code>
{
"meta_event_type": "heartbeat",
"interval": 5000,
"status": {
"good": true,
...
},
"post_type": "meta_event"
}
I wrote corresponding POJO classes for them, and add a specific deserializers to choose sub-class deserializers based on some fields:
<code>@JsonDeserialize(using = EventDataDeserializer::class)
abstract val postType: String
object EventDataDeserializer : StdDeserializer<EventData>(EventData::class.java) {
private fun readResolve(): Any = EventDataDeserializer
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): EventData {
val node = p.readValueAsTree<ObjectNode>()
val mapper = p.codec as ObjectMapper
return when (val postType = node.getNotNull(POST_TYPE).asText()) {
META_EVENT -> node.deserializeTo<MetaEventData>(mapper)
else -> throw IllegalArgumentException("Unknown post type: $postType")
@JsonDeserialize(using = MetaEventDataDeserializer::class)
sealed class MetaEventData : EventData() {
abstract val metaEventType: String
data class LifecycleMetaEventData(
override val postType: String,
override val metaEventType: String,
// "enable", "disable" or "connect"
data class HeartbeatEventData(
override val postType: String,
override val metaEventType: String,
object MetaEventDataDeserializer : StdDeserializer<MetaEventData>(MetaEventData::class.java) {
private fun readResolve(): Any = MetaEventDataDeserializer
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): MetaEventData {
val node = p.readValueAsTree<ObjectNode>()
val mapper = p.codec as ObjectMapper
return when (val subType = node.getNotNull(META_EVENT_TYPE).asText()) {
LIFECYCLE -> node.deserializeTo<LifecycleMetaEventData>(mapper)
HEARTBEAT -> node.deserializeTo<HeartbeatEventData>(mapper)
else -> throw IllegalArgumentException("Unexpected sub type: $subType")
<code>@JsonDeserialize(using = EventDataDeserializer::class)
sealed class EventData {
abstract val postType: String
}
object EventDataDeserializer : StdDeserializer<EventData>(EventData::class.java) {
private fun readResolve(): Any = EventDataDeserializer
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): EventData {
val node = p.readValueAsTree<ObjectNode>()
val mapper = p.codec as ObjectMapper
return when (val postType = node.getNotNull(POST_TYPE).asText()) {
META_EVENT -> node.deserializeTo<MetaEventData>(mapper)
// ...
else -> throw IllegalArgumentException("Unknown post type: $postType")
}
}
}
@JsonDeserialize(using = MetaEventDataDeserializer::class)
sealed class MetaEventData : EventData() {
abstract val metaEventType: String
}
data class LifecycleMetaEventData(
// "meta_event"
override val postType: String,
// "lifecycle"
override val metaEventType: String,
// "enable", "disable" or "connect"
@JsonProperty(SUB_TYPE)
val subType: String
) : MetaEventData()
data class HeartbeatEventData(
// "meta_event"
override val postType: String,
// "heartbeat"
override val metaEventType: String,
@JsonProperty(STATUS)
val status: Any?,
@JsonProperty(INTERVAL)
val interval: Long
) : MetaEventData()
object MetaEventDataDeserializer : StdDeserializer<MetaEventData>(MetaEventData::class.java) {
private fun readResolve(): Any = MetaEventDataDeserializer
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): MetaEventData {
val node = p.readValueAsTree<ObjectNode>()
val mapper = p.codec as ObjectMapper
return when (val subType = node.getNotNull(META_EVENT_TYPE).asText()) {
LIFECYCLE -> node.deserializeTo<LifecycleMetaEventData>(mapper)
HEARTBEAT -> node.deserializeTo<HeartbeatEventData>(mapper)
else -> throw IllegalArgumentException("Unexpected sub type: $subType")
}
}
}
</code>
@JsonDeserialize(using = EventDataDeserializer::class)
sealed class EventData {
abstract val postType: String
}
object EventDataDeserializer : StdDeserializer<EventData>(EventData::class.java) {
private fun readResolve(): Any = EventDataDeserializer
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): EventData {
val node = p.readValueAsTree<ObjectNode>()
val mapper = p.codec as ObjectMapper
return when (val postType = node.getNotNull(POST_TYPE).asText()) {
META_EVENT -> node.deserializeTo<MetaEventData>(mapper)
// ...
else -> throw IllegalArgumentException("Unknown post type: $postType")
}
}
}
@JsonDeserialize(using = MetaEventDataDeserializer::class)
sealed class MetaEventData : EventData() {
abstract val metaEventType: String
}
data class LifecycleMetaEventData(
// "meta_event"
override val postType: String,
// "lifecycle"
override val metaEventType: String,
// "enable", "disable" or "connect"
@JsonProperty(SUB_TYPE)
val subType: String
) : MetaEventData()
data class HeartbeatEventData(
// "meta_event"
override val postType: String,
// "heartbeat"
override val metaEventType: String,
@JsonProperty(STATUS)
val status: Any?,
@JsonProperty(INTERVAL)
val interval: Long
) : MetaEventData()
object MetaEventDataDeserializer : StdDeserializer<MetaEventData>(MetaEventData::class.java) {
private fun readResolve(): Any = MetaEventDataDeserializer
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): MetaEventData {
val node = p.readValueAsTree<ObjectNode>()
val mapper = p.codec as ObjectMapper
return when (val subType = node.getNotNull(META_EVENT_TYPE).asText()) {
LIFECYCLE -> node.deserializeTo<LifecycleMetaEventData>(mapper)
HEARTBEAT -> node.deserializeTo<HeartbeatEventData>(mapper)
else -> throw IllegalArgumentException("Unexpected sub type: $subType")
}
}
}
Functions getNotNull
and deserializeTo<T>
are some useful extension methods:
<code>fun JsonNode.getOptionalNullable(key: String) = get(key)
fun JsonNode.getOptionalNotNull(key: String) = when (this) {
if (result === null && contains(key)) {
throw NullPointerException("The value of key '$key' is null.")
else -> throw IllegalStateException("The node is not an object node.")
fun JsonNode.getNullable(key: String) = when (this) {
if (result === null && !contains(key)) {
throw NoSuchElementException("The value of key '$key' doesn't present.")
else -> throw IllegalStateException("The node is not an object node.")
fun JsonNode.getNotNull(key: String) =
getOptionalNotNull(key) ?: throw NullPointerException("The value of key '$key' is null.")
fun <T> JsonNode.deserializeTo(objectMapper: ObjectMapper, type: TypeReference<T>) = objectMapper.convertValue(this, type)
inline fun <reified T> JsonNode.deserializeTo(objectMapper: ObjectMapper) = deserializeTo(objectMapper, object : TypeReference<T>() {})
<code>fun JsonNode.getOptionalNullable(key: String) = get(key)
fun JsonNode.getOptionalNotNull(key: String) = when (this) {
is ObjectNode -> {
val result = get(key)
if (result === null && contains(key)) {
throw NullPointerException("The value of key '$key' is null.")
}
result
}
else -> throw IllegalStateException("The node is not an object node.")
}
fun JsonNode.getNullable(key: String) = when (this) {
is ObjectNode -> {
val result = get(key)
if (result === null && !contains(key)) {
throw NoSuchElementException("The value of key '$key' doesn't present.")
}
result
}
else -> throw IllegalStateException("The node is not an object node.")
}
fun JsonNode.getNotNull(key: String) =
getOptionalNotNull(key) ?: throw NullPointerException("The value of key '$key' is null.")
fun <T> JsonNode.deserializeTo(objectMapper: ObjectMapper, type: TypeReference<T>) = objectMapper.convertValue(this, type)
inline fun <reified T> JsonNode.deserializeTo(objectMapper: ObjectMapper) = deserializeTo(objectMapper, object : TypeReference<T>() {})
</code>
fun JsonNode.getOptionalNullable(key: String) = get(key)
fun JsonNode.getOptionalNotNull(key: String) = when (this) {
is ObjectNode -> {
val result = get(key)
if (result === null && contains(key)) {
throw NullPointerException("The value of key '$key' is null.")
}
result
}
else -> throw IllegalStateException("The node is not an object node.")
}
fun JsonNode.getNullable(key: String) = when (this) {
is ObjectNode -> {
val result = get(key)
if (result === null && !contains(key)) {
throw NoSuchElementException("The value of key '$key' doesn't present.")
}
result
}
else -> throw IllegalStateException("The node is not an object node.")
}
fun JsonNode.getNotNull(key: String) =
getOptionalNotNull(key) ?: throw NullPointerException("The value of key '$key' is null.")
fun <T> JsonNode.deserializeTo(objectMapper: ObjectMapper, type: TypeReference<T>) = objectMapper.convertValue(this, type)
inline fun <reified T> JsonNode.deserializeTo(objectMapper: ObjectMapper) = deserializeTo(objectMapper, object : TypeReference<T>() {})
But StackOverflowError will be thrown when deserializing the first example json, because in MetaEventDataDeserializer
, LIFECYCLE -> node.deserializeTo<LifecycleMetaEventData>(mapper)
will make Jackson use MetaEventDataDeserializer
to deserialize the node, instead of the default deserializer of LifecycleMetaEventData
.
How to solve it gracefully?