JSON, standart Scala sınıflarını kullanarak Scala'da nasıl ayrıştırılır?


113

JSON kodunu ayrıştırmak için Scala 2.8'deki JSON sınıfındaki yapıyı kullanıyorum. Bağımlılıkları en aza indirdiği için Liftweb'i kullanmak istemiyorum.

Bunu yapma şeklim çok zorunlu görünüyor, bunu yapmanın daha iyi bir yolu var mı?

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

Yanıtlar:


130

Bu, sınıf dökümünü yapacak olan çıkarıcılara dayalı bir çözümdür:

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

For döngüsünün başlangıcında sonucu yapay olarak bir listeye sararım, böylece sonunda bir liste verir. Daha sonra, for döngüsünün geri kalanında üreteçlerin (kullanarak <-) ve değer tanımlarının (kullanarak)= ) uygulanmayan yöntemleri kullanacağı .

(Daha eski yanıt düzenlendi - merak ediyorsanız düzenleme geçmişini kontrol edin)


Eski bir gönderiyi kazdığım için üzgünüz, ancak döngüdeki ilk Bazıları (M (harita)) için anlamı nedir? M (harita) 'nın haritayı "harita" değişkenine çıkardığını anlıyorum, peki ya Bazıları?
Federico Bonelli

1
@FedericoBonelli, JSON.parseFulldöner Option[Any], yani List(None)veya ile başlar List(Some(any)). SomeÜzerinde desen eşleştirme içindir Option.
huynhjl

21

Kalıp eşleştirmesini şu şekilde yapıyorum:

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

jsonStr'inizin bir örneğini verebilir misiniz, yukarıdaki jsonStr örneğiyle çalışmıyor
priya khokher

Sorununuz hakkında kendi sorunuzu göndermeye değer olabilir. Şu anda makinemde Scala yüklü değil, bu yüzden hazır bir JSON dizem yok.
Matthias Braun

12

@ Huynhjl'ın cevabını beğendim, beni doğru yola götürdü. Bununla birlikte, hata koşullarını ele almakta harika değildir. İstenen düğüm mevcut değilse, bir çevrim istisnası alırsınız. Bunu daha Optioniyi halletmek için bunu biraz uyarladım .

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

Elbette bu, hataları onlardan kaçınmak kadar ele almaz. Bu, json düğümlerinden herhangi biri eksikse boş bir liste verecektir. matchHarekete geçmeden önce bir düğümün varlığını kontrol etmek için a kullanabilirsiniz ...

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

3
CC unapply'nin önemli ölçüde basitleştirilebileceğini düşünüyorum def unapply(a: Option[Any]): Option[T] = a.map(_.asInstanceOf[T]).
Suma

Scala 2.12'nin ';' anlamak için '=' ile satırlardan önce.
akauppi

Benim için, en üstteki kod "json düğümlerinden herhangi biri eksikse boş bir liste vermedi", MatchErrorbunun yerine bir verdi (Scala 2.12). Bunun için bir dene / yakala bloğuna sarılması gerekiyordu. Daha güzel fikirlerin var mı?
akauppi

7

Dökümden kaçınmanın bir yolu olarak kalıp eşleştirmeyi tercih ederek birkaç şey denedim, ancak koleksiyon türlerinde tür silme konusunda sorunla karşılaştım.

Ana sorun, ayrıştırma sonucunun tam türünün JSON verilerinin yapısını yansıtması ve hantal veya tam olarak belirtilmesinin imkansız olmasıdır. Sanırım bu yüzden tür tanımlarını kesmek için Any kullanılıyor. Herhangi birini kullanma döküm için ihtiyaca yol.

Aşağıda kısa olan ancak söz konusu kodun ima ettiği JSON verilerine son derece özel bir şeyi hackledim. Daha genel bir şey daha tatmin edici olurdu ama çok şık olup olmayacağından emin değilim.

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}

Örtüklerin kullanıcısının onu çıkarmasını seviyorum.
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)) )

3
Bu, son ölçek olan Unbundled'de kullanımdan kaldırılmıştır. Nasıl kullanılacağına dair bir fikrin var mı?
Sanket_patil

4

Bunu beğenebilirsin! JSON kodunu ayrıştırması çok kolay: 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)
  }

}

2

Scala Parser Combinator Kitaplığını böyle yapıyorum:

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)

  }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.