使用类型参数在AST上播放-json

时间:2018-01-02 21:54:48

标签: scala playframework playframework-2.0 shapeless play-json

我正在尝试为一个基本上看起来像这个

的AST创建play-json读写
abstract sealed trait Rule[A] {
    def roomId: Option[Long] = None
    def valid(in: A): Boolean
}

abstract sealed trait ValueRule[A, B] extends Rule[A] {
    def value: B
}

abstract sealed trait NoValueRule[A] extends Rule[A]
case class OnlyDuringWorkHours(override val roomId: Option[Long] = None) extends NoValueRule[((ResStart, ResEnd), Center)] {
    override def valid(in: ((ResStart, ResEnd), Center)): Boolean = true
}

case class MaxLeadTime(override val roomId: Option[Long] = None, override val value: Int) extends ValueRule[ResStart, Int] {
    override def valid(in: ResStart): Boolean = true
}

case class MaxDuration(override val roomId: Option[Long] = None, override val value: String) extends ValueRule[(ResStart, ResEnd), String] {
    override def valid(in: (ResStart, ResEnd)): Boolean = true
}

case class Rules(centerId: Long, ruleList: Seq[Rule[_]])

我这样做的尝试就像这样

object Rule {
    implicit def ruleReads[R, V](implicit rReads: Reads[R], vReads: Reads[V] = null): Reads[Rule[R]] = {
        val theVRead = Option(vReads)
        val nvr = ???

        if (Option(theVRead).isDefined) {
            val vr = ???
            __.read[ValueRule[R, V]](vr).map(x => x.asInstanceOf[Rule[R]]).orElse(__.read[NoValueRule[R]](nvr).map(x => x.asInstanceOf[Rule[R]]))
        } else {
            __.read[NoValueRule[R]](nvr).map(x => x.asInstanceOf[Rule[R]])
        }
    }
    implicit def ruleWrites[R, V](implicit rWrites: Writes[R], vWrites: Writes[V] = null): Writes[Rule[R]] = Writes[Rule[R]]{
        case nv: NoValueRule[R] => Json.writes[NoValueRule[R]].writes(nv)
        case v: ValueRule[R, V] => Json.writes[ValueRule[R, V]].writes(v)
    }
}
object ValueRule {
    implicit def valueRuleReads[R, V](implicit rReads: Reads[R], vReads: Reads[V]): Reads[ValueRule[R, V]] = {
        val mlt = Json.reads[MaxLeadTime]
        val md = Json.reads[MaxDuration]
         __.read[MaxDuration](md).map(x => x.asInstanceOf[ValueRule[R, V]])
        .orElse(
            __.read[MaxLeadTime](mlt).map(x => x.asInstanceOf[ValueRule[R, V]])
        )
    }
    implicit def valueRuleWrites[R, V](implicit rWrites: Writes[R], vWrites: Writes[V]): Writes[ValueRule[R, V]] = Writes[ValueRule[R, V]]{
        case mlt: MaxLeadTime => Json.writes[MaxLeadTime].writes(mlt)
        case md: MaxDuration => Json.writes[MaxDuration].writes(md)
    }
}

object NoValueRule {
    implicit def noValueRuleReads[R](implicit rReads: Reads[R]): Reads[NoValueRule[R]] = {
        val odwh = Json.reads[OnlyDuringWorkHours]
        __.read[OnlyDuringWorkHours](odwh).map(x => x.asInstanceOf[NoValueRule[R]])
    }
    implicit def noValueRuleWrites[R](implicit rWrites: Writes[R]): Writes[NoValueRule[R]] = Writes[NoValueRule[R]]{
        case odwh: OnlyDuringWorkHours => Json.writes[OnlyDuringWorkHours].writes(odwh)
    }
}
object OnlyDuringWorkHours {
    implicit val format: Format[OnlyDuringWorkHours] = Json.format[OnlyDuringWorkHours]
}

object MaxLeadTime {
    implicit val format: Format[MaxLeadTime] = Json.format[MaxLeadTime]
}
object MaxDuration {
    implicit val format: Format[MaxDuration] = Json.format[MaxDuration]
}

object Rules {
    import play.api.libs.json.Reads._
    import play.api.libs.functional.syntax._

    implicit val rulesReads: Reads[Rules] = (
        (JsPath \ "centerId").read[Long] and
        (JsPath \ "ruleList").read[Seq[Rule]]
    )(Rules.apply _)
    implicit val rulesWrites: Writes[Rules] = (
        (JsPath \ "centerId").write[Long] and
        ???
    )(unlift(Rules.unapply))
    implicit val format: Format[Rules] = Format(rulesReads, rulesWrites)
}

这给我留下了两个问题。

