播放JSON:将Seq [Reads [JsObject]]转换成一个Reads [JsObject]

时间:2016-04-25 19:05:01

标签: json scala playframework playframework-2.5 playframework-json

我动态生成了一堆Reads[JsObject],然后我在Seq[Reads[JsObject]]中生成了Reads[JsObject]。为了实际应用所有这些单and,我必须将它们与Reads[JsObject]合并为一个val generatedReads: Seq[Reads[JsObject]] = Seq( (__ \ "attr1").json.copyFrom((__ \ "attr1" \ "attr1a").json.pick), (__ \ "attr2").json.pickBranch ) 。这可能吗?

我有(例子):

val finalReads: Reads[JsObject] =
    (__ \ "attr1").json.copyFrom((__ \ "attr1" \ "attr1a").json.pick) and
    (__ \ "attr2").json.pickBranch

我需要什么:

toDigits :: Integer -> [Integer]
toDigits = map (read . (:[])) . show

> toDigits 123
[1,2,3]
> toDigits 0
[0]

在编译时不知道属性名称和要选择的分支,这就是为什么它必须是动态的。

1 个答案:

答案 0 :(得分:3)

这是一个相当普遍的问题。这个答案受到Reads.traversableReads[F[_], A]的启发。

为了支持累积Reads[A]的想法,我们必须尝试所有你生成的Reads[JsObject],我们将使用[错误,Vector [JsObject]]。在原始的'Reads.traversableReads [F [_],A]'中返回Reads[List[A]]或某些集合,但我们需要简单的Json,没问题,++将我们的JsObjects连接起来。

def reduceReads(generated: Seq[Reads[JsObject]]) = Reads {json =>
  type Errors = Seq[(JsPath, Seq[ValidationError])]

  def locate(e: Errors, idx: Int) = e.map { case (p, valerr) => (JsPath(idx)) ++ p -> valerr }

  generated.iterator.zipWithIndex.foldLeft(Right(Vector.empty): Either[Errors, Vector[JsObject]]) {
    case (acc, (r, idx)) => (acc, r.reads(json)) match {
      case (Right(vs), JsSuccess(v, _)) => Right(vs :+ v)
      case (Right(_), JsError(e)) => Left(locate(e, idx))
      case (Left(e), _: JsSuccess[_]) => Left(e)
      case (Left(e1), JsError(e2)) => Left(e1 ++ locate(e2, idx))
    }
  }
    .fold(JsError.apply, { res =>
      JsSuccess(res.fold(Json.obj())(_ ++ _))
    })
}

scala> json: play.api.libs.json.JsValue = {"attr1":{"attr1a":"attr1a"},"attr2":"attr2"}

scala> res7: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"attr1":"attr1a","attr2":"attr2"},)

新的简单答案

几天后,我有了这个绝妙的主意。 object Reads已隐式Reducer[JsObject, JsObject],因此我们可以使用Seq(Reads[JsObject])FunctionalBuilder然后and)来减少reduce

def reduceReads(generated: Seq[Reads[JsObject]]) = 
  generated.foldLeft(Reads.pure(Json.obj())){
    case (acc, r) =>
      (acc and r).reduce 
  }

此解决方案简单明了。基于Seq(Reads[JsObject]) => Seq(JsResult[JsObject]) => Reads[JsObject]映射的原创思想,但最后一个基于Json组合原理Seq(Reads[JsObject]) => Reads[JsObject]

一般来说,问题已解决,但任务本身并不正确。如果你不控制Reads,你想看看是否会使用两次相同的路径?