Generic Play json Formatter

时间:2017-09-01 18:20:30

标签: json scala playframework libs lagom

我有一个scala应用程序,并且有一个类似于

的案例类
case class SR(
  systemId: Option[String] = None,
  x: Map[Timestamp, CaseClass1] = Map.empty,
  y: Map[Timestamp, CaseClass2] = Map.empty,
  y: Map[Timestamp, CaseClass3] = Map.empty
)

现在我必须为SR案例类的属性x,y,z提供隐式读写JSON格式,如 -

  implicit val mapCMPFormat = new Format[Map[Timestamp, CaseClass1]] {
    def writes(obj: Map[Timestamp, CaseClass1]): JsValue =
      JsArray(obj.values.toSeq.map(Json.toJson(_)))
    def reads(jv: JsValue): JsResult[Map[Timestamp, CaseClass1]] = jv.validate[scala.collection.Seq[CaseClass1]] match {
      case JsSuccess(objs, path) => JsSuccess(objs.map(obj => obj.dataDate.get -> obj).toMap, path)
      case err: JsError => err
    }
  }
对于Y和Z来说同样如此,将来,我将在SR案例类中添加更多属性,如x,y,z,然后需要提供格式化。

那么我可以获得一些能够照顾所有类型的Generic Formater吗?

1 个答案:

答案 0 :(得分:1)

据我所知,实现这一目标的一种简单方法并不存在,即创建一个"默认"每个对象的读者都应该不难做到,例如:

case class VehicleColorForAdd(
  name: String,
  rgb: String
)

object VehicleColorForAdd {
  implicit val jsonFormat: Format[VehicleColorForAdd] = Json.formats[VehicleColorForAdd]
}

这样你就可以通过简单地使用对象来访问隐式,所以你可以让包含这个对象的其他对象没有问题:

case class BiggerModel(
  vehicleColorForAdd: VehicleColorForAdd
)

object BiggerModel{
  implicit val jsonFormat: Format[BiggerModel] = Json.format[BiggerModel]
}

可悲的是,您需要为每个班级类型执行此操作,但您可以"扩展"例如,使用您自己的播放转换器,这是我的一些默认读卡器:

package common.json

import core.order.Order
import org.joda.time.{ DateTime, LocalDateTime }
import org.joda.time.format.DateTimeFormat
import core.promotion.{ DailySchedule, Period }
import play.api.libs.functional.syntax._
import play.api.libs.json.Reads._
import play.api.libs.json._
import play.api.libs.json.{ JsError, JsPath, JsSuccess, Reads }

import scala.language.implicitConversions

/**
 * General JSon readers and transformations.
 */
object JsonReaders {

  val dateTimeFormat = "yyyy-MM-dd HH:mm:ss"

  class JsPathHelper(val path: JsPath) {
    def readTrimmedString(implicit r: Reads[String]): Reads[String] = Reads.at[String](path)(r).map(_.trim)

    def readUpperString(implicit r: Reads[String]): Reads[String] = Reads.at[String](path)(r).map(_.toUpperCase)

    def readNullableTrimmedString(implicit r: Reads[String]): Reads[Option[String]] = Reads.nullable[String](path)(r).map(_.map(_.trim))
  }

  implicit val localDateTimeReader: Reads[LocalDateTime] = Reads[LocalDateTime]((js: JsValue) =>
    js.validate[String].map[LocalDateTime](dtString =>
      LocalDateTime.parse(dtString, DateTimeFormat.forPattern(dateTimeFormat))))

  val localDateTimeWriter: Writes[LocalDateTime] = new Writes[LocalDateTime] {
    def writes(d: LocalDateTime): JsValue = JsString(d.toString(dateTimeFormat))
  }

  implicit val localDateTimeFormat: Format[LocalDateTime] = Format(localDateTimeReader, localDateTimeWriter)

  implicit val dateTimeReader: Reads[DateTime] = Reads[DateTime]((js: JsValue) =>
    js.validate[String].map[DateTime](dtString =>
      DateTime.parse(dtString, DateTimeFormat.forPattern(dateTimeFormat))))

  implicit def toJsPathHelper(path: JsPath): JsPathHelper = new JsPathHelper(path)

  val defaultStringMax: Reads[String] = maxLength[String](255)

  val defaultStringMinMax: Reads[String] = minLength[String](1) andKeep defaultStringMax

  val rgbRegex: Reads[String] = pattern("""^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$""".r, "error.invalidRGBPattern")

  val plateRegex: Reads[String] = pattern("""^[\d\a-zA-Z]*$""".r, "error.invalidPlatePattern")

  val minOnlyWordsRegex: Reads[String] = minLength[String](2) keepAnd onlyWordsRegex

  val positiveInt: Reads[Int] = min[Int](1)

  val zeroPositiveInt: Reads[Int] = min[Int](0)

  val zeroPositiveBigDecimal: Reads[BigDecimal] = min[BigDecimal](0)

  val positiveBigDecimal: Reads[BigDecimal] = min[BigDecimal](1)

  def validLocalDatePeriod()(implicit reads: Reads[Period]) =
    Reads[Period](js => reads.reads(js).flatMap { o =>
      if (o.startPeriod isAfter o.endPeriod)
        JsError("error.startPeriodAfterEndPeriod")
      else
        JsSuccess(o)
    })

  def validLocalTimePeriod()(implicit reads: Reads[DailySchedule]) =
    Reads[DailySchedule](js => reads.reads(js).flatMap { o =>
      if (o.dailyStart isAfter o.dailyEnd)
        JsError("error.dailyStartAfterDailyEnd")
      else
        JsSuccess(o)
    })
}

然后,您只需要导入此对象即可访问所有这些隐式转换器:

package common.forms

import common.json.JsonReaders._
import play.api.libs.json._

/**
 * Form to add a model with only one string field.
 */
object SimpleCatalogAdd {

  case class Data(
    name: String
  )

  implicit val dataReads: Reads[Data] = (__ \ "name").readTrimmedString(defaultStringMinMax).map(Data.apply)
}