我正在使用几个 Reads[T]
实现读取嵌套的 JSON 文档,但是,我遇到了以下子对象:
{
...,
"attributes": {
"keyA": [1.68, 5.47, 3.57],
"KeyB": [true],
"keyC": ["Lorem", "Ipsum"]
},
...
}
键(“keyA”、“keyB”...)以及键的数量在编译时是未知的,并且可能会有所不同。键的值总是
JsArray
实例,但大小和类型不同(但是,特定数组的所有元素必须具有相同的 JsValue
类型)。单个属性的 Scala 表示:
case class Attribute[A](name: String, values: Seq[A])
// 'A' can only be String, Boolean or Double
目标是创建一个
Reads[Seq[Attribute]
] ,在转换整个文档时可用于“属性”字段(请记住,“属性”只是一个子文档)。然后是一个简单的映射,其中包含应用于验证属性的键和数组类型的允许组合。编辑:此 map 是 特定于每个请求 (或者更确切地说是特定于每种类型的 json 文档)。但是您可以假设它在范围内始终可用。
val required = Map(
"KeyA" -> "Double",
"KeyB" -> "String",
"KeyD" -> "String",
)
所以在上面显示的 JSON 的情况下,
Reads
应该创建两个错误:我在创建必要的
Reads
时遇到问题。我作为第一步尝试的第一件事,从外部 Reads
的角度来看:...
(__ \ "attributes").reads[Map[String, JsArray]]...
...
我认为这是一个很好的第一步,因为如果 JSON 结构不是包含
String
s 和 JsArray
s 作为键值对的对象,那么 Reads
会失败并显示正确的错误消息。它有效,但是:我不知道如何从那里继续。当然,我可以创建一个将 Map
转换为 Seq[Attribute]
的方法,但是这个方法应该以某种方式返回一个 JsResult
,因为还有进一步的验证要做。我尝试的第二件事:
val attributeSeqReads = new Reads[Seq[Attribute]] {
def reads(json: JsValue) = json match {
case JsObject(fields) => processAttributes(fields)
case _ => JsError("attributes not an object")
}
def processAttributes(fields: Map[String, JsValue]): JsResult[Seq[Attribute]] = {
// ...
}
}
这个想法是在
processAttributes
中手动验证 map 的每个元素。但我觉得这太复杂了。任何帮助表示赞赏。编辑澄清:
在帖子的开头我说 key (keyA,keyB...)在编译时是未知的。后来我说这些键是用于验证的 map
required
的一部分。这听起来很矛盾,但事实是:required
特定于每个文档/请求,并且在编译时也不知道。但是您不必担心这一点,只需假设对于每个请求,正确的 required
已经在作用域中可用。 最佳答案
你对任务太困惑了
那么key的数量和类型是事先知道的,最后知道的呢?
您的主要任务只是检查可用性和合规性?
您可以使用 Reads[Attribute]
为您的每个键实现 Reads.list(Reads.of[A])
(此读取将检查类型和必需的)并使用 Reads.pure(Attribute[A])
跳过省略(如果不需要)。然后元组转换为列表( _.productIterator.toList
),你会得到 Seq[Attribute]
val r = (
(__ \ "attributes" \ "keyA").read[Attribute[Double]](list(of[Double]).map(Attribute("keyA", _))) and
(__ \ "attributes" \ "keyB").read[Attribute[Boolean]](list(of[Boolean]).map(Attribute("keyB", _))) and
((__ \ "attributes" \ "keyC").read[Attribute[String]](list(of[String]).map(Attribute("keyC", _))) or Reads.pure(Attribute[String]("keyC", List()))) and
(__ \ "attributes" \ "keyD").read[Attribute[String]](list(of[String]).map(Attribute("keyD", _)))
).tupled.map(_.productIterator.toList)
scala>json1: play.api.libs.json.JsValue = {"attributes":{"keyA":[1.68,5.47,3.57],"keyB":[true],"keyD":["Lorem","Ipsum"]}}
scala>res37: play.api.libs.json.JsResult[List[Any]] = JsSuccess(List(Attribute(keyA,List(1.68, 5.47, 3.57)), Attribute(KeyB,List(true)), Attribute(keyC,List()), Attribute(KeyD,List(Lorem, Ipsum))),)
scala>json2: play.api.libs.json.JsValue = {"attributes":{"keyA":[1.68,5.47,3.57],"keyB":[true],"keyC":["Lorem","Ipsum"]}}
scala>res38: play.api.libs.json.JsResult[List[Any]] = JsError(List((/attributes/keyD,List(ValidationError(List(error.path.missing),WrappedArray())))))
scala>json3: play.api.libs.json.JsValue = {"attributes":{"keyA":[1.68,5.47,3.57],"keyB":["Lorem"],"keyC":["Lorem","Ipsum"]}}
scala>res42: play.api.libs.json.JsResult[List[Any]] = JsError(List((/attributes/keyD,List(ValidationError(List(error.path.missing),WrappedArray()))), (/attributes/keyB(0),List(ValidationError(List(error.expected.jsboolean),WrappedArray())))))
如果您将有超过 22 个属性,则会遇到另一个问题:具有超过 22 个属性的元组。
用于运行时的动态属性
灵感来自 'Reads.traversableReads[F[_], A]'
def attributesReads(required: Map[String, String]) = Reads {json =>
type Errors = Seq[(JsPath, Seq[ValidationError])]
def locate(e: Errors, idx: Int) = e.map { case (p, valerr) => (JsPath(idx)) ++ p -> valerr }
required.map{
case (key, "Double") => (__ \ key).read[Attribute[Double]](list(of[Double]).map(Attribute(key, _))).reads(json)
case (key, "String") => (__ \ key).read[Attribute[String]](list(of[String]).map(Attribute(key, _))).reads(json)
case (key, "Boolean") => (__ \ key).read[Attribute[Boolean]](list(of[Boolean]).map(Attribute(key, _))).reads(json)
case _ => JsError("")
}.iterator.zipWithIndex.foldLeft(Right(Vector.empty): Either[Errors, Vector[Attribute[_ >: Double with String with Boolean]]]) {
case (Right(vs), (JsSuccess(v, _), _)) => Right(vs :+ v)
case (Right(_), (JsError(e), idx)) => Left(locate(e, idx))
case (Left(e), (_: JsSuccess[_], _)) => Left(e)
case (Left(e1), (JsError(e2), idx)) => Left(e1 ++ locate(e2, idx))
}
.fold(JsError.apply, { res =>
JsSuccess(res.toList)
})
}
(__ \ "attributes").read(attributesReads(Map("keyA" -> "Double"))).reads(json)
scala> json: play.api.libs.json.JsValue = {"attributes":{"keyA":[1.68,5.47,3.57],"keyB":[true],"keyD":["Lorem","Ipsum"]}}
scala> res0: play.api.libs.json.JsResult[List[Attribute[_ >: Double with String with Boolean]]] = JsSuccess(List(Attribute(keyA,List(1.68, 5.47, 3.57))),/attributes)
关于json - Play JSON : Reading and validating a JsObject with unknown keys,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/36670756/