我试图弄清楚如何在使用路由指令完成时调用自定义 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)
感觉错误。