How to parse JSON in Scala using standard Scala classes?
This is a solution based on extractors which will do the class cast:
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]val jsonString = """ { "languages": [{ "name": "English", "is_active": true, "completeness": 2.5 }, { "name": "Latin", "is_active": false, "completeness": 0.9 }] } """.stripMarginval result = for { Some(M(map)) <- List(JSON.parseFull(jsonString)) L(languages) = map("languages") M(language) <- languages S(name) = language("name") B(active) = language("is_active") D(completeness) = language("completeness")} yield { (name, active, completeness)}assert( result == List(("English",true,2.5), ("Latin",false,0.9)))
At the start of the for loop I artificially wrap the result in a list so that it yields a list at the end. Then in the rest of the for loop I use the fact that generators (using <-
) and value definitions (using =
) will make use of the unapply methods.
(Older answer edited away - check edit history if you're curious)
This is the way I do the pattern match:
val result = JSON.parseFull(jsonStr)result match { // Matches if jsonStr is valid JSON and represents a Map of Strings to Any case Some(map: Map[String, Any]) => println(map) case None => println("Parsing failed") case other => println("Unknown data structure: " + other)}
I like @huynhjl's answer, it led me down the right path. However, it isn't great at handling error conditions. If the desired node does not exist, you get a cast exception. I've adapted this slightly to make use of Option
to better handle this.
class CC[T] { def unapply(a:Option[Any]):Option[T] = if (a.isEmpty) { None } else { Some(a.get.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 { M(map) <- List(JSON.parseFull(jsonString)) L(languages) = map.get("languages") language <- languages M(lang) = Some(language) S(name) = lang.get("name") B(active) = lang.get("is_active") D(completeness) = lang.get("completeness")} yield { (name, active, completeness)}
Of course, this doesn't handle errors so much as avoid them. This will yield an empty list if any of the json nodes are missing. You can use a match
to check for the presence of a node before acting...
for { M(map) <- Some(JSON.parseFull(jsonString))} yield { map.get("languages") match { case L(languages) => { for { language <- languages M(lang) = Some(language) S(name) = lang.get("name") B(active) = lang.get("is_active") D(completeness) = lang.get("completeness") } yield { (name, active, completeness) } } case None => "bad json" }}