Comment parsingr JSON en Scala en utilisant les classes Scala standard?

J’utilise la classe JSON dans Scala 2.8 pour parsingr le code JSON. Je ne veux pas utiliser l’un ou l’autre de Liftweb en raison de la minimisation des dépendances.

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

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

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

 class CC[T] { def unapply(a:Any):Option[T] = Some(a.asInstanceOf[T]) } object M extends CC[Map[Ssortingng, Any]] object L extends CC[List[Any]] object S extends CC[Ssortingng] object D extends CC[Double] object B extends CC[Boolean] val jsonSsortingng = """ { "languages": [{ "name": "English", "is_active": true, "completeness": 2.5 }, { "name": "Latin", "is_active": false, "completeness": 0.9 }] } """.ssortingpMargin 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))) 

Au début de la boucle for, j'emballe artificiellement le résultat dans une liste afin de générer une liste à la fin. Ensuite, dans le rest 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 non appariées.

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

C’est comme ça que je fais le pattern match:

 val result = JSON.parseFull(jsonStr) result match { // Matches if jsonStr is valid JSON and represents a Map of Ssortingngs to Any case Some(map: Map[Ssortingng, Any]) => println(map) case None => println("Parsing failed") case other => println("Unknown data structure: " + other) } 

J’aime la réponse de @huynhjl, cela m’a conduit sur la bonne voie. Cependant, ce n’est pas génial pour gérer les conditions d’erreur. Si le nœud souhaité n’existe pas, vous obtenez une exception de dissortingbution. Je l’ai légèrement adapté pour utiliser Option pour mieux gérer cela.

 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[Ssortingng, Any]] object L extends CC[List[Any]] object S extends CC[Ssortingng] 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, cela ne gère pas les erreurs autant que les éviter. Cela donnera une liste vide si l'un des nœuds json est manquant. Vous pouvez utiliser une 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" } } 

J’ai essayé quelques petites choses, en privilégiant la correspondance de modèles afin d’éviter le casting, mais j’ai rencontré des problèmes avec le type d’effacement sur les types de collection.

Le principal problème semble être que le type complet du résultat de l’parsing reflète la structure des données JSON et est soit lourd, soit impossible à définir complètement. Je suppose que c’est pourquoi Any est utilisé pour tronquer les définitions de type. L’utilisation de Any entraîne la nécessité de lancer.

J’ai piraté quelque chose en dessous qui est concis mais qui est extrêmement spécifique aux données JSON impliquées par le code dans la question. Quelque chose de plus général serait plus satisfaisant mais je ne suis pas sûr que ce soit très élégant.

 implicit def any2ssortingng(a: Any) = a.toSsortingng implicit def any2boolean(a: Any) = a.asInstanceOf[Boolean] implicit def any2double(a: Any) = a.asInstanceOf[Double] case class Language(name: Ssortingng, isActive: Boolean, completeness: Double) val languages = JSON.parseFull(jstr) match { case Some(x) => { val m = x.asInstanceOf[Map[Ssortingng, List[Map[Ssortingng, Any]]]] m("languages") map {l => Language(l("name"), l("isActive"), l("completeness"))} } case None => Nil } languages foreach {println} 
 val jsonSsortingng = """ |{ | "languages": [{ | "name": "English", | "is_active": true, | "completeness": 2.5 | }, { | "name": "Latin", | "is_active": false, | "completeness": 0.9 | }] |} """.ssortingpMargin val result = JSON.parseFull(jsonSsortingng).map { case json: Map[Ssortingng, List[Map[Ssortingng, 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)) )