Làm cách nào để phân tích cú pháp JSON trong Scala bằng cách sử dụng các lớp Scala chuẩn?


113

Tôi đang sử dụng bản dựng trong lớp JSON trong Scala 2.8 để phân tích cú pháp mã JSON. Tôi không muốn sử dụng Liftweb một hay bất kỳ trang nào khác do giảm thiểu sự phụ thuộc.

Cách tôi đang làm có vẻ quá bắt buộc, có cách nào tốt hơn để làm không?

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

Câu trả lời:


129

Đây là một giải pháp dựa trên trình trích xuất sẽ thực hiện việc truyền lớp:

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

Khi bắt đầu vòng lặp for, tôi bao bọc giả tạo kết quả trong một danh sách để nó tạo ra một danh sách ở cuối. Sau đó, trong phần còn lại của vòng lặp for, tôi sử dụng thực tế là các trình tạo (sử dụng <-) và định nghĩa giá trị (sử dụng =) sẽ sử dụng các phương thức không được áp dụng.

(Câu trả lời cũ hơn đã được chỉnh sửa - hãy kiểm tra lịch sử chỉnh sửa nếu bạn tò mò)


Xin lỗi khi tìm lại một bài viết cũ, nhưng ý nghĩa của một số (M (bản đồ)) đầu tiên trong vòng lặp là gì? Tôi hiểu M (bản đồ) đang trích xuất bản đồ thành "bản đồ" biến, nhưng còn một số thì sao?
Federico Bonelli

1
@FedericoBonelli, JSON.parseFulltrả về Option[Any], vì vậy nó bắt đầu bằng List(None)hoặc List(Some(any)). Là Someđể khớp mẫu trên Option.
huynhjl, 17:14

21

Đây là cách tôi thực hiện khớp mẫu:

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

bạn có thể cho một ví dụ về jsonStr của bạn, nó không làm việc với ví dụ trên của jsonStr
priya khokher

Nó có thể đáng để đăng một câu hỏi riêng về vấn đề của bạn. Tôi hiện chưa cài đặt Scala trên máy tính của mình nên tôi chưa có sẵn chuỗi JSON.
Matthias Braun

12

Tôi thích câu trả lời của @ huynhjl, nó đã dẫn tôi đi đúng con đường. Tuy nhiên, nó không tốt trong việc xử lý các điều kiện lỗi. Nếu nút mong muốn không tồn tại, bạn nhận được một ngoại lệ truyền. Tôi đã điều chỉnh điều này một chút để tận dụng Optionđể xử lý việc này tốt hơn.

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

Tất nhiên, điều này không xử lý lỗi nhiều như tránh chúng. Điều này sẽ mang lại một danh sách trống nếu thiếu bất kỳ nút json nào. Bạn có thể sử dụng một matchđể kiểm tra sự hiện diện của một nút trước khi hành động ...

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
Tôi nghĩ CC chưa áp dụng có thể được đơn giản hóa đáng kể def unapply(a: Option[Any]): Option[T] = a.map(_.asInstanceOf[T]).
Suma

Scala 2.12 dường như cần ';' trước các dòng có '=' trong phần đọc hiểu.
akauppi

Đối với tôi, mã trên cùng không "mang lại danh sách trống nếu thiếu bất kỳ nút json nào", mà MatchErrorthay vào đó là một (Scala 2.12). Cần thiết để bọc for trong một khối try / catch cho điều đó. Bất kỳ ý tưởng tốt hơn?
akauppi

7

Tôi đã thử một vài cách, thích so khớp mẫu như một cách để tránh đúc nhưng lại gặp rắc rối với tính năng xóa kiểu trên các loại bộ sưu tập.

Vấn đề chính dường như là kiểu hoàn chỉnh của kết quả phân tích cú pháp phản ánh cấu trúc của dữ liệu JSON và cồng kềnh hoặc không thể trình bày đầy đủ. Tôi đoán đó là lý do tại sao Any được sử dụng để cắt ngắn các định nghĩa kiểu. Sử dụng Bất kỳ dẫn đến nhu cầu đúc.

Tôi đã hack một cái gì đó dưới đây ngắn gọn nhưng cực kỳ cụ thể đối với dữ liệu JSON được ngụ ý bởi mã trong câu hỏi. Một cái gì đó chung chung hơn sẽ hài lòng hơn nhưng tôi không chắc liệu nó có trang nhã hay không.

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}

Tôi thích người dùng của ẩn để giải nén nó.
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
Tính năng này không còn được dùng trong scala mới nhất, Unbundled. Bất kỳ ý tưởng làm thế nào để sử dụng nó sau đó?
Sanket_patil

4

Bạn có thể làm như thế này! Rất dễ phân tích cú pháp mã 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)
  }

}

2

Đây là cách tôi thực hiện Thư viện Bộ kết hợp Phân tích cú pháp Scala:

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)

  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.