2010-11-13 10 views
92

Estoy usando la compilació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.¿Cómo se analiza JSON en Scala usando las clases estándar de Scala?

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

Respuesta

104

Esta es una solución basada en extractores que hará el reparto de 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 inicio del bucle for Me artificialmente envuelvo el resultado en una lista para que dé una lista en el fin. Luego, en el resto del ciclo for use el hecho de que los generadores (usando <-) y las definiciones de valores (usando =) harán uso de los métodos de desaplicación.

(editado respuesta Mayor de distancia - comprobar la historia de edición si tienes curiosidad)

+0

Me gusta su enfoque de edición 2 de declarar objetos con los tipos previstos y un método de cancelar la aplicación. Si lo publica como una respuesta separada, lo votaré. – Steve

+0

Perdón por desenterrar una publicación anterior, pero ¿cuál es el significado de la primera Parte (M (mapa)) en el ciclo? Entiendo que el M (mapa) está extrayendo el mapa a la variable "mapa", pero ¿qué hay del Algunos? –

+1

@FedericoBonelli, 'JSON.parseFull' devuelve' Opción [Cualquiera] ', por lo que comienza con' List (None) 'o' List (Some (any)) '. El 'Algunos' es para la coincidencia de patrones en' Opción'. – huynhjl

7

he intentado un par de cosas, lo que favorece la coincidencia de patrones como una forma de evitar la fundición, pero se metió en problemas con el tipo de borrado sobre 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 usa para truncar las definiciones de tipo. Usando Cualquier conduce a la necesidad de fundición.

He pirateado algo debajo de lo cual 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} 
+0

Me gusta el usuario de implicit's para extraerlo. – Phil

10

Esta es la forma en que hago la comparación de patrones:

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

me gusta @ respuesta de huynhjl, que 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. He adaptado esto ligeramente para hacer uso de 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 tanto los errores como los evita. Esto arrojará una lista vacía si falta alguno de los nodos json. Puede utilizar un match para comprobar 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" 
    } 
} 
+1

Creo que la aplicación de CC puede simplificarse significativamente para 'def unpply (a: Option [Any]): Option [T] = a.map (_. AsInstanceOf [T]) '. – Suma

+0

Scala 2.12 parece necesitar ';' antes de las líneas con '=' en el para comprensión. – akauppi

+0

Para mí, el código más alto no "dio como resultado una lista vacía si falta alguno de los nodos json", pero dio un 'MatchError' en su lugar (Scala 2.12). Necesario para envolver el para en un bloque de prueba/captura para eso. ¿Alguna idea más agradable? – akauppi

2
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))) 
Cuestiones relacionadas