Paladini
Paladini

Reputation: 4572

How to convert PlayJSON list to Scala Map (complex)?

I'm converting a Python script to Scala and I'm having some problems (note that I never programmed in Scala before).

I'm making an API request to KairosDB and I'm parsing it's response in Scala with PlayJSON. As you can see in the Query Metrics Documentation, KairosDB responds with a JSON like the following:

{
  "queries": [
      {
          "sample_size": 14368,
          "results": [
              {
                  "name": "abc_123",
                  "group_by": [
                      {
                         "name": "type",
                         "type": "number"
                      },
                      {
                          "name": "tag",
                          "tags": [
                              "host"
                          ],
                          "group": {
                              "host": "server1"
                          }
                      }
                  ],
                  "tags": {
                      "host": [
                          "server1"
                      ],
                      "customer": [
                          "bar"
                      ]
                  },
                  "values": [
                      [
                          1364968800000,
                          11019
                      ],
                      [
                          1366351200000,
                          2843
                      ]
                  ]
              }
          ]
      }
  ]
}

Currently I would like to parse this response (in JSON format) and create a structure like the following (that I guess will be represent as a Scala Map):

{ 
    "abc_123": [
        [timestamp1, value1], 
        [timestamp2, value2]
     ],
     "abd_124": [
        [timestamp1, value1], 
        [timestamp2, value2]
     ]
}

In other words, I want to create a key for each sensor "name" that I got from "results" and associate this key with an array of "values" of this specific sensor "name". I don't need to save / store "sample_size", "tags" and "group_by" right now because it's useless for me.

At the moment I'm trying this to solve the problem (remeber that I'm using PlayJSON):

// Get "results" from JSON
val parsedResponse = (((Json.parse(response.body.asString) \ "queries")(0)) \\ "results")

// Some commands that I've tested and works:
// Print keys: parsedResponse.foreach { p => print( (p(0)) \ "name" ) }
// Print values: parsedResponse.foreach { p => print((p(0) \\ "values")) }

// Here I'm trying to return a Set, but it don't work at all
// val data = parsedResponse.foreach { p => ((p(0)) \ "name").asOpt[String] -> ((p(0) \\ "values").asOpt[Seq]) }

Note that I can print parsedResponse and I get the following: https://gist.github.com/paladini/b474bba6c3711ddcdacd.

How can I achieve that in Scala?

Upvotes: 0

Views: 97

Answers (1)

Onilton Maciel
Onilton Maciel

Reputation: 3699

As opposed to dynamic languages like python, in Scala we usually do things in a typesafe way, including json parsing. We usually don't use dictionaries, we always convert the json to something typed (and safer).

In play, you can do as suggested in play's documentation, use play-json

It would turn out to be something like this:

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class ResultValue(timestamp: Long, value: Long)
case class Result(name: String, values: Seq[ResultValue])

implicit val resultValueReads: Reads[ResultValue] = (
  (JsPath(0)).read[Long] and
  (JsPath(1)).read[Long]
)(ResultValue.apply _)

implicit val resultReads: Reads[Result] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "values").read[Seq[ResultValue]]
)(Result.apply _)

val parsedResponse = (((Json.parse(text) \ "queries")(0)) \ "results").validate[Seq[Result]]

parsedResponse.asOpt match { 
  case Some(results) => println(results)
  case None => println("Nothing was parsed")
}

Prints:

List(Result(abc_123,List(ResultValue(1364968800000,11019), ResultValue(1366351200000,2843))))

Then you can use it like this:

results(0).name
results(0).values.timestamp
results(0).values.value

Other alternative: json4s

play-json is a little bit verbose (specially since you came from a dynamic language), so if you want an easier way of parsing json you could use json4s. The solution would be something like this:

import org.json4s.DefaultFormats
import org.json4s.native.JsonMethods._

implicit val formats = DefaultFormats

case class Result(name: String, values: List[List[Long]])

val json = parse(text)

val results = ((json \ "queries")(0) \ "results").extract[List[Result]]

println(results(0).name)
println(results(0).values)

** A more concise, but less safer version using play-json**

case class Result(name: String, values: Seq[Seq[Long]])                                                                                                                

implicit val resultReads: Reads[Result] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "values").read[Seq[Seq[Long]]]
)(Result.apply _)

val parsedResponse = (((Json.parse(text) \ "queries")(0)) \ "results").validate[Seq[Result]]

parsedResponse.asOpt match { 
  case Some(results) => println(results)
  case None => println("Nothing was parsed")
}

Prints:

List(Result(abc_123,List(List(1364968800000, 11019), List(1366351200000, 2843))))

Upvotes: 3

Related Questions