2010-11-13 23 views
92

Tôi đang sử dụng xây 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 hoặc bất kỳ khác do giảm thiểu phụ thuộc.Làm thế nào để phân tích cú pháp JSON trong Scala bằng cách sử dụng các lớp Scala tiêu chuẩn?

Cách tôi làm dường như quá cấp bách, có cách nào tốt hơn để làm điều đó 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] 
} 

Trả lời

104

Đây là một giải pháp dựa trên nhổ mà sẽ làm các diễn viê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))) 

Tại sự bắt đầu của vòng lặp for tôi nhân tạo quấn kết quả trong một danh sách để nó mang lại một danh sách ở kết thúc. Sau đó, trong phần còn lại của vòng lặp tôi sử dụng thực tế là máy phát điện (sử dụng <-) và định nghĩa giá trị (sử dụng =) sẽ sử dụng các phương pháp không áp dụng.

(câu trả lời cũ sửa đi - kiểm tra lịch sử sửa đổi nếu bạn tò mò)

+0

Tôi thích cách tiếp cận chỉnh sửa 2 lại tuyên bố đối tượng với các loại dự kiến ​​và một phương pháp unapply. Nếu bạn đăng nó như là một câu trả lời riêng biệt, tôi sẽ bỏ phiếu cho nó. – Steve

+0

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

+1

@FedericoBonelli, 'JSON.parseFull' trả về' Option [Any] ', vì vậy nó bắt đầu với' List (None) 'hoặc' List (Some (any)) '. 'Một số' là để khớp mẫu trên' Tùy chọn'. – huynhjl

7

Tôi đã thử một vài điều, ưu phù hợp với mô hình như một cách để tránh đúc nhưng chạy vào rắc rối với loại chô bôi trên các loại bộ sưu tập.

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

Tôi đã tấn công thứ gì đó bên dưới ngắn gọn nhưng cực kỳ cụ thể đối với dữ liệu JSON ngụ ý bởi mã trong câu hỏi. Một cái gì đó tổng quát hơn sẽ là thỏa đáng hơn nhưng tôi không chắc chắn nếu nó sẽ rất thanh lịch.

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} 
+0

Tôi thích người dùng tiềm ẩn để trích xuất nó. – Phil

10

Đây là cách tôi làm mô hình phù hợp:

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

Tôi thích câu trả lời @ huynhjl, nó dẫn tôi xuống con đường đúng đắn. Tuy nhiên, nó không phải là tuyệt vời 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 sẽ nhận được một ngoại lệ cast. Tôi đã điều chỉnh một chút để sử dụng Option để xử lý tốt hơn điều này.

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 có bất kỳ nút json nào bị thiếu. 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" 
    } 
} 
+1

Tôi nghĩ CC không áp dụng có thể được đơn giản hóa đáng kể thành 'def unapply (a: Option [Any]): Option [T] = a.map (_. AsInstanceOf [T])'. – Suma

+0

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

+0

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

2
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))) 
Các vấn đề liên quan