正如@Dima 指出的那样,我认为不存在涵盖所有情况的通用解决方案。此外,我不确定它是否存在,因为差异可能隐藏在任意深处,我怀疑足够聪明的人可以从中创建halting problem。但是可以解决很多具体情况。
首先,如果您控制双方(序列化和反序列化),您应该考虑使用带有TypeIdResolver 子类的JsonTypeIdResolver 注释,这些子类会将类型名称放入JSON 本身。
如果您不能使用JsonTypeIdResolver,可能唯一的解决方案是按照错误提示推出您的自定义JsonDeserializer。您在问题中提供的示例可以通过以下方式处理:
sealed trait Item
case class IntItem(foo: Int, bar: Int) extends Item
case class Baz(baz: Int)
case class BazItem(foo: Int, bar: Baz) extends Item
import com.fasterxml.jackson.core._
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.util.TokenBuffer
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.node._
import com.fasterxml.jackson.databind.exc._
import java.io.IOException
class ItemDeserializer() extends StdDeserializer[Item](classOf[Item]) {
@throws[IOException]
@throws[JsonProcessingException]
def deserialize(jp: JsonParser, ctxt: DeserializationContext): Item = {
// 1) Buffer current state of the JsonParser
// 2) Use firstParser (from the buffer) to parser whole sub-tree into a generic JsonNode
// 3) Analyze tree to find out the real type to be parser
// 3) Using the buffer roll back history and create objectParser to parse the sub-tree as known type
val tb = new TokenBuffer(jp, ctxt)
tb.copyCurrentStructure(jp)
val firstParser = tb.asParser
firstParser.nextToken
val curNode = firstParser.getCodec.readTree[JsonNode](firstParser)
val objectParser = tb.asParser
objectParser.nextToken()
val bar = curNode.get("bar")
if (bar.isInstanceOf[IntNode]) {
objectParser.readValueAs[IntItem](classOf[IntItem])
}
else if (bar.isInstanceOf[ObjectNode]) {
objectParser.readValueAs[BazItem](classOf[BazItem])
}
else {
throw ctxt.reportBadDefinition[JsonMappingException](classOf[Item], "Unknown subtype of Item") // Jackson 2.9
//throw InvalidDefinitionException.from(jp, "Unknown subtype of Item", ctxt.constructType(classOf[Item])) // Jackson 2.8
}
}
}
然后你可以像下面这样使用它
def test() = {
import com.fasterxml.jackson.module.scala._
import com.fasterxml.jackson.module.scala.experimental._
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
// add our custom ItemDeserializer
val module = new SimpleModule
module.addDeserializer(classOf[Item], new ItemDeserializer)
mapper.registerModule(module)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val string = "[{\"foo\": 1, \"bar\": 2}, {\"foo\": 3, \"bar\": {\"baz\": 4}}]"
val list = mapper.readValue[List[Item]](string)
println(list.mkString(", "))
}
打印出来的
IntItem(1,2), BazItem(3,Baz(4))
ItemDeserializer 中的主要技巧是使用TokenBuffer 解析 JSON 两次:第一次分析 JSON-tree 并找出它应该被解析为什么类型,第二次实际解析对象已知类型。