播放 - 使用 Generic Crud 自定义 Json

Play - Custom Json with Generic Crud

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

按照给定的案例class:

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 无法找到任何隐式转换器。

所以我的通用 Crud 控制器是这样的:

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 通过 Whosebug 给我一些帮助 =)

更新

我几乎可以使用这种方法了:

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])
      )
    }

您好,我认为您的案例 class 有一个选项参数,您的 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 _))

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

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

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

中的类型
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 _)

Reads 已经存在 UUIDDateTime,所以这应该没问题。

同样,Writes[Validity] 看起来像这样:

implicit val writes: Writes[Validity] = (
  (JsPath \ "id").writeNullable[UUID] and
  (JsPath \ "objectType").write[String] and
  (JsPath \ "since").write[DateTime] and
  (JsPath \ "until").write[DateTime] and
  (JsPath \ "objectId").write[UUID] and
  (JsPath \ "validityTargetId").write[UUID] and
  (JsPath \ "validityTargetType").write[String]
)(unlift(Validity.unapply))