NonEmptyList[A] 的自定义喷雾格式器

Custom Spray Formatter for NonEmptyList[A]

我正在尝试为我的 NonEmptyList[A]:

编写自定义 JSON 格式化程序
package net

import spray.json._
import Foo.NonEmptySeq

class NonEmptyCustomFormatter[A](implicit ev: A => JsValue) 
    extends JsonFormat[NonEmptySeq[A]] {
  override def read(json: JsValue): NonEmptySeq[A] = 
    ???

  override def write(xs: NonEmptySeq[A]): JsValue = {
    val values: Seq[JsValue] = Foo.toSeq[A](xs).map(ev(_))
    JsArray( values: _* )
  }
}

object Foo {

    type NonEmptySeq[A] = (A, Seq[A])

    def toSeq[A](neq: NonEmptySeq[A]): Seq[A] = 
        neq._1 +: neq._2

    implicit def stringToJsValue(x: String): JsValue = JsString(x)
}

REPL 示例:

scala> import spray.json._
import spray.json._

scala> import net._
import net._

scala> import net.Foo._
import net.Foo._

scala> implicit object NonEmptyStringList extends NonEmptyCustomFormatter[String]
defined object NonEmptyStringList

scala> val xs: NonEmptySeq[String] = ("foo", Nil)
xs: net.Foo.NonEmptySeq[String] = (foo,List())

scala> xs.toJson
res0: spray.json.JsValue = ["foo"]

接下来我要实现read方法,目前定义为???.

如果我有一个非通用的,即 String-specific 格式化程序,那么我可以简单地在 JsString 上进行模式匹配,如果它不是 JsString,则返回 deserializationError .

但是,由于 JsArray 可以有 0 个或多个 JsValue 元素,我必须求助于反射吗?基本上,我想定义另一个 (implicit ev2: JsValue => A) without reflection.

我该怎么做?

编辑

我将 ev2 类型签名从 JsValue => Option[A] 更改为 JsValue => A - 因为将抛出异常(据我所知,根据喷射约定)以指示反序列化失败。

我不确定您是否需要使用反射。您需要的是 A 的 JsonFormat 实例,并用它组成您的 NonEmptySeq 格式。然后你可以使用 toJsonconvertTo[A]

例如:

class NonEmptyCustomFormatter[A : JsonFormat] extends JsonFormat[NonEmptySeq[A]] {

  override def read(json: JsValue): NonEmptySeq[A] = json match {
     case JsArray(Vector(value, values @ _*)) => 
       (value.converTo[A], values.map(_.convertTo[A]))
  }

  override def write(xs: NonEmptySeq[A]): JsValue =
    JsArray(Foo.toSeq[A](xs).map(_.toJson): _*)
}

我建议你使用 spray 的 seq 格式来继承已经实现的功能,并在其之上放置你自己的逻辑。

首先调用 implicitly[JsonFormat[Seq[A]]]Seq[A] 获取 json 格式化程序。编写 NonEmptySeq[A] 很简单,因为您知道如何编写 Seq[A] 以及如何将 NonEmptySeq[A] 转换为 Seq[A]。读取NonEmptySeq[A]时,首先读取为Seq[A],然后验证它至少包含一个元素。如果它通过验证,你得到一个 NonEmptySeq[A],如果不是,给出一个 DeserializationError。

class NonEmptySeqFormat[A : JsonFormat] extends JsonFormat[NonEmptySeq[A]] {

  // this is implemented by spray developers
  val seqFormat = implicitly[JsonFormat[Seq[A]]]

  // read json as Seq[A] then check if it contains at least 1 element
  def read(json: JsValue): NonEmptySeq[A] =
    seqFormat.read(json) match {
      case x +: xs => (x, xs)
      case e => deserializationError("There should be at least one element, but got " + e)
    }

  // convert NonEmptySeq to Seq and then write it as json
  def write(xs: NonEmptySeq[A]): JsValue =
    seqFormat.write(Foo.toSeq(xs))

}