¿Cómo se analiza JSON en Scala usando clases estándar de Scala?

Estoy usando la comstackción en la clase JSON en Scala 2.8 para analizar el código JSON. No quiero usar Liftweb ni uno ni ningún otro debido a la minimización de dependencias.

La forma en que lo estoy haciendo parece demasiado imperativa, ¿hay una mejor manera de hacerlo?

import scala.util.parsing.json._ ... val json:Option[Any] = JSON.parseFull(jsonString) val map:Map[String,Any] = json.get.asInstanceOf[Map[String, Any]] val languages:List[Any] = map.get("languages").get.asInstanceOf[List[Any]] languages.foreach( langMap => { val language:Map[String,Any] = langMap.asInstanceOf[Map[String,Any]] val name:String = language.get("name").get.asInstanceOf[String] val isActive:Boolean = language.get("is_active").get.asInstanceOf[Boolean] val completeness:Double = language.get("completeness").get.asInstanceOf[Double] } 

Esta es una solución basada en extractores que hará el elenco de la clase:

 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 }] } """.stripMargin val 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))) 

Al comienzo del bucle for, envuelvo artificialmente el resultado en una lista para que arroje una lista al final. Luego, en el rest del ciclo for, utilizo el hecho de que los generadores (usando < - ) y las definiciones de valores (usando = ) harán uso de los métodos de desaplicación.

(Respuesta anterior editada fuera - verifique el historial de edición si tiene curiosidad)

Esta es la forma en que hago la coincidencia de patrón:

 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) } 

Me gusta la respuesta de @huynhjl, me llevó por el camino correcto. Sin embargo, no es excelente para manejar condiciones de error. Si el nodo deseado no existe, obtendrá una excepción de conversión. Lo he adaptado ligeramente para hacer uso de la Option para manejar mejor esto.

 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) } 

Por supuesto, esto no maneja errores tanto como evitarlos. Esto arrojará una lista vacía si falta alguno de los nodos json. Puede usar una match para verificar la presencia de un nodo antes de actuar ...

 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" } } 

Intenté algunas cosas, favoreciendo la coincidencia de patrones como una forma de evitar el lanzamiento, pero tuve problemas con el borrado de tipos en los tipos de colección.

El problema principal parece ser que el tipo completo del resultado de análisis refleja la estructura de los datos JSON y es engorroso o imposible de completar. Supongo que es por eso que Any se utiliza para truncar las definiciones de tipo. Usar Cualquiera conduce a la necesidad de lanzar.

He pirateado algo debajo que es conciso pero es extremadamente específico para los datos JSON implicados por el código en la pregunta. Algo más general sería más satisfactorio, pero no estoy seguro de si sería muy elegante.

 implicit def any2string(a: Any) = a.toString implicit def any2boolean(a: Any) = a.asInstanceOf[Boolean] implicit def any2double(a: Any) = a.asInstanceOf[Double] case class Language(name: String, isActive: Boolean, completeness: Double) val languages = JSON.parseFull(jstr) match { case Some(x) => { val m = x.asInstanceOf[Map[String, List[Map[String, Any]]]] m("languages") map {l => Language(l("name"), l("isActive"), l("completeness"))} } case None => Nil } languages foreach {println} 
 val jsonString = """ |{ | "languages": [{ | "name": "English", | "is_active": true, | "completeness": 2.5 | }, { | "name": "Latin", | "is_active": false, | "completeness": 0.9 | }] |} """.stripMargin val result = JSON.parseFull(jsonString).map { case json: Map[String, List[Map[String, Any]]] => json("languages").map(l => (l("name"), l("is_active"), l("completeness"))) }.get println(result) assert( result == List(("English", true, 2.5), ("Latin", false, 0.9)) )