scoder
scoder

Reputation: 2611

scala parse json which has List of maps

I have some json when I parsed that it is returning stucture Some(List(Map...))

How to match with something and get the values

Below is the code I tried, I need to get all the map values

 import scala.util.parsing.json._
 val result = JSON.parseFull("[{\"start\":\"starting\",\"test\":123,\"test2\":324,\"end\":\"ending\"}]")


 result match {
   case Some(map: Map[String, Any]) => { println(map) 

   }

   case None => println("Parsing failed")
   case other => println("Unknown data structure: " + other)
 }

but its printing non matching

 Unknown data structure: Some(List(Map(start -> starting, test -> 123, test2 -> 324, end -> ending)))

Upvotes: 1

Views: 1497

Answers (3)

Ra Ka
Ra Ka

Reputation: 3055

Because of type erasure, you cannot pattern match on generics types. List, Map, Option are generic containers, and in runtime compiler will erase types of these generic containers. e.g. List[String], String will be erased and type will be List[_].

case Some(map: List[Map[String, Any]]) => println(map) 

In your case above case, if result is val result: Option[Any] = Some(List(12)) i.e. 12, which type is Int and not Map[String, Any], compiler will still match the result with above case even it expects Map[String, Any]] as type of List.

So, Whats going on?

Its all because of type erasure. The compiler will erase all the types and it won't have any type information at runtime unless you use reflection. That means: case Some(map: List[Map[String, Any]]) => println(map) is essentially case Some(map: List[_]) => println(map) and therefore, match will success for any type parameter of List e.g. List[Map[String, Any]], List[Map[String, Int]], List[String], List[Int] etc.

Therefore, In case you need to match on such generic container, you have to resolve each container and its nested subtypes explicitly.

def resolve(result: Any): Unit = result match {
    case Some(res) if res.isInstanceOf[List[_]] && res.asInstanceOf[List[_]].isEmpty  => println("Empty List.")  //Some(List())
    case Some(res) if res.isInstanceOf[List[_]] && !res.asInstanceOf[List[_]].exists(p => p.isInstanceOf[Map[_, _]] && p.asInstanceOf[Map[_, _]].nonEmpty) => println("List is not empty but each item of List is empty Map.")  //Some(List(Map(), Map()))
    case Some(res) if res.isInstanceOf[List[_]] && res.asInstanceOf[List[_]].filter(p => p.isInstanceOf[Map[_, _]] && p.asInstanceOf[Map[_, _]].nonEmpty).map(_.asInstanceOf[Map[_,_]]).exists(p => {p.head match {case e if e._1.isInstanceOf[String] && e._2.isInstanceOf[Any] => true; case _ => false}})  => println("Correct data.") // Some(List(Map("key1"-> 1), Map("key2" -> 2)))
    case None => println("none")
    case other => println("other")
}

val a: Option[Any] = Some(List())
val b: Option[Any] = Some(List(Map(), Map()))
val c: Option[Any] = Some(List(Map("key1"-> 1), Map("key2" -> 2)))
val d: Option[Any] = None
val e: Option[Any] = Some("apple")

resolve(a) // match first case
resolve(b) // match second case
resolve(c) // match third case
resolve(d) // match fourth case
resolve(e) // match fifth case

Upvotes: 2

Tim
Tim

Reputation: 27356

As has been pointed out the return type is actually Option[List[Map[String, Any]]] so you need to unpick this. However you cannot do this with a single match because of type erasure, so you need to do nested matches to ensure that you have the correct type. This is really tedious, so I thoroughly recommend using something like the Extraction.extract function in json4s that will attempt to match your JSON to a specific Scala type:

type ResultType = List[Map[String, Any]]

def extract(json: JValue)(implicit formats: Formats, mf: Manifest[ResultType]): ResultType =
  Extraction.extract[ResultType](json)

If you must do it by hand, it looks something like this:

result match {
  case Some(l: List[_]) =>
    l.headOption match {
      case Some(m) =>
        m match {
          case m: Map[_,_] =>
            m.headOption match {
              case Some(p) =>
                p match {
                  case (_: String, _) =>
                    m.foreach(println(_))
                  case _ => println("Map key was not String")
                }
              case _ => println("Map was empty")
            }
          case _ => println("List did not contain a Map")
        }
      case _ => println("Result List was empty")
    }
  case _ => println("Parsing failed")
}

Upvotes: 2

James Whiteley
James Whiteley

Reputation: 3468

Your output is Option[List[Map[String, Any]]], not Option[Map[String, Any]]. match on the List and you'll be fine:

import scala.util.parsing.json._
val result = JSON.parseFull("[{\"start\":\"starting\",\"test\":123,\"test2\":324,\"end\":\"ending\"}]")

val l: List[Map[String, Any]] = result match {
  case Some(list: List[Map[String, Any]]) => list
  case _ => throw new Exception("I shouldn't be here") // whatever for a non-match
}

Then you can map (if you want a non-Unit return type)/ foreach (if you don't care about a Unit return type) on that List and do whatever you want it:

l.foreach(println)
l.map(_.toString) // or whatever you want ot do with the Map

Upvotes: -2

Related Questions