Сериализация spray-json в маршрутизации с использованием пользовательских форматов Json

Использование Spray with spray-json для системы, версия:

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

Я не могу понять, как заставить пользовательские определения JsonFormat работать для сериализации, которая обрабатывается с помощью спрей-маршрутизации.

У меня было два отдельных обстоятельства, которые потерпели неудачу.

1. Вложенные Классы Случая

Классификация JSON в базовом случае работала нормально

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

Однако, если у меня есть вложенный класс case в классе case, который нужно сериализовать, я могу решить проблемы компиляции, предоставив другой неявный 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

Здесь у меня есть черта, которая служит типом захвата для группы классов дел. Некоторые из расширяющих классов имеют значения, в то время как другие не имеют значений и являются объектами. Когда происходит сериализация, кажется, что мой неявный определенный JsonFormat полностью игнорируется, и я просто даю пустой JsObject, особенно когда фактический базовый тип был одним из объектов case без значений vals.

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, но, похоже, он вообще не использует мой метод записи, а вместо этого внезапно выяснил, как сериализовать самостоятельно.

РЕДАКТИРОВАТЬ

В конкретных случаях сериализации используется чтение / десериализация, например:

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

И написать / сериализацию с complete директивы.

Теперь я понимаю, благодаря первому ответу, опубликованному здесь, что мои реализации JsonDefaultProtocol и JsonFormat предназначены для классов spray-json, в то время как для извлечения директивы объекта в десериализации используется json4s JObject, а не JSObject типа spray-json.

2 ответа

Решение

Другой подход для чистого вывода 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)

Если вам нужны как чтение, так и запись, вы можете сделать это следующим образом:

  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)
Другие вопросы по тегам