Bagaimana cara mengurai JSON di Scala menggunakan kelas Scala standar?

113

Saya menggunakan build in JSON class di Scala 2.8 untuk mengurai kode JSON. Saya tidak ingin menggunakan Liftweb satu atau lainnya karena meminimalkan ketergantungan.

Cara saya melakukannya sepertinya terlalu penting, adakah cara yang lebih baik untuk melakukannya?

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
sumber

Jawaban:

130

Ini adalah solusi berdasarkan ekstraktor yang akan melakukan pemeran kelas:

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

Pada awal perulangan for, saya membungkus hasil secara artifisial dalam daftar sehingga menghasilkan daftar di akhir. Kemudian di sisa loop for saya menggunakan fakta bahwa generator (menggunakan <-) dan definisi nilai (menggunakan =) akan menggunakan metode yang tidak berlaku.

(Jawaban lama diedit - periksa riwayat edit jika Anda penasaran)

huynhjl
sumber
Maaf menggali postingan lama, tapi apa arti Some (M (peta)) pertama dalam lingkaran? Saya mengerti bahwa M (peta) mengekstraksi peta ke variabel "peta", tapi bagaimana dengan Some?
Federico Bonelli
1
@FedericoBonelli, JSON.parseFullkembali Option[Any], jadi ini dimulai dengan List(None)atau List(Some(any)). Ini Someuntuk pencocokan pola pada Option.
huynhjl
21

Ini adalah cara saya melakukan pencocokan pola:

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
sumber
dapatkah Anda memberikan contoh jsonStr Anda, itu tidak berfungsi dengan contoh jsonStr di atas
priya khokher
Mungkin ada baiknya mengeposkan pertanyaan sendiri tentang masalah Anda. Saat ini saya belum menginstal Scala di komputer saya, jadi saya tidak memiliki string JSON yang siap.
Matthias Braun
12

Saya suka jawaban @ huynhjl, itu menuntun saya ke jalan yang benar. Namun, ini tidak bagus dalam menangani kondisi kesalahan. Jika node yang diinginkan tidak ada, Anda mendapatkan pengecualian cast. Saya telah menyesuaikan ini sedikit agar Optiondapat menangani ini dengan lebih baik.

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

Tentu saja, ini tidak menangani kesalahan sebanyak menghindarinya. Ini akan menghasilkan daftar kosong jika salah satu node json hilang. Anda dapat menggunakan a matchuntuk memeriksa keberadaan node sebelum bertindak ...

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
sumber
3
Saya pikir CC tidak dapat disederhanakan secara signifikan def unapply(a: Option[Any]): Option[T] = a.map(_.asInstanceOf[T]).
Suma
Scala 2.12 tampaknya membutuhkan ';' sebelum baris dengan '=' di untuk pemahaman.
akauppi
Bagi saya, kode paling atas tidak "menghasilkan daftar kosong jika salah satu node json hilang", tetapi memberikan sebagai MatchErrorgantinya (Scala 2.12). Diperlukan untuk membungkus untuk dalam blok coba / tangkap untuk itu. Ada ide yang lebih bagus?
akauppi
7

Saya mencoba beberapa hal, mendukung pencocokan pola sebagai cara menghindari casting tetapi mengalami masalah dengan penghapusan jenis pada jenis koleksi.

Masalah utama tampaknya adalah bahwa tipe lengkap hasil parse mencerminkan struktur data JSON dan tidak praktis atau tidak mungkin dinyatakan sepenuhnya. Saya rasa itulah mengapa Any digunakan untuk memotong definisi tipe. Menggunakan Setiap prospek untuk kebutuhan transmisi.

Saya telah meretas sesuatu di bawah ini yang ringkas tetapi sangat spesifik untuk data JSON yang tersirat oleh kode dalam pertanyaan. Sesuatu yang lebih umum akan lebih memuaskan tetapi saya tidak yakin apakah itu akan sangat elegan.

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
sumber
Saya suka pengguna implisit untuk mengekstraknya.
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
sumber
3
Ini tidak berlaku lagi dalam skala terbaru, Tidak Dibundel. Tahu bagaimana cara menggunakannya?
Sanket_patil
4

Kamu bisa melakukan seperti ini! Sangat mudah untuk mengurai kode JSON: 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)
  }

}
Gema Zeng
sumber
2

Ini adalah cara saya melakukan Library Combinator Scala Parser:

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
sumber