Play JSON:把一个Seq[Reads[JsObject]]变成一个Reads[JsObject]
Play JSON: turn a Seq[Reads[JsObject]] into one Reads[JsObject]
我动态生成了一堆 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
属性 名称和选择哪个分支在编译时是未知的,这就是为什么它必须是动态的。
这是一个相当普遍的问题。这个答案的灵感来自 Reads.traversableReads[F[_], A]
.
为了支持 Reads[A]
的累积思想,我们必须尝试您生成的所有 Reads[JsObject]
,我们将为此使用 Either[Errors, Vector[JsObject]]。在原始的 'Reads.traversableReads[F[_], A]' returns 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]
所以我们可以用 FunctionalBuilder
减少 Seq(Reads[JsObject])
(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,你想看同一个路径会不会被使用两次?
我动态生成了一堆 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
属性 名称和选择哪个分支在编译时是未知的,这就是为什么它必须是动态的。
这是一个相当普遍的问题。这个答案的灵感来自 Reads.traversableReads[F[_], A]
.
为了支持 Reads[A]
的累积思想,我们必须尝试您生成的所有 Reads[JsObject]
,我们将为此使用 Either[Errors, Vector[JsObject]]。在原始的 'Reads.traversableReads[F[_], A]' returns 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]
所以我们可以用 FunctionalBuilder
减少 Seq(Reads[JsObject])
(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,你想看同一个路径会不会被使用两次?