Сериализация 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)