1

我试图弄清楚如何在使用路由指令完成时调用自定义 JsonFormat 写入方法。使用 jsonFormat 帮助函数集创建的 JsonFormat 可以正常工作,但不会调用定义完整的 JsonFormat。

sealed trait Error
sealed trait ErrorWithReason extends Error {
  def reason: String
}

case class ValidationError(reason: String) extends ErrorWithReason
case object EntityNotFound extends Error
case class DatabaseError(reason: String) extends ErrorWithReason

case class Record(a: String, b: String, error: Error)

object MyJsonProtocol extends DefaultJsonProtocol {
  implicit object ErrorJsonFormat extends JsonFormat[Error] {
    def write(err: Error) = failure match {
      case e: ErrorWithReason => JsString(e.reason)
      case x => JsString(x.toString())
    }
    def read(value: JsValue) = {
      value match {
        //Really only intended to serialize to JSON for API responses, not implementing read
        case _ => throw new DeserializationException("Can't reliably deserialize Error")
      }
    }
  }

  implicit val record2Json = jsonFormat3(Record)
}

然后是这样的路线:

import MyJsonProtocol._

trait TestRoute extends HttpService with Json4sSupport {
  path("testRoute") {
    val response: Record = getErrorRecord()
    complete(response)
  }
}

如果我添加日志记录,我可以看到 ErrorJsonFormat.write 方法永远不会被调用。

后果如下所示,显示了我想要获得的输出和实际获得的输出。假设 Record 实例是 Record("something", "somethingelse", EntityNotFound)

实际的

{
  "a": "something",
  "b": "somethingelse",
  "error": {}
}

故意的

{
  "a": "something",
  "b": "somethingelse",
  "error": "EntityNotFound"
}

我期待complete(record)使用隐式 JsonFormat for Record ,这又依赖于隐式对象 ErrorJsonFormat ,该对象指定创建适当 JsString 字段的写入方法。相反,它似乎既能识别提供的 ErrorJsonFormat 而忽略其序列化指令。

我觉得应该有一个不需要implicit val record2Json = jsonFormat3(Record)用显式替换的解决方案implicit object RecordJsonFormat extends JsonFormat[Record] { ... }

所以总结一下我在问什么

  • 为什么Record的序列化调用ErrorJsonFormat写入方法失败(它甚至做了什么?)在下面回答
  • 有没有办法在使用的同时满足我的期望complete(record)

编辑

翻遍spray-json源码,有一个sbt-boilerplate模板好像定义了jsonFormat系列方法:https ://github.com/spray/spray-json/blob/master/src/main/boilerplate/喷雾/json/ProductFormatsInstances.scala.template

和 jsonFormat3 的相关产品似乎是:

def jsonFormat3[P1 :JF, P2 :JF, P3 :JF, T <: Product :ClassManifest](construct: (P1, P2, P3) => T): RootJsonFormat[T] = {
  val Array(p1,p2,p3) = extractFieldNames(classManifest[T])
  jsonFormat(construct, p1, p2, p3)
}

def jsonFormat[P1 :JF, P2 :JF, P3 :JF, T <: Product](construct: (P1, P2, P3) => T, fieldName1: String, fieldName2: String, fieldName3: String): RootJsonFormat[T] = new RootJsonFormat[T]{
  def write(p: T) = {
    val fields = new collection.mutable.ListBuffer[(String, JsValue)]
    fields.sizeHint(3 * 4)

    fields ++= productElement2Field[P1](fieldName1, p, 0)
    fields ++= productElement2Field[P2](fieldName2, p, 0)
    fields ++= productElement2Field[P3](fieldName3, p, 0)

    JsObject(fields: _*)
  }
  def read(value: JsValue) = {
    val p1V = fromField[P1](value, fieldName1)
    val p2V = fromField[P2](value, fieldName2)
    val p3V = fromField[P3](value, fieldName3)

    construct(p1v, p2v, p3v)
  }
}

由此看来, jsonFormat3 本身是非常好的(如果你追踪到 productElement2Field 它会抓住 writer 并直接调用 write)。那么问题必须是complete(record)根本不涉及 JsonFormat 并且以某种方式交替编组对象。

所以这似乎回答了第 1 部分:为什么 Record 的序列化无法调用 ErrorJsonFormat 写入方法(它甚至做了什么?)。没有 JsonFormat 被调用,因为通过其他方式完成编组。

剩下的问题似乎是是否可以为完整的指令提供一个编组器,如果 JsonFormat 存在,它将使用它,否则默认为其正常行为。我意识到我通常可以依赖默认编组器进行基本案例类序列化。但是当我得到一个像这个例子中那样复杂的特征/案例类设置时,我需要使用 JsonFormat 来获得正确的响应。理想情况下,对于编写路由的人来说,这种区别不必很明确,因为他们需要知道其默认编组器的情况,而不是需要调用 JsonFormat。或者换句话说,需要区分给定类型是否需要写成complete(someType)complete(someType.toJson)感觉错误。

4

1 回答 1

0

在进一步挖掘之后,问题的根源似乎是代码中的 Json4s 和 Spray-Json 库的混淆。在尝试追踪 JSON 处理的各种元素的示例时,我没有很容易地认识到这两个库之间的分离,最终得到的代码混合了其中的一些,解释了意外的行为。

在这个问题中,有问题的部分是在路由器中拉入 Json4sSupport。正确的定义应该是使用 SprayJsonSupport:

import MyJsonProtocol._

trait TestRoute extends HttpService with SprayJsonSupport {
  path("testRoute") {
    val response: Record = getErrorRecord()
    complete(response)
  }
}

考虑到这一切,答案就更明显了。

1:Record的序列化为什么调用ErrorJsonFormat写入方法失败(它甚至做了什么)?.

没有 JsonFormat 被调用,因为通过其他方式完成编组。另一种方法是 Json4s 隐式提供的编组和 Json4sSupport。您可以使用record.toJson强制spray-json 序列化对象,但输出不会是干净的(它将包括嵌套的JS 对象和“字段”键)。

  1. 有没有办法在仍然使用完整(记录)的同时满足我的期望?

是的,使用 SprayJsonSupport 将在需要时使用隐式 RootJsonReader 和/或 RootJsonWriter 来自动创建相关的 Unmarshaller 和/或 Marshaller。文档参考

因此,使用 SprayJsonSupport 它将看到由定义的 RootJsonWriterjsonFormat3(Record)并将complete(record)按预期进行序列化。

于 2014-09-22T17:04:53.423 回答