首先,如果我在Rule.ruleReads中为???Json.reads[NoValueRule[R]]Json.reads[ValueRule[R, V]]两个实例分别插入我认为正确的表达式,我会得到以下编译错误

cmd16.sc:8: type mismatch;
 found   : play.api.libs.json.JsResult[Helper.this.OnlyDuringWorkHours]
 required: play.api.libs.json.JsResult[Helper.this.NoValueRule[R]]
        val nvr = Json.reads[NoValueRule[R]]
                            ^cmd16.sc:11: type mismatch;
 found   : play.api.libs.json.JsResult[Helper.this.MaxLeadTime]
 required: play.api.libs.json.JsResult[Helper.this.ValueRule[R,V]]
            val vr = Json.reads[ValueRule[R, V]]
                               ^

第二个是如果我离开???以便该部分编译它然后无法使用

编译规则对象
cmd17.sc:71: No Json deserializer found for type Seq[cmd17Wrapper.this.cmd16.wrapper.Rule]. Try to implement an implicit Reads or Format for this type.
        (JsPath \ "ruleList").read[Seq[Rule]]
                                  ^

我可以让规则读/写一个格式,并得到一个非常相似的错误

我认为2的问题是包含Seq[Rule[_]]的规则与我定义隐式读取之间的区别,隐式读取应该涵盖任何特定的规则但不是可以是任何规则

我有什么想法可以让这个工作?我觉得这应该是可能的,但也许不是。

1 个答案:

答案 0 :(得分:1)

虽然我认为你应该尝试一些基于宏的库,可以通过谷歌搜索找到"播放json密封的特性"例如Play JSON Derived Codecs ,这是一个可能适合您的手写解决方案:

object PlayJson {

  import play.api.libs.json._

  // fake types instead of your real ones
  type ResStart = Int
  type ResEnd = Int
  type Center = Int

  sealed trait Rule[A] {
    def roomId: Option[Long] = None

    def valid(in: A): Boolean
  }

  sealed trait ValueRule[A, B] extends Rule[A] {
    def value: B
  }

  sealed trait NoValueRule[A] extends Rule[A]

  case class OnlyDuringWorkHours(override val roomId: Option[Long] = None) extends NoValueRule[((ResStart, ResEnd), Center)] {
    override def valid(in: ((ResStart, ResEnd), Center)): Boolean = true
  }

  case class MaxLeadTime(override val roomId: Option[Long] = None, override val value: Int) extends ValueRule[ResStart, Int] {
    override def valid(in: ResStart): Boolean = true
  }

  case class MaxDuration(override val roomId: Option[Long] = None, override val value: String) extends ValueRule[(ResStart, ResEnd), String] {
    override def valid(in: (ResStart, ResEnd)): Boolean = true
  }

  case class Rules(centerId: Long, ruleList: Seq[Rule[_]])


  object CompoundFormat {
    final val discriminatorKey = "$type$"

    private case class UnsafeFormatWrapper[U, R <: U : ClassTag](format: OFormat[R]) extends OFormat[U] {
      def typeName: String = {
        val clazz = implicitly[ClassTag[R]].runtimeClass
        try {
          clazz.getSimpleName
        }
        catch {
          // getSimpleName might fail for inner classes because of the name mangling
          case _: InternalError => clazz.getName
        }
      }

      override def reads(json: JsValue): JsResult[U] = format.reads(json)

      override def writes(o: U): JsObject = {
        val base = format.writes(o.asInstanceOf[R])
        base + (discriminatorKey, JsString(typeName))
      }
    }

  }

  class CompoundFormat[A]() extends OFormat[A] {

    import CompoundFormat._

    private val innerFormatsByName = mutable.Map.empty[String, UnsafeFormatWrapper[A, _]]
    private val innerFormatsByClass = mutable.Map.empty[Class[_], UnsafeFormatWrapper[A, _]]

    override def reads(json: JsValue): JsResult[A] = {
      val jsObject = json.asInstanceOf[JsObject]
      val name = jsObject(discriminatorKey).asInstanceOf[JsString].value
      val innerFormat = innerFormatsByName.getOrElse(name, throw new RuntimeException(s"Unknown child type $name"))
      innerFormat.reads(jsObject)
    }

    override def writes(o: A): JsObject = {
      val innerFormat = innerFormatsByClass.getOrElse(o.getClass, throw new RuntimeException(s"Unknown child type ${o.getClass}"))
      innerFormat.writes(o)
    }

    def addSubType[R <: A : ClassTag](format: OFormat[R]): Unit = {
      val wrapper = new UnsafeFormatWrapper[A, R](format)
      innerFormatsByName.put(wrapper.typeName, wrapper)
      innerFormatsByClass.put(implicitly[ClassTag[R]].runtimeClass, wrapper)
    }
  }

