Gatling : get the first element of the first object in a JSON Array

Viewed 837

I have this JSON object :

{"isCompany":false,"accommodations":[{"id":"00000000031000000067","isChecked":false,"name":"5 JULI 2017","addressLine1":"STRAAT 10 ","addressLine2":"1000 New York","nightsDeclared":0,"schoolNightsDeclared":0,"schoolNightsAttached":0,"taxableNights":0.0,"totalPayment":0.0,"isInProgress":false,"isLate":false,"isPayed":"false","deadline":"2021-12-31","initialAmount":0.0,"remainingAmount":0.0},{"id":"00000000031000006362","isChecked":false,"name":"BELLEVIE","addressLine1":"STRAAT 10 ","addressLine2":"1000 New York","nightsDeclared":0,"schoolNightsDeclared":0,"schoolNightsAttached":0,"taxableNights":0.0,"totalPayment":0.0,"isInProgress":false,"isLate":false,"isPayed":"false","deadline":"2021-12-31","initialAmount":0.0,"remainingAmount":0.0}]}

Which if prettified, render this :

{
  "isCompany": false,
  "accommodations": [
    {
      "id": "00000000031000000067",
      "isChecked": false,
      "name": "5 JULI 2017",
      "addressLine1": "STRAAT 10 ",
      "addressLine2": "1000 New York",
      "nightsDeclared": 0,
      "schoolNightsDeclared": 0,
      "schoolNightsAttached": 0,
      "taxableNights": 0.0,
      "totalPayment": 0.0,
      "isInProgress": false,
      "isLate": false,
      "isPayed": "false",
      "deadline": "2021-12-31",
      "initialAmount": 0.0,
      "remainingAmount": 0.0
    },
    {
      "id": "00000000031000006362",
      "isChecked": false,
      "name": "BELLEVIE",
      "addressLine1": "STRAAT 10 ",
      "addressLine2": "1000 New York",
      "nightsDeclared": 0,
      "schoolNightsDeclared": 0,
      "schoolNightsAttached": 0,
      "taxableNights": 0.0,
      "totalPayment": 0.0,
      "isInProgress": false,
      "isLate": false,
      "isPayed": "false",
      "deadline": "2021-12-31",
      "initialAmount": 0.0,
      "remainingAmount": 0.0
    }
  ]
}

I've got this full JSON Array from a div inside of an HTML by writing this :

.check(css("section.ht-declarations-tab-content-container>div#DATA--DECL-DATA").saveAs("jsonObj"))

And then to render the result I wrote this :

.exec { session => println("json = " + session("jsonObj").as[String]); session }.exitHereIfFailed

However as explained, I've got the full JSON array.

How to do to just get the first ID element of the first object? So basically : 00000000031000000067

4 Answers

If your JSON is embedded inside HTML, you have 2 possibilities:

  1. Use a css check like you've done so far to extract the JSON from the HTML and then use transform to parse the JSON and extract the desired value, eg with Jackson as suggested in the other answer
  2. Use regex to extract the desired value in one go, eg check(regex(""""accommodations":\[\{"id":"(.*?)""""))

I would use Jackson for that job:

private static final ObjectMapper MAPPER = new ObjectMapper();

public static String getFirstId(String json) throws JsonProcessingException {
    return MAPPER.readTree(json).get("accommodations").get(0).get("id").asText();
}

Then you can do:

System.out.println(getFirstId(json));

Output:

00000000031000000067

If you want to print de JSON as a tree you can do:

public static String toTree(String json) throws JsonProcessingException {
    return MAPPER.readTree(json).toPrettyString();
}

Then you can do:

System.out.println(toTree(json));

Output:

{
  "isCompany" : false,
  "accommodations" : [ {
    "id" : "00000000031000000067",
    "isChecked" : false,
    "name" : "5 JULI 2017",
    "addressLine1" : "STRAAT 10 ",
    "addressLine2" : "1000 New York",
    "nightsDeclared" : 0,
    "schoolNightsDeclared" : 0,
    "schoolNightsAttached" : 0,
    "taxableNights" : 0.0,
    "totalPayment" : 0.0,
    "isInProgress" : false,
    "isLate" : false,
    "isPayed" : "false",
    "deadline" : "2021-12-31",
    "initialAmount" : 0.0,
    "remainingAmount" : 0.0
  }, {
    "id" : "00000000031000006362",
    "isChecked" : false,
    "name" : "BELLEVIE",
    "addressLine1" : "STRAAT 10 ",
    "addressLine2" : "1000 New York",
    "nightsDeclared" : 0,
    "schoolNightsDeclared" : 0,
    "schoolNightsAttached" : 0,
    "taxableNights" : 0.0,
    "totalPayment" : 0.0,
    "isInProgress" : false,
    "isLate" : false,
    "isPayed" : "false",
    "deadline" : "2021-12-31",
    "initialAmount" : 0.0,
    "remainingAmount" : 0.0
  } ]
}

This can be done in a few steps.

  1. At the first need to extract only json and change response body.
  2. And then you can use simple jsonPath and work with json

For change - use transformResponse where extract json string and remove break lines and set as new response body

http("...")
    .get("...")
    .transformResponse { (response, _) =>
      val json = response.body.string match {
        case s"""<div id="DATA--DECL-DATA">${json}</div>""" => json.replaceAll("\n", "")
      }

      response.copy(body = new StringResponseBody(json, response.body.charset))
    }
    .check(jsonPath("$...").find.saveAs("..."))

I would recommend using circe to parse the JsonString and there are two ways to do this:

  1. requires you to add the following packages to your sbt build:
val core = "io.circe" %% "circe-core" % circeVersion
val generic = "io.circe" %% "circe-generic" % circeVersion
val parser = "io.circe" %% "circe-parser" % circeVersion

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full),

This method uses case classes and field fetching in order to get your desired objects. The Case classes can be defined specifically the fields you want or express the whole json object as documentation. This solution gives you a lot of flexibility but requires a lot of boiler plate

// SOLUTION 1:

import io.circe.parser.decode
import io.circe.generic.JsonCodec

/** 
  * First Build two case classes to represent 
  * the JSON object that you will be parsing to
  */

@JsonCodec
case class Accommodation(id: String)

@JsonCodec
case class Response(isCompany: Boolean, accommodations: List[Accommodation]) {
    def getFirstAccomodationID: Option[String] = {
        this.accommodations.headOption match {
            case Some(accommodation) => Some(accommodation.id)
            case _ => None
        }

    }
}

/* In order to actually pull the data */
val response: Either[io.circe.Error,Response] = decode[Response](jsonString)
println(response.right.get.getFirstAccomodationID)
  1. Requires you to add the following packages
val core = "io.circe" %% "circe-core" % circeVersion
val generic = "io.circe" %% "circe-generic" % circeVersion
val parser = "io.circe" %% "circe-parser" % circeVersion
val optics = "io.circe" %% "circe-optics" % circeVersion

This solution uses json parsing and traversal to get to the exact fields you want and allows you to specify exactly what fields you want.

This solution is also more compact and easier to reason with for someone reading the code.

// SOLUTION 2:

import io.circe._,
import io.circe.parser._

/* First convert the String into a JSON object */
val json: Json = parse(jsonString).getOrElse(Json.Null)

import io.circe.optics.JsonPath._

/* Use Circe Optics to define the json path you wish to traverse */
val _getFirstAccomodationID = root.accommodations.each.id.string

/* Finally, fetch the actual String value */
val firstId: Option[String] = _getFirstAccomodationID.getAll(json).headOption
println(firstId)
Related