在 JsArray 中检索对象内的特定字段

Retrieving a particular field inside objects in JsArray

我的部分 JSON 回复如下所示:

"resources": [{
        "password": "",
        "metadata": {
            "updated_at": "20190806172149Z",
            "guid": "e1be511a-eb8e-1038-9547-0fff94eeae4b",
            "created_at": "20190405013547Z",
            "url": ""
        },
        "iam": false,
        "email": "<some mail id>",
        "authentication": {
            "method": "internal",
            "policy_id": "Default"
        }
    }, {
        "password": "",
        "metadata": {
            "updated_at": "20190416192020Z",
            "guid": "6b47118c-f4c8-1038-8d93-ed6d7155964a",
            "created_at": "20190416192020Z",
            "url": ""
        },
        "iam": true,
        "email": "<some mail id>",
        "authentication": {
            "method": "internal",
            "policy_id": null
        }
    },
    ...
]

我正在使用 Play 框架提供的 Json 帮助程序来解析此 Json,如下所示:

val resources: JsArray = response("resources").as[JsArray]

现在我需要从 resources JsArray 中的所有这些对象中提取字段 email。为此,我尝试编写一个 foreach 循环,例如:

for (resource <- resources) {

}

但我在 <- 符号处收到错误 Cannot resolve symbol foreach。如何从 JsArray

中的每个 JSON 对象中检索特定字段,例如 email

播放JSON我一直用case classes。所以你的例子看起来像:

import play.api.libs.json._

case class Resource(password: String, metadata: JsObject, iam: Boolean, email: String, authentication: JsObject)

object Resource {
  implicit val jsonFormat: Format[Resource] = Json.format[Resource]
}

val resources: Seq[Resource] = response("resources").validate[Seq[Resource]] match {
  case JsSuccess(res, _) => res
  case errors => // handle errors , e.g. throw new IllegalArgumentException(..)
}

现在您可以 type-safe 方式访问任何字段。

当然,您可以用相同的方式将 JsObject 替换为 case classes - 如果您在我的回答中需要这个,请告诉我。

但在你的情况下,你只需要 email 就没有必要了:

resources.map(_.email) // returns Seq[String]

resources 是一个 JsArray,一种不提供 .flatMap 的类型,因此不能在 for comprehension 中用在 <- 的右边。

val emailReads: Reads[String] = (JsPath \ "email").reads[String]
val resourcesReads = Reads.seqReads(emailReads)

val r: JsResult[Seq[String]] = resources.validate(resources reads)

所以就像@pme 说你应该使用 case 类,它们应该看起来像这样:

import java.util.UUID

import play.api.libs.json._


case class Resource(password:String, metadata: Metadata, iam:Boolean, email:UUID, authentication:Authentication)

object Resource{
  implicit val resourcesImplicit: OFormat[Resource] = Json.format[Resource]
}

case class Metadata(updatedAt:String, guid:UUID, createdAt:String, url:String)

object Metadata{
  implicit val metadataImplicit: OFormat[Metadata] = Json.format[Metadata]
}


case class Authentication(method:String, policyId: String)

object Authentication{
  implicit val authenticationImplicit: OFormat[Authentication] = 
Json.format[Authentication]
}

您也可以使用 Writes 和 Reads 而不是 OFormat,或者自定义 Writes 和 Reads,我使用 OFormat 是因为它不那么冗长。

然后当你有你的回复时,你验证它们,你可以按照@pme 所说的方式或我做的方式验证它们:

val response_ = response("resources").validate[Seq[Resource]]
response_.fold(
    errors => Future.succeful(BadRequest(JsError.toJson(errors)),
resources => resources.map(_.email))// extracting emails from your objects
    ???
)

所以这里你在 Json 无效时做一些事情,在 Json 有效时做另一件事,行为与 pme 所做的相同,在我看来只是更优雅一点

假设您的 json 看起来像这样:

val jsonString =
  """
    |{
    |  "resources": [
    |    {
    |      "password": "",
    |      "metadata": {
    |        "updated_at": "20190806172149Z",
    |        "guid": "e1be511a-eb8e-1038-9547-0fff94eeae4b",
    |        "created_at": "20190405013547Z",
    |        "url": ""
    |      },
    |      "iam": false,
    |      "email": "<some mail id1>",
    |      "authentication": {
    |        "method": "internal",
    |        "policy_id": "Default"
    |      }
    |    },
    |    {
    |      "password": "",
    |      "metadata": {
    |        "updated_at": "20190416192020Z",
    |        "guid": "6b47118c-f4c8-1038-8d93-ed6d7155964a",
    |        "created_at": "20190416192020Z",
    |        "url": ""
    |      },
    |      "iam": true,
    |      "email": "<some mail id2>",
    |      "authentication": {
    |        "method": "internal",
    |        "policy_id": null
    |      }
    |    }
    |  ]
    |}
  """.stripMargin

你可以做到:

(Json.parse(jsonString) \ "resources").as[JsValue] match{
  case js: JsArray => js.value.foreach(x => println((x \ "email").as[String]))
  case x => println((x \ "email").as[String])
}

或:

(Json.parse(jsonString) \ "resources").validate[JsArray] match {
  case s: JsSuccess[JsArray] => s.get.value.foreach(x => println((x \ "email").as[String]))
  case _: JsError => arr().value //or do something else
}

都适合我。