json - 使用 Json4s 和自定义序列化器多态读取 JSON

标签 json scala serialization json4s

我有以下类层次结构:

object Calendar {
  trait DayType
  case object Weekday extends DayType
  case object Weekend extends DayType
  case object Holiday extends DayType
}

trait Calendar {
  def dateType(date: LocalDate): Calendar.DayType
}

class ConstantCalendar(dayType: Calendar.DayType) extends Calendar {
  override def dateType(date: LocalDate) = dayType
}

case object DefaultCalendar extends ConstantCalendar(Calendar.Weekday)

case class WeekdaysCalendar(defaults: Array[Calendar.DayType]) extends Calendar {
  override def dateType(date: LocalDate) = defaults(date.getDayOfWeek - 1)
}

case class CustomCalendar(defaultCalendar: Calendar = DefaultCalendar,
                     dates: Map[LocalDate, Calendar.DayType] = Map.empty)
    extends Calendar {
  private def defaultType(date: LocalDate) = defaultCalendar.dateType(date)
  private val dateMap = dates.withDefault(defaultType)

  override def dateType(date: LocalDate) = dateMap(date)
}

我定义了以下序列化器:

class JsonFormats(domainTypeHints: TypeHints,
                  domainCustomSerializers: List[Serializer[_]] = Nil,
                  domainFieldSerializers: List[(Class[_], FieldSerializer[_])] = Nil)
    extends DefaultFormats {
  override val typeHintFieldName = "type"
  override val typeHints         = domainTypeHints
  override val customSerializers = JodaTimeSerializers.all ++ domainCustomSerializers
  override val fieldSerializers  = domainFieldSerializers
}

class JsonCalendarSerializer extends CustomSerializer[CustomCalendar]( format => (
    {
      case JObject(JField("type", JString("CustomCalendar")) ::
                   JField("defaultCalendar", JString(defaultCalendar)) ::
                   JField("dates", dates) ::
                   Nil
                  ) =>
        CustomCalendar(defaultCalendar) // TODO dates
    },
    {
      case cal: CustomCalendar =>
        val dates = cal.dates.foldLeft(JObject()) { (memo, dt) =>
          dt match {
            case (d, t) => memo ~ (f"${d.getYear}%04d-${d.getMonthOfYear}%02d-${d.getDayOfMonth}%02d", t.toString)
          }
                                                  }
        ("type" -> "CustomCalendar") ~
        ("defaultCalendar" -> cal.defaultCalendar) ~
        ("dates" -> dates)
    }
    ))

implicit val jsonFormats = new JsonFormats(ShortTypeHints(List(Calendar.Weekday.getClass,
                                                               Calendar.Weekend.getClass,
                                                               Calendar.Holiday.getClass,
                                                               classOf[CustomCalendar])),
                                           new JsonCalendarSerializer :: Nil)

我必须创建一个自定义序列化器来解决以下事实:在 Json4 中,Map 键必须是字符串。

我有一个文件可能包含某些日历的数据,但我事先不知道它是什么日历类型。

当我尝试以下操作时:

val cal = CustomCalendar("default", Map(new LocalDate(2013, 1, 1) -> Calendar.Holiday))
val ser = Serialization.write(cal)
val cal2: Calendar = Serialization.read(ser)

我得到:

org.json4s.package$MappingException: Do not know how to deserialize 'CustomCalendar'
    at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$mkWithTypeHint(Extraction.scala:444)
    at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$result$6.apply(Extraction.scala:452)
    at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$result$6.apply(Extraction.scala:450)
    at org.json4s.Extraction$.org$json4s$Extraction$$customOrElse(Extraction.scala:462)
    at org.json4s.Extraction$ClassInstanceBuilder.result(Extraction.scala:450)
    at org.json4s.Extraction$.extract(Extraction.scala:306)
    at org.json4s.Extraction$.extract(Extraction.scala:42)
    at org.json4s.ExtractableJsonAstNode.extract(ExtractableJsonAstNode.scala:21)
    at org.json4s.jackson.Serialization$.read(Serialization.scala:50)

看来 Json4s 无法找到我的序列化器。

所以...有什么提示吗?或者如何让 Json4s 使用非字符串键序列化/反序列化 Map,或者如何使其工作?

谢谢!

最佳答案

最后我实现了 JsonCalendarSerializer,如下所示:

class JsonCalendarSerializer extends CustomSerializer[CustomCalendar]( format => (
    {
      case JObject(JField("defaults", JString(defaults)) ::
                   JField("dates", JObject(dateList)) ::
                   Nil
                  ) =>
        val dates = dateList map {
          case JField(dt, JString(t)) =>
            val tp = t match {
              case "Weekday" => Calendar.Weekday
              case "Weekend" => Calendar.Weekend
              case "Holiday" => Calendar.Holiday
            }

            (LocalDate.parse(dt), tp)
        }

        CustomCalendar(defaults, dates.toMap)
    },
    {
      case cal: CustomCalendar =>
        val dates = cal.dates.foldLeft(JObject()) { (memo, dt) =>
          dt match {
            case (d, t) => memo ~ (d.toString, t.toString)
          }
                                                  }
        (format.typeHintFieldName -> classOf[CustomCalendar].getSimpleName) ~
        ("defaults" -> cal.defaultCalendar) ~
        ("dates" -> dates)
    }
    ))

我从反序列化器中删除了 JField("type"...) 并修复了序列化器以调用 format.typeHintFieldNameclassOf[CustomCalendar]。 getSimpleName,这似乎解决了问题。

关于json - 使用 Json4s 和自定义序列化器多态读取 JSON,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21267491/

相关文章:

java - 使用 jackson-dataformat-xml 重写 ArrayList XML 序列化

java - 使用 Jersey 防止对象属性内的空值

Python:将整个 JSON 目录转换为 Python 字典以发送到 MongoDB

scala - 检查文件是否为 ORC 文件

scala - 通过 sbt 添加 Joda-Time 时出现编译错误,但如果我通过 ide 添加它则运行正常?

java - FindBugs 不会提示枚举中的不可序列化字段

python - 为什么我不能通过 id 更新嵌套序列化器?

javascript - 使用变量作为key解析json

javascript - 单击时在 JSON 数组选项之间切换

scala - 我想缩短 Akka Http 中的路由