Как вы пишете CustomSerializer json4s, который обрабатывает коллекции

У меня есть класс, который я пытаюсь десериализовать, используя функциональность json4s CustomSerializer. Мне нужно сделать это из-за неспособности json4s десериализовать изменяемые коллекции.

Это базовая структура класса, который я хочу десериализовать (не беспокойтесь о том, почему класс устроен именно так):

case class FeatureValue(timestamp:Double)

object FeatureValue{
  implicit def ordering[F <: FeatureValue] = new Ordering[F] {
    override def compare(a: F, b: F): Int = {
      a.timestamp.compareTo(b.timestamp)
    }
  }
}

class Point {
  val features = new HashMap[String, SortedSet[FeatureValue]]

  def add(name:String, value:FeatureValue):Unit = {
    val oldValue:SortedSet[FeatureValue] = features.getOrElseUpdate(name, SortedSet[FeatureValue]())
    oldValue += value
  }
}

Json4s сериализует это просто отлично. Сериализованный экземпляр может выглядеть следующим образом:

{"features":
  {
   "CODE0":[{"timestamp":4.8828914447482E8}],
   "CODE1":[{"timestamp":4.8828914541333E8}],
   "CODE2":[{"timestamp":4.8828915127325E8},{"timestamp":4.8828910097466E8}]
  }
}

Я пытался написать собственный десериализатор, но я не знаю, как работать с хвостами списка. В обычном сопоставителе вы можете просто рекурсивно вызвать свою собственную функцию, но в этом случае функция анонимна и вызывается через API json4s. Я не могу найти никаких примеров, которые касаются этого, и я не могу понять это.

В настоящее время я могу сопоставить только один хэш-ключ и один экземпляр FeatureValue в его значении. Вот CustomSerializer в его нынешнем виде:

import org.json4s.{FieldSerializer, DefaultFormats, Extraction, CustomSerializer}
import org.json4s.JsonAST._

class PointSerializer extends CustomSerializer[Point](format => (
  {
    case JObject(JField("features", JObject(Nil)) :: Nil) => new Point
    case JObject(List(("features", JObject(List(
      (feature:String, JArray(List(JObject(List(("timestamp",JDouble(ts)))))))))
    ))) => {
      val point = new Point
      point.add(feature, FeatureValue(ts))
      point
    }
  },
  {
    // don't need to customize this, it works fine
    case x: Point => Extraction.decompose(x)(DefaultFormats + FieldSerializer[Point]())
  }
  ))

Если я попытаюсь перейти на использование формата разделенного списка ::, до сих пор я получал ошибки компилятора. Даже если бы я не получил ошибок компилятора, я не уверен, что бы я с ними делал.


person seanmk    schedule 09.11.2015    source источник


Ответы (1)


Вы можете получить список функций json в соответствии с шаблоном, а затем сопоставить этот список, чтобы получить Feature и их коды.

class PointSerializer extends CustomSerializer[Point](format => (
  {
    case JObject(List(("features", JObject(featuresJson)))) => 
      val features = featuresJson.flatMap { 
        case (code:String, JArray(timestamps)) =>
          timestamps.map { case JObject(List(("timestamp",JDouble(ts)))) =>
            code -> FeatureValue(ts)
          }
      }

      val point = new Point
      features.foreach((point.add _).tupled)
      point
  }, {
    case x: Point => Extraction.decompose(x)(DefaultFormats + FieldSerializer[Point]())
  }
))

Который десериализует ваш json следующим образом:

import org.json4s.native.Serialization.{read, write}
implicit val formats = Serialization.formats(NoTypeHints) + new PointSerializer

val json = """
{"features":
  {
   "CODE0":[{"timestamp":4.8828914447482E8}],
   "CODE1":[{"timestamp":4.8828914541333E8}],
   "CODE2":[{"timestamp":4.8828915127325E8},{"timestamp":4.8828910097466E8}]
  }
}
"""

val point0 = read[Point]("""{"features": {}}""")
val point1 = read[Point](json)

point0.features // Map()
point1.features 
// Map(
//   CODE0 -> TreeSet(FeatureValue(4.8828914447482E8)), 
//   CODE2 -> TreeSet(FeatureValue(4.8828910097466E8), FeatureValue(4.8828915127325E8)), 
//   CODE1 -> TreeSet(FeatureValue(4.8828914541333E8))
// )
person Peter Neyens    schedule 10.11.2015