122 votes

Comment analyser JSON dans Scala en utilisant des classes Scala standard?

J'utilise la classe JSON intégrée à Scala 2.8 pour analyser le code JSON. Je ne veux pas utiliser le Liftweb l'un ou l'autre en raison de la réduction des dépendances.

La façon dont je le fais me semble trop impérative, existe-t-il une meilleure façon de le faire?

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

135voto

huynhjl Points 26045

Ceci est une solution basée sur des extracteurs qui feront le cast de classe:

 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))
    L(languages) = map("languages")
    M(language) <- languages
    S(name) = language("name")
    B(active) = language("is_active")
    D(completeness) = language("completeness")
} yield {
    (name, active, completeness)
}
 

Au début de la boucle for, j’emballe artificiellement le résultat dans une liste afin qu’elle produise une liste à la fin. Ensuite, dans le reste de la boucle for, j'utilise le fait que les générateurs (en utilisant <- ) et les définitions de valeur (en utilisant = ) utiliseront les méthodes de non application.

(Ancienne réponse supprimée - vérifiez l'historique de modification si vous êtes curieux)

25voto

Matthias Braun Points 1114

C’est comme je le fais la correspondance de modèle :

13voto

murrayju Points 241

J'aime @huynhjl réponse, il m'a conduit sur le droit chemin. Cependant, il n'est pas excellent dans le maniement des conditions d'erreur. Lorsqu'un nœud n'existe pas, vous obtenez un casting d'exception. J'ai adapté un peu à faire usage de l' Option afin de mieux les gérer.

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

Bien sûr, ce ne gère pas les erreurs autant que de les éviter. Cela vous donnera une liste vide si tout le json nœuds sont manquants. Vous pouvez utiliser un match , pour vérifier la présence d'un nœud avant d'agir...

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

8voto

Don Mackenzie Points 3639

J'ai essayé un peu les choses, en favorisant la correspondance de modèle comme un moyen d'éviter de casting, mais a rencontré des problèmes avec le type d'effacement sur les types de collection.

Le principal problème semble être que le type de l'analyse résultat reflète la structure de données JSON et est soit difficile, voire impossible pour complètement de l'état. Je suppose que c'est pourquoi Tout est utilisé pour tronquer les définitions de type. À l'aide de Tout conduit à la nécessité pour la coulée.

J'ai piraté quelque chose en dessous de ce qui est concis, mais il est extrêmement spécifique pour les données JSON implicite par le code de la question. Quelque chose de plus général serait plus satisfaisante, mais je ne sais pas si ça serait très élégante.

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}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X