json4s和scala:创建自定义序列化程序而不指定所有字段

时间:2016-03-18 12:14:23

标签: json scala serialization json4s

我有两个案例类和一个特征,格式如下:

trait Parent
case class ChildClassOne(kind: String = "first_type", id: String) extends Parent
case class ChildClassTwo(kind: String = "second_type", id: String) extends Parent

另一个包含Parent s列表的案例类:

case class ParentResponse(total: Int, results: List[Parent])

基本上,json响应可能有一个对象列表,可以是ChildClassOneChildClassTwo类型。

因为这个(我认为)我需要创建一个自定义序列化器:

class ParentSerializer extends CustomSerializer[Parent](format => ( {
    case JObject(List(JField("kind", JString(kind)), JField("id", JString(id)))) 
        if kind == "first_type" => ChildClassOne(kind, id) 
    case JObject(List(JField("kind", JString(kind)), JField("id", JString(id)))) 
        if kind == "second_type" => ChildClassTwo(kind, id) 
  }, {
    case _ => null
  }))

这很好用。问题是这些对象可能会变得很大,我不想在自定义序列化程序中指定每个字段。我也没有以任何方式修改属性,并且使用自定义序列化程序只是为了根据kind字段返回正确类型的案例类。

有没有办法避免在JObject中指定每个字段,只是让非自定义序列化程序负责创建正确的案例类?例如

case JObject(List(JField("kind", JString(kind)))) 
    if kind == "first_type" => read[ChildClassOne](format)

2 个答案:

答案 0 :(得分:3)

您不需要自定义序列化程序,但需要一些自定义TypeHints来指定自定义“种类”字段与对象类之间的映射。

trait Parent
case class ChildClassOne(kind: String = "first_type", id: String) extends Parent
case class ChildClassTwo(kind: String = "second_type", id: String) extends Parent

case class ParentResponse(total: Int, results: List[Parent])


object MyTypeHints extends TypeHints {
  // map class to kind and viceversa
  val classToHint: Map[Class[_], String] = Map (
    classOf[ChildClassOne] -> "first_type",
    classOf[ChildClassTwo] -> "second_type"
  )
  val hintToClass = classToHint.map(_.swap)

  override val hints: List[Class[_]] = List(classOf[ChildClassOne], classOf[ChildClassTwo])
  override def classFor(hint: String): Option[Class[_]] = hintToClass.get(hint)
  override def hintFor(clazz: Class[_]): String = classToHint(clazz)
}

implicit val formats = Serialization.formats(MyTypeHints).withTypeHintFieldName("kind")

val obj = ParentResponse(2, List(ChildClassOne(id = "one"), ChildClassTwo(id = "two")))
val serialized = Serialization.write(obj)

val deserializedFromString = Serialization.read[ParentResponse](
  """{"total":2,"results":[{"kind":"first_type","kind":"first_type","id":"one"},
    {"kind":"second_type","kind":"second_type","id":"two"}]}""")

val deserializedFromSerialized = Serialization.read[ParentResponse](serialized)

assert(obj == deserializedFromString)
assert(obj == deserializedFromSerialized)

如果您不需要自定义类型提示字段,则可以使用默认字段。在readme

中搜索序列化多态列表

答案 1 :(得分:0)

我设法最终使用Serializer来解决问题:

trait Parent
case class ChildClassOne(kind: String = "first_type", id: String) extends Parent
case class ChildClassTwo(kind: String = "second_type", id: String) extends Parent

case class ParentResponse(total: Int, results: List[Parent])

class ParentSerializer extends Serializer[Parent] {
    private val ParentClass = classOf[Parent]
    implicit val formats = DefaultFormats

    def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Parent] = {
      case (TypeInfo(ParentClass, _), json) => json match {
        case JObject(JField("kind", JString(kind)) :: _) => kind match {
          case "first_type" => json.extract[ChildClassOne]
          case "second_type" => json.extract[ChildClassTwo]
        }

        case _ => throw new MappingException("Invalid kind")
      }
    }

    def serialize(implicit format: Formats): PartialFunction[Any, JValue] = Map()
  }

implicit val formats = DefaultFormats + new ParentSerializer