2010-11-13 3 views
92

Я использую сборку в классе JSON в Scala 2.8 для разбора кода JSON. Я не хочу использовать Liftweb один или любой другой из-за минимизации зависимостей.Как разобрать JSON в Scala с использованием стандартных классов Scala?

То, как я это делаю, кажется слишком необходимым, есть ли лучший способ сделать это?

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

ответ

104

Это решение, основанное на экстракторов, который будет делать класс бросок:

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 я использую тот факт, что генераторы (используя <-) и определения значений (используя =) будут использовать методы unapply.

(Older ответ отредактированы прочь - проверить историю изменений, если вам интересно)

+0

Мне нравится ваш подход редактировать 2 декларирования объектов с ожидаемыми типами и с методом Исключить. Если вы опубликуете его как отдельный ответ, я его проголосую. – Steve

+0

Извините, что выкопал старую почту, но в чем смысл первого Some (M (map)) в цикле? Я понимаю, что M (карта) извлекает карту в переменную «карта», но как насчет Some? –

+1

@FedericoBonelli, 'JSON.parseFull' возвращает' Option [Any] ', поэтому он начинается с' List (None) 'или' List (Some (any)) '. 'Some' предназначен для сопоставления шаблонов на' Option'. – huynhjl

7

Я попробовал несколько вещей, в пользу сопоставления с образцом, как способ избежать литья, но столкнулся с проблемой с типом стиранием о типах сбора.

Основная проблема заключается в том, что полный тип результата анализа отражает структуру данных JSON и является либо громоздким, либо невозможным для полного состояния. Я думаю, именно поэтому Любое используется для усечения определений типов. Использование Любое приводит к необходимости литья.

Я взломал что-то ниже, которое является кратким, но чрезвычайно специфичным для данных 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} 
+0

Мне нравится пользователь implicit, чтобы его извлечь. – Phil

10

Это путь я делаю поиск по шаблону:

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

Мне нравится @ ответ huynhjl, он привел меня вниз правильный путь. Однако при обработке условий ошибок это не очень удобно. Если нужный узел не существует, вы получаете исключение cast. Я немного адаптировал это, чтобы использовать 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" 
    } 
} 
+1

Я думаю, что CC unapply может быть значительно упрощено для 'def unapply (a: Option [Any]): Option [T] = a.map (_ asInstanceOf [T])'. – Suma

+0

Scala 2.12, похоже, нужен ';' перед строками с «=» в понимании. – akauppi

+0

Для меня самый верхний код не «дал пустой список, если какой-либо из json-узлов отсутствует», но вместо этого дал «MatchError» (Scala 2.12). Нужно обернуть это для блока try/catch для этого. Любые более приятные идеи? – 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))) 
Смежные вопросы