(Не) маршалл JSON с именованным корнем для данных Ember с использованием класса случая Scala в Spray

Я пишу интерфейс RESTful, и я хотел бы маршалировать и демаршаллировать JSON, готовый для Ember Data. Проблема в том, что Ember Data хочет имя объекта, а две библиотеки, которые я пробовал, spray-json и json4s, похоже, не справляются с этим легко.

Желаемый формат Ember Data

{
  "coursePhoto": {
    "photoId": 1
  }
}

Текущий формат по умолчанию:

{"photoId":15}

Это должно происходить из класса case:

case class CoursePhoto(photoId: Long)

Я запустил его со следующим пользовательским кодом:

object PtolemyJsonProtocol extends DefaultJsonProtocol {
  implicit object CoursePhotoFormat extends RootJsonFormat[CoursePhoto] {
  def write(cp: CoursePhoto) =
    JsObject("CoursePhoto" -> JsObject("photoId" -> JsNumber(cp.photoId)))  
  def read(value: JsValue) = value match {
    case coursePhotoJsObject: JsObject => {
      CoursePhoto(coursePhotoJsObject.getFields("CoursePhoto")(0).asJsObject
      .getFields("photos")(0).asInstanceOf[JsArray].elements(0)
      .asInstanceOf[JsNumber].value.toLong)
    }            
    case _ => deserializationError("CoursePhoto expected")
  }
}

Этот код кажется ужасающе хрупким и уродливым со всеми asInstanceOf и (0).

Учитывая, что я пишу в Spray с помощью Scala, какой хороший способ получить именованный корневой вывод JSON? Я вполне счастлив сделать это с любой библиотекой JSON, которая хорошо интегрируется со Spray и достаточно производительна.


person Sarge    schedule 07.06.2014    source источник


Ответы (2)


Решает ли следующее вашу проблему?

scala> import spray.json._
import spray.json._

scala> import DefaultJsonProtocol._
import DefaultJsonProtocol._

scala> case class CoursePhoto(photoId: Long)
defined class CoursePhoto

scala> case class CoursePhotoEmber(coursePhoto: CoursePhoto)
defined class CoursePhotoEmber

scala> implicit val jsonFormatCoursePhoto = jsonFormat1(CoursePhoto)
jsonFormatCoursePhoto: spray.json.RootJsonFormat[CoursePhoto] = spray.json.ProductFormatsInstances$$anon$1@6f5d66b6

scala> implicit val jsonFormatCoursePhotoEmber = jsonFormat1(CoursePhotoEmber)
jsonFormatCoursePhotoEmber: spray.json.RootJsonFormat[CoursePhotoEmber] = spray.json.ProductFormatsInstances$$anon$1@401a0d22

scala> """{ "coursePhoto": { "photoId": 1 } }""".parseJson.convertTo[CoursePhotoEmber]
res0: CoursePhotoEmber = CoursePhotoEmber(CoursePhoto(1))

scala> res0.toJson
res1: spray.json.JsValue = {"coursePhoto":{"photoId":1}}
person Christian    schedule 07.06.2014
comment
Ах! Теперь я понимаю, где меня запутали. Я пробовал обернутый подход, но был привязан к моей бизнес-логике, возвращающей CoursePhoto, но имея два JsonFormat[CoursePhoto] в области видимости. К сожалению, я не думал просто о создании CoursePhotoEmber. - person Sarge; 08.06.2014

Эта проблема заставила меня задуматься, можно ли сделать это повторно. Я считаю, что нашел разумный способ сделать это для нескольких типов.

object PtolemyJsonProtocol extends DefaultJsonProtocol {
  implicit val CoursePhotoFormat = new NamedRootFormat("CoursePhoto", jsonFormat1(CoursePhoto))
}
import PtolemyJsonProtocol._

class NamedRootFormat[T](rootName: String, delegate: RootJsonFormat[T]) extends RootJsonFormat[T] {
  def write(obj: T): JsValue = {
    JsObject((rootName, delegate.write(obj)))
  }
  def read(json: JsValue): T = json match {
    case parentObject: JsObject => {
      delegate.read(parentObject.getFields(rootName).head)
    }
    case _ => deserializationError("CoursePhoto expected")
  }
}
person Sarge    schedule 08.06.2014