Scala spray-json和spray-routing:如何调用JsonFormat-write-in-complete

Scala spray-json和spray-routing:如何调用JsonFormat-write-in-complete,scala,spray,spray-json,Scala,Spray,Spray Json,我试图弄清楚如何在使用routing指令complete时调用定制的JsonFormat write方法。使用JsonFormat帮助函数集创建的JsonFormat工作正常,但不会调用定义完整的JsonFormat sealed trait Error sealed trait ErrorWithReason extends Error { def reason: String } case class ValidationError(reason: String) extends Er

我试图弄清楚如何在使用routing指令complete时调用定制的JsonFormat write方法。使用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(“something”,“somethingelse”,EntityNotFound)

实际值

{
  "a": "something",
  "b": "somethingelse",
  "error": {}
}
预定的

{
  "a": "something",
  "b": "somethingelse",
  "error": "EntityNotFound"
}
我希望
complete(record)
使用隐式JsonFormat for record,这反过来又依赖于隐式对象ErrorJsonFormat,它指定了创建适当JsString字段的write方法。相反,它似乎都能识别提供的ErrorJsonFormat,而忽略了序列化的指令

我觉得应该有一个解决方案,它不需要将
隐式val record2Json=jsonFormat3(Record)
替换为显式
隐式对象RecordJsonFormat extensed JsonFormat[Record]{…}

总结一下我的问题

  • 为什么记录的序列化无法调用ErrorJsonFormat写入方法(它甚至做了什么?下面回答了
  • 在仍然使用
    complete(record)
    的情况下,是否有办法满足我的期望
编辑

在spray json源代码中,有一个sbt样板模板似乎定义了jsonFormat系列方法:

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)
  }
}
defjsonformat3[P1:JF,P2:JF,P3:JF,tt]:RootJsonFormat[T]={
val数组(p1、p2、p3)=提取字段名(类清单[T])
jsonFormat(构造、p1、p2、p3)
}
def jsonFormat[P1:JF,P2:JF,P3:JF,tt,fieldName1:String,fieldName2:String,fieldName3:String):RootJsonFormat[T]=新的RootJsonFormat[T]{
def写入(p:T)={
val fields=new collection.mutable.ListBuffer[(字符串,JsValue)]
fields.sizeHint(3*4)
fields++=productElement2Field[P1](fieldName1,p,0)
fields++=productElement2Field[P2](fieldName2,p,0)
fields++=productElement2Field[P3](fieldName3,p,0)
JsObject(字段:*)
}
def读取(值:JsValue)={
val p1V=fromField[P1](值,fieldName1)
val p2V=fromField[P2](值,fieldName2)
val p3V=fromField[P3](值,字段名3)
构造(p1v、p2v、p3v)
}
}
由此看来,jsonFormat3本身似乎很好(如果您跟踪productElement2Field,它会抓住writer并直接调用write)。问题一定是
complete(record)
根本不涉及JsonFormat,而是以某种方式交替封送对象

因此,这似乎回答了第1部分:为什么记录序列化无法调用ErrorJsonFormat写入方法(它甚至做了什么?。没有调用JsonFormat,因为通过其他方式完成封送处理


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

进一步挖掘之后,问题的根源似乎是代码中Json4s和Spray Json库的混淆。在试图追踪Json处理的各种元素的示例时,我没有意识到这两个库之间的分离,结果是代码混合了其中的一些,解释如下对意外行为的惩罚

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

import MyJsonProtocol._

trait TestRoute extends HttpService with SprayJsonSupport {
  path("testRoute") {
    val response: Record = getErrorRecord()
    complete(response)
  }
}
综上所述,答案更加明显

1:为什么记录的序列化无法调用ErrorJsonFormat写入方法(它甚至做了什么)?

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

  • 在仍然使用complete(record)的情况下,是否有办法满足我的期望?
  • 是的,使用SprayJsonSupport将在需要时使用隐式RootJsonReader和/或RootJsonWriter自动创建相关的解组器和/或封送器


    因此,使用SprayJsonSupport,它将看到由
    jsonFormat3(记录)
    complete(记录)
    定义的RootJsonWriter将按预期进行序列化。

    哪里有
    import MyJsonProtocol.\u
    编辑的路由片段以显式显示导入