วิธีแยกวิเคราะห์ JSON ใน Scala โดยใช้คลาส Scala มาตรฐาน


113

ฉันใช้บิลด์ในคลาส JSON ใน Scala 2.8 เพื่อแยกวิเคราะห์โค้ด JSON ฉันไม่ต้องการใช้ Liftweb อย่างใดอย่างหนึ่งเนื่องจากการลดการอ้างอิง

วิธีที่ฉันทำดูเหมือนว่าจำเป็นเกินไปมีวิธีที่ดีกว่านี้ไหม

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

คำตอบ:


130

นี่เป็นวิธีแก้ปัญหาที่ใช้ตัวแยกซึ่งจะทำการร่ายคลาส:

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 loop ฉันจะห่อผลลัพธ์ไว้ในรายการโดยเทียมเพื่อให้ได้รายการในตอนท้าย จากนั้นในส่วนที่เหลือของ for loop ฉันใช้ข้อเท็จจริงที่ว่าเครื่องกำเนิดไฟฟ้า (โดยใช้<-) และคำจำกัดความของค่า (โดยใช้=) จะใช้ประโยชน์จากวิธีการที่ไม่ได้ใช้

(คำตอบที่เก่ากว่าแก้ไขออกไป - ตรวจสอบประวัติการแก้ไขหากคุณสงสัย)


ขออภัยที่ขุดโพสต์เก่า ๆ แต่ความหมายของ Some (M (แผนที่)) แรกในลูปคืออะไร ฉันเข้าใจว่า M (แผนที่) กำลังแตกแผนที่ไปยังตัวแปร "แผนที่" แต่บางอย่างล่ะ?
Federico Bonelli

1
@FedericoBonelli, JSON.parseFullผลตอบแทนOption[Any]จึงเริ่มต้นด้วยหรือList(None) สำหรับการจับคู่รูปแบบบน List(Some(any))SomeOption
huynhjl

21

นี่คือวิธีที่ฉันจับคู่รูปแบบ:

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 ของคุณได้ไหมมันใช้ไม่ได้กับตัวอย่างข้างบนของ jsonStr
priya khokher

คุณควรโพสต์คำถามเกี่ยวกับปัญหาของคุณเอง ขณะนี้ฉันยังไม่ได้ติดตั้ง Scala บนเครื่องของฉันดังนั้นฉันจึงไม่มีสตริง JSON พร้อม
Matthias Braun

12

ฉันชอบคำตอบของ @ huynhjl มันทำให้ฉันไปถูกทาง อย่างไรก็ตามการจัดการเงื่อนไขข้อผิดพลาดนั้นไม่ดีนัก หากไม่มีโหนดที่ต้องการคุณจะได้รับข้อยกเว้นในการร่าย ฉันได้ปรับสิ่งนี้เล็กน้อยเพื่อใช้ประโยชน์ในOptionการจัดการสิ่งนี้ให้ดีขึ้น

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

แน่นอนว่าสิ่งนี้ไม่ได้จัดการกับข้อผิดพลาดมากนักเนื่องจากหลีกเลี่ยงข้อผิดพลาดเหล่านี้ สิ่งนี้จะให้รายการว่างหากโหนด json ขาดหายไป คุณสามารถใช้matchเพื่อตรวจสอบการมีอยู่ของโหนดก่อนที่จะดำเนินการ ...

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 สามารถทำให้ง่ายdef unapply(a: Option[Any]): Option[T] = a.map(_.asInstanceOf[T])ขึ้นได้อย่างมาก
สุ

Scala 2.12 ดูเหมือนจะต้องการ ';' ก่อนบรรทัดด้วย '=' ในเพื่อความเข้าใจ
akauppi

สำหรับฉันรหัสบนสุดไม่ได้ "ให้รายการว่างถ้าโหนด json ใด ๆ หายไป" แต่ให้MatchErrorแทน (Scala 2.12) จำเป็นต้องห่อสำหรับในบล็อก try / catch สำหรับสิ่งนั้น ความคิดที่ดีกว่า?
akauppi

7

ฉันลองทำบางอย่างโดยชอบการจับคู่รูปแบบเพื่อหลีกเลี่ยงการแคสต์ แต่ประสบปัญหาในการลบประเภทในประเภทคอลเลกชัน

ปัญหาหลักน่าจะเป็นประเภทที่สมบูรณ์ของผลการแยกวิเคราะห์สะท้อนโครงสร้างของข้อมูล JSON และอาจยุ่งยากหรือเป็นไปไม่ได้ที่จะระบุทั้งหมด ฉันเดาว่านั่นคือเหตุผลที่ใช้Anyเพื่อตัดทอนคำจำกัดความประเภท ใช้Anyนำไปสู่ความจำเป็นในการหล่อ

ฉันได้แฮ็กบางสิ่งด้านล่างซึ่งมีความกระชับ แต่มีความเฉพาะเจาะจงอย่างยิ่งกับข้อมูล JSON ที่นัยโดยรหัสในคำถาม สิ่งที่ทั่วไปน่าพอใจกว่านี้ แต่ฉันไม่แน่ใจว่ามันจะสวยหรูมากไหม

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}

ฉันชอบให้ผู้ใช้ของ implicit ดึงมันออกมา
ฟิลิป

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
สิ่งนี้เลิกใช้แล้วใน Scala ล่าสุด, Unbundled มีความคิดอย่างไรที่จะใช้มัน?
Sanket_patil

4

ทำได้แบบนี้! ง่ายมากที่จะแยกวิเคราะห์รหัส 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

นี่คือวิธีที่ฉันทำ 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)

  }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.