  def buildRuleFormat: OFormat[Rule[_]] = {
    val compoundFormat = new CompoundFormat[Rule[_]]
    compoundFormat.addSubType(Json.format[OnlyDuringWorkHours])
    compoundFormat.addSubType(Json.format[MaxLeadTime])
    compoundFormat.addSubType(Json.format[MaxDuration])
    compoundFormat
  }

  def test(): Unit = {
    implicit val ruleFormat = buildRuleFormat
    implicit val rulesFormat = Json.format[Rules]

    val rules0 = Rules(1, List(
      OnlyDuringWorkHours(Some(1)),
      MaxLeadTime(Some(2), 2),
      MaxDuration(Some(3), "Abc")
    ))

    val json = Json.toJsObject(rules0)
    println(s"encoded: '$json'")
    val rulesDecoded = Json.fromJson[Rules](json)
    println(s"decoded: $rulesDecoded")
  }
}

调用PlayJson.test打印

  

编码:&#39; {&#34; centerId&#34;:1,&#34; ruleList&#34;:[{&#34; roomId&#34;:1,&#34; $ type $ &#34;:&#34; OnlyDuringWorkHours&#34;},{&#34; roomId&#34;:2&#34;值&#34;:2&#34; $类型$&#34 ;: &#34; MaxLeadTime&#34;},{&#34; roomId&#34;:3,&#34;值&#34;:&#34;美国广播公司&#34;&#34; $类型$&#34 ;:&#34; MaxDuration&#34;}]}&#39;

     


解码:JsSuccess(规则(1,List(OnlyDuringWorkHours(Some(1)),MaxLeadTime(Some(2),2),MaxDuration(Some(3),Abc))),)

主要思想是为密封的特征设置CompoundFormat,该特征在每个孩子的类名和相应的OFormat之间存储映射。

更新(关于反思问题)

这是CompoundFormat的非泛型版本,我希望它与基于宏的库可以生成的类似(实际上我希望好的基于宏的库也处理一些情况下的某些子代密封的特征是单例object而不是class,此代码无法处理):

object ExplicitRuleFormat {
  implicit val format: OFormat[Rule[_]] = new ExplicitRuleFormat()

  private object InnerFormats {

    final val discriminatorKey = "$type$"
    implicit val onlyDuringWorkHoursFormat = Json.format[OnlyDuringWorkHours]
    final val onlyDuringWorkHoursTypeName = "OnlyDuringWorkHours"
    implicit val maxLeadTimeFormat = Json.format[MaxLeadTime]
    final val maxLeadTimeTypeName = "MaxLeadTime"
    implicit val maxDurationFormat = Json.format[MaxDuration]
    final val maxDurationTypeName = "MaxDuration"
  }

}

class ExplicitRuleFormat extends OFormat[Rule[_]] {

  import ExplicitRuleFormat.InnerFormats._

  override def reads(json: JsValue): JsResult[Rule[_]] = {
    val jsObject = json.asInstanceOf[JsObject]
    val name = jsObject(discriminatorKey).asInstanceOf[JsString].value
    name match {
      case s if onlyDuringWorkHoursTypeName.equals(s) => Json.fromJson[OnlyDuringWorkHours](jsObject)
      case s if maxLeadTimeTypeName.equals(s) => Json.fromJson[MaxLeadTime](jsObject)
      case s if maxDurationTypeName.equals(s) => Json.fromJson[MaxDuration](jsObject)
    }
  }

  override def writes(r: Rule[_]): JsObject = r match {
    case rr: OnlyDuringWorkHours => writeImpl(rr, onlyDuringWorkHoursTypeName)
    case rr: MaxLeadTime => writeImpl(rr, maxLeadTimeTypeName)
    case rr: MaxDuration => writeImpl(rr, maxDurationTypeName)
  }

  def writeImpl[R <: Rule[_]](r: R, typeName: String)(implicit w: OWrites[R]): JsObject = {
    Json.toJsObject(r) + (discriminatorKey, JsString(typeName))
  }
}

并且test成为:

def test(): Unit = {
  import ExplicitRuleFormat.format
  implicit val rulesFormat = Json.format[Rules]

  val rules0 = Rules(1, List(
    OnlyDuringWorkHours(Some(1)),
    MaxLeadTime(Some(2), 2),
    MaxDuration(Some(3), "Abc")
  ))

  val json = Json.toJsObject(rules0)
  println(s"encoded: '$json'")
  val rulesDecoded = Json.fromJson[Rules](json)
  println(s"decoded: $rulesDecoded")
}

有效地,您只需将implicit val ruleFormat = buildRuleFormat替换为import ExplicitRuleFormat.format