Wie analysiere ich JSON in Scala mit Standard-Scala-Klassen?

113

Ich verwende die Build-in-JSON-Klasse in Scala 2.8, um JSON-Code zu analysieren. Ich möchte das eine oder andere Liftweb nicht verwenden, da die Abhängigkeiten minimiert werden.

Die Art und Weise, wie ich es mache, 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]
}
Phil
quelle

Antworten:

129

Dies ist eine Lösung, die auf Extraktoren basiert, die die Klassenbesetzung durchführen:

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 verpacke ich das Ergebnis künstlich in eine Liste, sodass am Ende eine Liste angezeigt wird. Dann verwende ich im Rest der for-Schleife die Tatsache, dass Generatoren (using <-) und =Wertedefinitionen (using ) die unapply-Methoden verwenden.

(Ältere Antwort weggeschnitten - überprüfen Sie den Bearbeitungsverlauf, wenn Sie neugierig sind)

huynhjl
quelle
Es tut mir leid, einen alten Beitrag ausgegraben zu haben, aber was bedeutet das erste Some (M (Karte)) in der Schleife? Ich verstehe, dass das M (Karte) die Karte in die Variable "Karte" extrahiert, aber was ist mit den Einige?
Federico Bonelli
1
@FedericoBonelli, JSON.parseFullkehrt zurück Option[Any], also beginnt es mit List(None)oder List(Some(any)). Das Someist für die Musterübereinstimmung auf Option.
Huynhjl
21

So mache ich den Pattern Match:

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)
}
Matthias Braun
quelle
Können Sie ein Beispiel für Ihr jsonStr geben, es funktioniert nicht mit dem obigen Beispiel von jsonStr
priya khokher
Es könnte sich lohnen, eine eigene Frage zu Ihrem Problem zu stellen. Ich habe derzeit Scala nicht auf meinem Computer installiert, daher habe ich keine JSON-Zeichenfolge bereit.
Matthias Braun
12

Ich mag die Antwort von @ huynhjl, sie hat mich auf den richtigen Weg geführt. Es ist jedoch nicht besonders gut im Umgang mit Fehlerbedingungen. Wenn der gewünschte Knoten nicht vorhanden ist, erhalten Sie eine Cast-Ausnahme. Ich habe dies leicht angepasst, um Optiones besser nutzen zu können.

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

Dies behandelt natürlich Fehler nicht so sehr, als dass sie vermieden werden. Dies ergibt eine leere Liste, wenn einer der JSON-Knoten fehlt. Sie können a verwenden match, um 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"
  }
}
Murrayju
quelle
3
Ich denke, CC unapply kann erheblich vereinfacht werden def unapply(a: Option[Any]): Option[T] = a.map(_.asInstanceOf[T]).
Suma
Scala 2.12 scheint ';' zu brauchen. vor Zeilen mit '=' im zum Verständnis.
Akauppi
Für mich ergab der oberste Code keine "leere Liste, wenn einer der JSON-Knoten fehlt", sondern eine MatchError(Scala 2.12). Musste das für in einen Try / Catch-Block dafür einwickeln. Irgendwelche schöneren Ideen?
Akauppi
7

Ich habe ein paar Dinge ausprobiert und den Mustervergleich bevorzugt, um das Casting zu vermeiden, aber ich hatte Probleme mit der Typlöschung der Sammlungstypen.

Das Hauptproblem scheint zu sein, dass der vollständige Typ des Analyseergebnisses die Struktur der JSON-Daten widerspiegelt und entweder umständlich oder nicht vollständig anzugeben ist. Ich denke, deshalb wird Any verwendet, um die Typdefinitionen abzuschneiden. Die Verwendung von Any führt zur Notwendigkeit des Castings.

Ich habe etwas gehackt, das kurz ist, aber äußerst spezifisch für die JSON-Daten ist, die durch den Code in der Frage impliziert werden. Etwas allgemeineres wäre zufriedenstellender, 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}
Don Mackenzie
quelle
Ich mag den Benutzer von impliziten, um es zu extrahieren.
Phil
4
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)) )
Yuriy Tumakha
quelle
3
Dies ist in der neuesten Scala Unbundled veraltet. Irgendeine Idee, wie man es dann benutzt?
Sanket_patil
4

Das kannst du so machen! Sehr einfach zu analysierender JSON-Code: P.

package org.sqkb.service.common.bean

import java.text.SimpleDateFormat

import org.json4s
import org.json4s.JValue
import org.json4s.jackson.JsonMethods._
//import org.sqkb.service.common.kit.{IsvCode}

import scala.util.Try

/**
  *
  */
case class Order(log: String) {

  implicit lazy val formats = org.json4s.DefaultFormats

  lazy val json: json4s.JValue = parse(log)

  lazy val create_time: String = (json \ "create_time").extractOrElse("1970-01-01 00:00:00")
  lazy val site_id: String = (json \ "site_id").extractOrElse("")
  lazy val alipay_total_price: Double = (json \ "alipay_total_price").extractOpt[String].filter(_.nonEmpty).getOrElse("0").toDouble
  lazy val gmv: Double = alipay_total_price
  lazy val pub_share_pre_fee: Double = (json \ "pub_share_pre_fee").extractOpt[String].filter(_.nonEmpty).getOrElse("0").toDouble
  lazy val profit: Double = pub_share_pre_fee

  lazy val trade_id: String = (json \ "trade_id").extractOrElse("")
  lazy val unid: Long = Try((json \ "unid").extractOpt[String].filter(_.nonEmpty).get.toLong).getOrElse(0L)
  lazy val cate_id1: Int = (json \ "cate_id").extractOrElse(0)
  lazy val cate_id2: Int = (json \ "subcate_id").extractOrElse(0)
  lazy val cate_id3: Int = (json \ "cate_id3").extractOrElse(0)
  lazy val cate_id4: Int = (json \ "cate_id4").extractOrElse(0)
  lazy val coupon_id: Long = (json \ "coupon_id").extractOrElse(0)

  lazy val platform: Option[String] = Order.siteMap.get(site_id)


  def time_fmt(fmt: String = "yyyy-MM-dd HH:mm:ss"): String = {
    val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    val date = dateFormat.parse(this.create_time)
    new SimpleDateFormat(fmt).format(date)
  }

}
Echo Zeng
quelle
2

So mache ich die Scala Parser Combinator Library:

import scala.util.parsing.combinator._
class ImprovedJsonParser extends JavaTokenParsers {

  def obj: Parser[Map[String, Any]] =
    "{" ~> repsep(member, ",") <~ "}" ^^ (Map() ++ _)

  def array: Parser[List[Any]] =
    "[" ~> repsep(value, ",") <~ "]"

  def member: Parser[(String, Any)] =
    stringLiteral ~ ":" ~ value ^^ { case name ~ ":" ~ value => (name, value) }

  def value: Parser[Any] = (
    obj
      | array
      | stringLiteral
      | floatingPointNumber ^^ (_.toDouble)
      |"true"
      |"false"
    )

}
object ImprovedJsonParserTest extends ImprovedJsonParser {
  def main(args: Array[String]) {
    val jsonString =
    """
      {
        "languages": [{
            "name": "English",
            "is_active": true,
            "completeness": 2.5
        }, {
            "name": "Latin",
            "is_active": false,
            "completeness": 0.9
        }]
      }
    """.stripMargin


    val result = parseAll(value, jsonString)
    println(result)

  }
}
hmehdi
quelle