2010-11-13 20 views
92

Ich benutze die Build in JSON-Klasse in Scala 2.8, um JSON-Code zu analysieren. Ich möchte das Liftweb wegen der Minimierung von Abhängigkeiten nicht verwenden.Wie wird JSON in Scala mit Standard-Scala-Klassen analysiert?

Die Art, wie ich es tue, scheint zu zwingend, gibt es einen besseren Weg, es zu tun?

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

Antwort

104

Dies ist eine Lösung auf Extraktoren basiert, die die Klasse Guss tun:

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

Zu Beginn der for-Schleife künstlich das Ergebnis in einer Liste wickeln ich so, dass es eine Liste ergibt bei das Ende. Dann verwende ich im Rest der for-Schleife die Tatsache, dass Generatoren (unter Verwendung von <-) und Wertdefinitionen (unter Verwendung von =) die Nichtanwendungsmethoden verwenden.

(ältere Antwort bearbeitet weg - überprüfen Editierhistorie wenn Sie neugierig sind)

+0

Ich mag deine Bearbeitung 2 Annäherung von Objekten mit den erwarteten Typen und einer Unapply Methode deklarieren. Wenn Sie es als separate Antwort veröffentlichen, werde ich es abstimmen. – Steve

+0

Entschuldigung, um einen alten Post zu graben, aber was bedeutet die erste Some (M (map)) in der Schleife? Ich verstehe, dass das M (map) die Karte der Variablen "map" entnimmt, aber was ist mit dem Some? –

+1

@FedericoBonelli, 'JSON.parseFull' gibt' Option [Any] 'zurück, also beginnt es mit' List (None) 'oder' List (Some (any)) ''. Das 'Some' ist für den Mustervergleich auf' Option'. – huynhjl

7

habe ich versucht, ein paar Dinge, die Begünstigung Mustervergleich als eine Möglichkeit, Gießen zu vermeiden, aber in Schwierigkeiten geriet mit Typ Löschung auf den Sammlungstypen.

Das Hauptproblem scheint zu sein, dass der vollständige Typ des Parse-Ergebnisses die Struktur der JSON-Daten widerspiegelt und entweder schwerfällig oder unmöglich vollständig zu definieren ist. Ich denke, das ist der Grund, warum Any verwendet wird, um die Typdefinitionen zu kürzen. Mit Any führt zu der Notwendigkeit zum Gießen.

Ich habe etwas darunter gehackt, das kurz ist, aber ist extrem spezifisch für die JSON-Daten durch den Code in der Frage impliziert. Etwas allgemeineres wäre befriedigender, aber ich bin mir nicht sicher, ob es sehr elegant wäre.

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

Ich mag den Benutzer von impliziten, um es zu extrahieren. – Phil

10

Dies ist die Art, wie ich das Muster Spiel tun:

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

I @ Antwort des huynhjl mag, sie führte mich auf den richtigen Weg nach unten. Es ist jedoch nicht gut im Umgang mit Fehlerbedingungen. Wenn der gewünschte Knoten nicht existiert, erhalten Sie eine Cast-Ausnahme. Ich habe dies leicht angepasst, um Option zu verwenden, um besser damit umzugehen.

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

Natürlich behandelt dies nicht Fehler so sehr wie sie vermeiden. Dies führt zu einer leeren Liste, wenn einer der Json-Knoten fehlt. Sie können eine match verwenden für das Vorhandensein eines Knotens zu überprüfen, bevor Sie handeln ...

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

Ich denke, dass CC unapply erheblich vereinfacht werden kann, um 'def unapply '(a: Option [Any]) zu vereinfachen: Option [T] = a.map (_. AsInstanceOf [T])'. – Suma

+0

Scala 2.12 scheint ';' vor Zeilen mit '=' zum Verständnis. – akauppi

+0

Für mich ergab der oberste Code keine "leere Liste, wenn einer der Json-Knoten fehlt", sondern gab stattdessen einen "MatchError" (Scala 2.12). Benötigt, um die für einen Versuch/catch-Block dafür zu wickeln. Irgendwelche schöneren Ideen? – 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))) 
Verwandte Themen