简体   繁体   English

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

[英]Play JSON: turn a Seq[Reads[JsObject]] into one Reads[JsObject]

I dynamically generate a bunch of Reads[JsObject] which I then have in a Seq[Reads[JsObject]] . 我动态生成了一堆Reads[JsObject] ,然后我在Seq[Reads[JsObject]] In order to actually apply all these single Reads[JsObject] , I've got to merge them with and into one single Reads[JsObject] . 为了实际应用所有这些单Reads[JsObject]我必须与它们合并and成一个单一的Reads[JsObject] Is this possible? 这可能吗?

I've got (example): 我有(例子):

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

What I need: 我需要的:

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

The property names and which branch to pick is not known at compile time, that's why it's got to be dynamic. 在编译时不知道属性名称和要选择的分支,这就是为什么它必须是动态的。

This is a fairly common question. 这是一个相当普遍的问题。 This answer inspired by Reads.traversableReads[F[_], A] . 这个答案的灵感来自Reads.traversableReads[F[_], A]

To support the idea of cumulative of Reads[A] , we must try all your genererated Reads[JsObject] and we will use Either[Errors, Vector[JsObject]] for this. 为了支持Reads[A]累积的想法,我们必须尝试所有你生成的Reads[JsObject] ,我们将使用Either [Errors,Vector [JsObject]]。 In original 'Reads.traversableReads[F[_], A]' returns Reads[List[A]] or some collection, but we need simple Json, no problem, ++ concatinates our JsObjects . 在原始的'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"},)

new awesome simple answer 新的很棒的简单答案


After few days I had this brilliant idea. 几天后,我有了这个绝妙的主意。 object Reads have implicit Reducer[JsObject, JsObject] so we can reduce Seq(Reads[JsObject]) with FunctionalBuilder ( and and then reduce )! object Reads具有隐含Reducer[JsObject, JsObject]所以我们可以减少Seq(Reads[JsObject])FunctionalBuilderandreduce )!

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

This solution is simple and clear. 该解决方案简单明了。 Original idea based Seq(Reads[JsObject]) => Seq(JsResult[JsObject]) => Reads[JsObject] mapping, but the last based on basic Json combinators principles Seq(Reads[JsObject]) => Reads[JsObject] 基于原始思路的Seq(Reads[JsObject]) => Seq(JsResult[JsObject]) => Reads[JsObject]映射,但最后一个基于Json组合原理Seq(Reads[JsObject]) => Reads[JsObject]


In general, the problem is solved, but the task itself is not correct. 一般来说,问题已经解决,但任务本身并不正确。 If you don't control Reads, what you want see if the same path will be used twice? 如果你不控制Reads,你想看看是否会使用两次相同的路径?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM