4

Given a JSON string like this:

{"Locations":
  {"list":
    [
      {"description": "some description", "name": "the name", "id": "dev123"},
      {"description": "other description", "name": "other name", "id": "dev59"}
    ]
  }
}

I'd like to return a list of "id"s from a function parsing the above string. JSON.parseFull() (from scala.util.parsing.json) gives me a result of type Option[Any]. Scala REPL shows it as Some(Map(Locations -> Map(list -> List(Map(id -> dev123, ... and as a beginner in Scala I'm puzzled as to which way to approach it.

Scala API docs suggest "to treat it as a collection or monad and use map, flatMap, filter, or foreach". Top-level element is an Option[Any] however that should be Some with a Map that should contain a single key "Locations", that should contain a single key "list" that finally is a List. What would be an idiomatic way in Scala to write a function retrieving the "id"s?

FilipK
  • 626
  • 1
  • 4
  • 13

5 Answers5

7

First of all, you should cast json from Any to right type:

val json = anyJson.asInstanceOf[Option[Map[String,List[Map[String,String]]]]]

And then you may extract ids from Option using map method:

val ids = json.map(_("Locations")("list").map(_("id"))).getOrElse(List())
alno
  • 3,556
  • 1
  • 19
  • 10
  • The type is incomplete I think - it worked when I used `anyJson.asInstanceOf[Option[Map[String,Map[String,List[Map[String,String]]]]]]`. How does it work though? The `asInstanceOf` is a runtime cast I read. I assume it would throw an exception when the given Any object is different? And by the way what does `map(_("Locations")("list")...` mean? – FilipK Oct 17 '11 at 11:29
  • Yes, `asInstanceOf` will throw an exception. But, not always... due to type erasure in JVM. `map(_("Locations")("list").map(_("id"))` is a shortcut for `map(x => x("Locactions")("list").map(_("id")))` Method `map` transforms internal value of container (`Option` in this case) with given function. Given function receives a `Map` of `Map` and retrieves value from it (two levels deep) - it's list of maps, containing id. So, `.map(_("id"))` transforms list of maps to list of ids by retrieving an "id" value from each element (which is `Map[String,String]` – alno Oct 17 '11 at 16:42
3

For this type of tasks, you should take a look at Rapture.io. I'm also a scala beginner, but from what I've searched for, this seems to have the friendliest syntax. Here's a short example, taken from a gist:

import rapture.io._

// Let's parse some JSON
val src: Json = Json.parse("""
{
  "foo": "Hello world",
  "bar": {
    "baz": 42
  }
}
""")     

// We can now access the value bar.baz
val x: Json = src.bar.baz

// And get it as an integer
val y: Int = x.get[Int]

// Alternatively, we can use an extractor to get the values we want:
val json""" { "bar": { "baz": $x }, "foo": $z }""" = src

// Now x = 42 and z = "Hello world".
Alex Ciminian
  • 11,398
  • 15
  • 60
  • 94
3

Because Any is everywhere is the returned result, you'll have to cast. Using one of my earlier answers:

class CC[T] { def unapply(a:Any):Option[T] = Some(a.asInstanceOf[T]) }

object M extends CC[Map[String, Any]]
object L extends CC[List[Any]]
object S extends CC[String]
object D extends CC[Double]
object B extends CC[Boolean]

for {
    Some(M(map)) <- List(JSON.parseFull(jsonString))
    M(locMap) = map("Locations")
    L(list) = locMap("list")
    description <- list
    M(desc) = description
    S(id) = desc("id")
} yield id
// res0: List[String] = List(dev123, dev59)
Community
  • 1
  • 1
huynhjl
  • 41,520
  • 14
  • 105
  • 158
1

Is this what you need? (using lift-json)

scala> import net.liftweb.json._
import net.liftweb.json._

scala> implicit val formats = DefaultFormats
formats: net.liftweb.json.DefaultFormats.type = net.liftweb.json.DefaultFormats$@79e379

scala> val jsonString = """{"Locations":
  {"list":
    [
      {"description": "some description", "name": "the name", "id": "dev123"},
      {"description": "other description", "name": "other name", "id": "dev59"}
    ]
  }
}"""
jsonString: java.lang.String =
{"Locations":
  {"list":
    [
      {"description": "some description", "name": "the name", "id": "dev123"},
      {"description": "other description", "name": "other name", "id": "dev59"}
    ]
  }
}

scala> Serialization.read[Map[String, Map[String, List[Map[String, String]]]]](jsonString)
res43: Map[String,Map[String,List[Map[String,String]]]] = Map(Locations -> Map(list -> List(Map(description -> some desc
ription, name -> the name, id -> dev123), Map(description -> other description, name -> other name, id -> dev59))))

scala> val json = parse(jsonString)
json: net.liftweb.json.package.JValue = JObject(List(JField(Locations,JObject(List(JField(list,JArray(List(JObject(List(
JField(description,JString(some description)), JField(name,JString(the name)), JField(id,JString(dev123)))), JObject(Lis
t(JField(description,JString(other description)), JField(name,JString(other name)), JField(id,JString(dev59))))))))))))

scala> json \\ "id"
res44: net.liftweb.json.JsonAST.JValue = JObject(List(JField(id,JString(dev123)), JField(id,JString(dev59))))

scala> compact(render(res44))
res45: String = {"id":"dev123","id":"dev59"}
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
  • Thanks. Looks more complicated than other answers though. And the `net.liftweb.json` suggests a third-party library usage (from Lift framework I guess). – FilipK Oct 17 '11 at 12:11
  • @FilipK: lift-json comes as a separate package. You don't have to pull in entire lift framework into your project. And I am new at it. For sure there must be an easier lift-json way to do what you want. – missingfaktor Oct 17 '11 at 13:23
0

In a branch of SON of JSON, this will work. Note that I'm not using the parser. Not that it doesn't exist. It's just that creating an JSON object using the builder methods is easier:

scala> import nl.typeset.sonofjson._
import nl.typeset.sonofjson._

scala> var all = obj(
     |   locations = arr(
     |     obj(description = "foo", id = "807",
     |     obj(description = "bar", id = "23324"
     |   )
     | )

scala> all.locations.map(_.id).as[List[String]]
res2: List[String] = List(23324, 807)

Or use a for comprehension:

scala> (for (location <- all.locations) yield location.id).as[List[String]]
res4: List[String] = List(23324, 807)
Wilfred Springer
  • 10,869
  • 4
  • 55
  • 69