播放 - 使用Generic Crud定制Json

时间:2015-02-06 16:55:59

标签: json scala generics playframework

我的应用程序中有一个Crud控制器,它与JsonInception完美配合,但是使用自定义json转换器失败了。

按照给定的案例类:

case class Validity(id: Option[UUID], objectType: String, since: DateTime, until: DateTime, objectId: UUID, validityTargetId: UUID, validityTargetType: String)

我有一个对象伴侣如下:

object Validity extends CrudObject[Validity] {
  implicit val reads = Json.reads[Validity]
  implicit val writes = Json.writes[Validity]
}

我的CrudObject是具有给定代码的特征:

trait CrudObject[T] {
  val reads: Reads[T]
  val writes: Writes[T]
}

这是必要的,因为我正在使用Generic Crud。没有这个,Play无法找到任何隐式转换器。

所以我的Generic Crud Controller就是这样的:

trait CrudController[T] extends Controller {

  def service: ServiceModule[T]
  def companion: CrudObject[T]

  def search...
  def insert...

  implicit def reads: Reads[T] = companion.reads
  implicit def writes: Writes[T] = companion.writes

对于每个控制器,我都有以下内容:

object ValidityController extends CrudController[Validity] {
  override def service: GenericServiceModule[Validity] = ServiceModule
  override def companion: CrudObject[Validity] = Validity
}

好的,正如我所说的那样,考虑到这种设计,完美无缺,我需要设计一个自定义的json转换器。是的,我知道,这很简单,但事情正在发生,我不知道如何摆脱它。

现在我正在尝试执行以下操作:

implicit val reads: Reads[Validity] = (
      (JsPath \ "id").read[String] and
      (JsPath \ "objectType").read[String] and
      (JsPath \ "since").read[Long] and
      (JsPath \ "until").read[Long] and
      (JsPath \ "objectId").read[String] and
      (JsPath \ "validityTargetId").read[String] and
      (JsPath \ "validityTargetType").read[String]
    )(unlift(Validity.apply _))

它给了我:

Type mismatch, expected: (NotInferedA) => Option[NotInferedB], actual: (CrudObject[Nothing]) => CrudObject[Nothing]

我相信这是发生的事情因为CrudObject是一个特质而且没有申请和取消申请。

无论如何,删除CrudObject会给我一个类似的错误:

Type mismatch, expected: (NotInferedA) => Option[NotInferedB], actual: (Option[UUID], String, DateTime, DateTime, UUID, UUID, String) => Validity

但即使我能解决这个问题,我也无法想象因为我的GenericCrud而没有CrudObject。

有什么想法吗?

PS:感谢@ m-z,他通过stackoverflow给我一些帮助=)

更新

我差不多采用这种方法:

implicit object validityFormat extends Format[Validity] {
    override def writes(o: Validity): JsValue = {
      Json.obj(
        "id" -> JsString(o.id.getOrElse(null).toString),
        "objectType" -> JsString(o.objectType),
        "since" -> JsString(o.since.toString),
        "until" -> JsString(o.since.toString),
        "objectId" -> JsString(o.objectId.toString),
        "validityTargetId" -> JsString(o.validityTargetId.toString),
        "validityTargetType" -> JsString(o.validityTargetType))
    }

    override def reads(json: JsValue): JsResult[Validity] = {
      JsSuccess(Validity(
        (json \ "id").as[Option[UUID]],
        (json \ "objectType").as[String],
        (json \ "since").as[DateTime],
        (json \ "until").as[DateTime],
        (json \ "objectId").as[UUID],
        (json \ "validityTargetId").as[UUID],
        (json \ "validityTargetType").as[String])
      )
    }
  }

使用这种方式,这与文档Scala Combinators不同,我没有得到上一个错误,这是好的,没有类型不匹配=)

现在我正在研究如何转换为UUID和DateTime。

更新

我做了一个有效的例子,但我接受了@ m-z答案,因为它的样板比我的少,但两者都运行良好。最大的区别是,在我的方法中,我需要为DateTime和UUID提供一些自定义转换器,而@ m-z方法则不需要!

implicit object UUIDFormatter extends Format[UUID] {
    override def reads(json: JsValue): JsResult[UUID] = {
      val uuid = json.validate[String]
      JsSuccess(UUID.fromString(uuid.get))
    }

    override def writes(o: UUID): JsValue = {
      JsString(o.toString)
    }
  }

  implicit object DateTimeFormatter extends Format[DateTime] {
    override def reads(json: JsValue): JsResult[DateTime] = {
      val datetime = json.validate[Long]
      JsSuccess(new DateTime(datetime.get))
    }

    override def writes(o: DateTime): JsValue = {
      JsNumber(o.getMillis)
    }
  }

  implicit object validityFormat extends Format[Validity] {
    override def writes(o: Validity): JsValue = {
      Json.obj(
        "id" -> JsString(o.id.getOrElse(null).toString),
        "objectType" -> JsString(o.objectType),
        "since" -> JsNumber(o.since.getMillis),
        "until" -> JsNumber(o.since.getMillis),
        "objectId" -> JsString(o.objectId.toString),
        "validityTargetId" -> JsString(o.validityTargetId.toString),
        "validityTargetType" -> JsString(o.validityTargetType))
    }

    override def reads(json: JsValue): JsResult[Validity] = {
      JsSuccess(Validity(
        (json \ "id").as[Option[UUID]],
        (json \ "objectType").as[String],
        (json \ "since").as[DateTime],
        (json \ "until").as[DateTime],
        (json \ "objectId").as[UUID],
        (json \ "validityTargetId").as[UUID],
        (json \ "validityTargetType").as[String])
      )
    }

2 个答案:

答案 0 :(得分:2)

这里有几个问题,但它们都与泛型无关,因为你正在处理具体类型Validity

首先,使用Reads组合子的最后一个参数应为(Validity.apply _)。您只能将unliftWrites一起使用。

其次,组合器中的类型必须映射到Validity类中的类型。

implicit val reads: Reads[Validity] = (
  (JsPath \ "id").readNullable[UUID] and    // readNullable reads to Option
  (JsPath \ "objectType").read[String] and
  (JsPath \ "since").read[DateTime] and
  (JsPath \ "until").read[DateTime] and
  (JsPath \ "objectId").read[UUID] and
  (JsPath \ "validityTargetId").read[UUID] and
  (JsPath \ "validityTargetType").read[String]
)(Validity.apply _)
{p {1}}和Reads已经存在

UUID,所以这应该可行。

同样,DateTime看起来像这样:

Writes[Validity]

答案 1 :(得分:1)

嗨我认为您的案例类有一个选项参数,您的Reads必须在可选参数中具有readnullable必须是这样的,我不确定UIID和String

case class Validity(id: Option[UUID], objectType: String, since: DateTime, until: DateTime, objectId: UUID, validityTargetId: UUID, validityTargetType: String)

implicit val reads: Reads[Validity] = (
      (JsPath \ "id").readNullable[UIID] and
      (JsPath \ "objectType").read[String] and
      (JsPath \ "since").read[Long] and
      (JsPath \ "until").read[Long] and
      (JsPath \ "objectId").read[String] and
      (JsPath \ "validityTargetId").read[String] and
      (JsPath \ "validityTargetType").read[String]
    )(unlift(Validity.apply _))