假设您有一个如下所示的JSON:
[{"foo": 1, "bar": 2}, {"foo": 3, "bar": {"baz": 4}}]
尝试使用Scala sum类型表示这一点似乎很自然:
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
我的问题是:是否可以使用Jackson的Scala模块将上面的JSON序列化为List[Item]
?
我的尝试:
val string = "[{\"foo\": 1, \"bar\": 2}, {\"foo\": 3, \"bar\": {\"baz\": 4}}]"
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
mapper.readValue[List[Item]](string)
例外:
线程“main”中的异常com.fasterxml.jackson.databind.JsonMappingException:无法构造...的实例,问题:抽象类型需要映射到具体类型,具有自定义反序列化器,或者实例化为其他类型信息 在[来源:[{“foo”:1,“bar”:{“baz”:2}},{“foo”:3,“bar”:{“baz”:4}}]; line:1,column:2](通过引用链:com.fasterxml.jackson.module.scala.deser.BuilderWrapper [0])
这清楚地表明问题是什么,但我不确定如何最好地解决它。
答案 0 :(得分:0)
正如@Dima指出的那样,我认为不存在涵盖所有案例的通用解决方案。而且我不确定它是否可以存在,因为差异可能隐藏在任意深处,我怀疑有足够聪明的人可以从中创建halting problem。然而,许多具体案例都可以解决。
首先,如果你控制双方(序列化和反序列化),你应该考虑使用JsonTypeIdResolver
注释和一些TypeIdResolver
子类,这些子类将把类型的名称放在JSON本身。< / p>
如果您无法使用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树并找出应该解析的类型,第二次实际解析解析已知类型的对象。