【问题标题】:spray-json serialization in spray-routing with custom JsonFormats使用自定义 JsonFormats 在喷雾路由中进行喷雾 json 序列化
【发布时间】:2014-11-13 08:56:14
【问题描述】:

将 Spray 与 spray-json 一起用于系统,版本:

"io.spray" %% "spray-json" % "1.2.6"

我不知道如何让自定义 JsonFormat 定义为喷雾路由处理的序列化工作。

我有两种不同的情况都失败了。

1.嵌套案例类

基本案例类 JSON 序列化工作正常

case class Something(a: String, b: String)
implicit val something2Json = jsonFormat3(Something)

但是,如果我在要序列化的案例类中有一个嵌套的案例类,我可以通过提供另一个隐式 JsonFormat 来解决编译问题,但在运行时它拒绝序列化

case class Subrecord(value: String)
case class Record(a: String, b: String, subrecord: Subrecord)

object MyJsonProtocol extends DefaultJsonProtocol {
  implicit object SubrecordJsonFormat extends JsonFormat[Subrecord] {
    def write(sub: Subrecord) = JsString(sub.value)
    def read(value: JsValue) = value match {
      case JsString(s) => Subrecord(s)
      case _ => throw new DeserializationException("Cannot parse Subrecord")
    }
  }

  implicit val record2Json = jsonFormat3(Record)
}

这将在运行时抛出 MappingException,说明子记录没有可用值

2。具有各种 0-N 案例扩展的特征

在这里,我有一个 trait 可以作为一组案例类的捕获类型。一些扩展类有 vals,而另一些没有 vals 并且是对象。当发生序列化时,似乎我的隐式定义的 JsonFormat 被完全忽略了,我只是给出了一个空的 JsObject,特别是当实际的底层类型是没有 val 的 case 对象之一时。

sealed trait Errors
sealed trait ErrorsWithReason extends Errors {
  def reason: String
}

case class ValidationError(reason: String) extends ErrorsWithReason
case object EntityNotFound extends Errors
case class DatabaseError(reason: String) extends ErrorsWithReason

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

鉴于上述情况,如果实际被序列化的类型是 EntityNotFound,那么序列化就变成了 RootJsonFormat 变成了{}。如果它是一个 ErrorsWithReason,那么它会变成一个 RootJsonFormat 变成 { "reason": "somevalue" }。我可能对 JsonFormat 定义应该如何工作感到困惑,但它似乎根本没有使用我的 write 方法,而是突然想出了如何自行序列化。

编辑

具体的序列化案例是使用读/反序列化,如:

entity(as[JObject]) { json =>
  val extraction: A = json.extract[A]
}

并使用complete 指令进行写入/序列化。

感谢此处发布的第一个答案,我现在意识到我的 JsonDefaultProtocol 和 JsonFormat 实现是针对 spray-json 类的,同时反序列化中的实体指令提取使用的是 json4s JObject 而不是 spray-json JsObject。

【问题讨论】:

  • 您是否将您的隐式放在扩展 DefaultJsonProtocol 的对象中,然后从该对象导入成员?示例:github.com/spray/…
  • 一旦您可以使用 jsonprotocol 对象的扩展来序列化内部类。然后为外壳类创建一个对象,并将内部对象的成员导入其中。
  • 对不起,我将编辑问题以反映这一点,但是是的,我所有的隐含都在导入的 DefaultJsonProtocol 扩展中。

标签: scala spray spray-json


【解决方案1】:

另一种干净的 JSON 输出方法

  import spray.json._
  import spray.json.DefaultJsonProtocol._

  // #1. Subrecords
  case class Subrecord(value: String)
  case class Record(a: String, b: String, subrecord: Subrecord)

  implicit object RecordFormat extends JsonFormat[Record] {
    def write(obj: Record): JsValue = {
      JsObject(
        ("a", JsString(obj.a)),
        ("b", JsString(obj.b)),
        ("reason", JsString(obj.subrecord.value))
      )
    }

    def read(json: JsValue): Record = json match {
      case JsObject(fields)
        if fields.isDefinedAt("a") & fields.isDefinedAt("b") & fields.isDefinedAt("reason") =>
          Record(fields("a").convertTo[String],
            fields("b").convertTo[String],
            Subrecord(fields("reason").convertTo[String])
          )

      case _ => deserializationError("Not a Record")
    }

  }


  val record = Record("first", "other", Subrecord("some error message"))
  val recordToJson = record.toJson
  val recordFromJson = recordToJson.convertTo[Record]

  println(recordToJson)
  assert(recordFromJson == record)

【讨论】:

    【解决方案2】:

    如果你需要读写,你可以这样做:

      import spray.json._
      import spray.json.DefaultJsonProtocol._
    
      // #1. Subrecords
      case class Subrecord(value: String)
      case class Record(a: String, b: String, subrecord: Subrecord)
    
      implicit val subrecordFormat = jsonFormat1(Subrecord)
      implicit val recordFormat = jsonFormat3(Record)
    
      val record = Record("a", "b", Subrecord("c"))
      val recordToJson = record.toJson
      val recordFromJson = recordToJson.convertTo[Record]
    
      assert(recordFromJson == record)
    
      // #2. Sealed traits
    
      sealed trait Errors
      sealed trait ErrorsWithReason extends Errors {
        def reason: String
      }
    
      case class ValidationError(reason: String) extends ErrorsWithReason
      case object EntityNotFound extends Errors
      case class DatabaseError(reason: String) extends ErrorsWithReason
    
      implicit object ErrorsJsonFormat extends JsonFormat[Errors] {
        def write(err: Errors) = err match {
          case ValidationError(reason) =>
            JsObject(
            ("error", JsString("ValidationError")),
            ("reason", JsString(reason))
          )
          case DatabaseError(reason) =>
            JsObject(
              ("error", JsString("DatabaseError")),
              ("reason", JsString(reason))
            )
          case EntityNotFound => JsString("EntityNotFound")
        }
    
        def read(value: JsValue) = value match {
          case JsString("EntityNotFound") => EntityNotFound
          case JsObject(fields) if fields("error") == JsString("ValidationError") =>
             ValidationError(fields("reason").convertTo[String])
          case JsObject(fields) if fields("error") == JsString("DatabaseError") =>
            DatabaseError(fields("reason").convertTo[String])
        }
      }
    
      val validationError: Errors = ValidationError("error")
      val databaseError: Errors = DatabaseError("error")
      val entityNotFound: Errors = EntityNotFound
    
      assert(validationError.toJson.convertTo[Errors] == validationError)
      assert(databaseError.toJson.convertTo[Errors] == databaseError)
      assert(entityNotFound.toJson.convertTo[Errors] == entityNotFound)
    

    【讨论】:

    • 我对创建的实际 JSON 并不完全清楚。当我使用 toJson 时,我得到了不可用的凌乱 JSON 对象。假设记录不是包含子记录,而是错误标记为“原因”,并且我的实例返回一个数据库错误。所以案例实例Record("first", "other", DatabaseError("some error message")) 变为{ "fields": { "a": "first", "b": "other", "reason": { "value": "some error message" } }。我想要的是{"a": "first", "b": "other", "reason": "some error message" }
    • 你期望什么 JSON 作为输出?对我来说,它看起来完全有效且合法
    • 它是有效的 JSON,但我在这里编写了一个 API,当我试图表示资源时,到处都有所有这些额外的嵌套 JS 对象是虚假的。我确实在评论末尾显示了我期望的 JSON
    猜你喜欢
    • 2014-11-13
    • 2013-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-15
    • 1970-01-01
    • 2011-02-23
    相关资源
    最近更新 更多