json4s 和 scala:在不指定所有字段的情况下创建自定义序列化程序

json4s and scala: creating custom serializer without specifying all fields

我有两种情况 classes 和以下格式的特征:

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

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

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
  }))

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

有什么方法可以避免指定 JObject 中的每个字段,而让非自定义序列化程序负责创建正确的大小写 class?例如

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

您不需要自定义序列化程序,但您需要一些自定义 TypeHints 来指定自定义 "kind" 字段和对象的 class 之间的映射。

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

中搜索 Serializing polymorphic Lists

最后我设法使用序列化程序解决了问题:

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