1

A part of my JSON response looks like this:

"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
        }
    },
    ...
]

I am using Json helpers provided by the Play framework to parse this Json like this:

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

Now I need to extract the field email from all these objects in the resources JsArray. For this I tried writing a foreach loop like:

for (resource <- resources) {

}

But I'm getting an error Cannot resolve symbol foreach at the <- sign. How do I retrieve a particular field like email from each of the JSON objects inside a JsArray

0

4 Answers 4

2

With Play JSON I always use case classes. So your example would look like:

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(..)
}

Now you can access any field in a type-safe manner.

Of course you can replace the JsObjects with case classes in the same way - let me know if you need this in my answer.

But in your case as you only need the email there is no need:

resources.map(_.email) // returns Seq[String]
Sign up to request clarification or add additional context in comments.

4 Comments

@Pedro Correia Luís thanks for the hint - I adjusted my answer
Hey, thanks for the answer. Right now my requirement is only getting Email, but this is not the entire output, and just like metadata and authorization, I have some other stuff as well which will be needed to extract. Could you please edit the answer to show with case classes?
I'm not sure how I handle the errors. I know that if there is a problem parsing any field, it will be an error, but how do I handle because the output of the case errors also expects an object of type Resource
@Sparker0i 1. Pedro's answer did already provide that in his answer. 2. a more functional way would be to return Try[Seq[Resource]] and handle the error case at a later point.
0

So like @pme said you should work with case classes, they should look something like this:

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]
}

You can also use Writes and Reads instead of OFormat, or custom Writes and Reads, I used OFormat because it is less verbose.

Then when you have your responses, you validate them, you can validate them the way @pme said, or the way I do it:

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
    ???
)

So here you do something when the Json is invalid, and another thing when Json is valid, the behavior is the same as what pme did, just a bit more elegant in my opinion

5 Comments

If the goal is to get a single value email: String (to ends with a Seq[String]) per array element, there is no need to add a case class ("just" to benefit from the macro generated Reads)
And so? A email is not a structures type (a Value class rather than a Case class)
@pedro, I tried your code of the response part, but I am getting error at the BadRequest part.
Here. I'm using the latest Play framework. Maybe that's why..?
I don´t think that's the case, probably an import missing. If you take BadRequest you still have the error? also take the ???, was just because I didn't know what else you wanted to do after having the emails
0

Assuming that your json looks like this:

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

you can do:

(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])
}

or:

(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
}

both works for me.

Comments

-1

The resources is a JsArray, a type that doesn't provide .flatMap so cannot be used at right of <- in a for comprehension.

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

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

1 Comment

That's not what I want

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.