2009-04-08 53 views
9

我正在考慮將一個非常簡單的文本模板庫移植到scala中,主要是作爲學習語言的練習。該庫在Python和JavaScript和它的基本操作或多或少地歸結爲(在python)目前實施:我應該如何在Scala中指定類似JSON的非結構化數據?

template = CompiledTemplate('Text {spam} blah {eggs[1]}') 
data = { 'spam': 1, 'eggs': [ 'first', 'second', { 'key': 'value' }, true ] } 
output = template.render(data) 

這一切都不是非常困難的斯卡拉做,但事情我米不清楚是如何最好地表示data參數的靜態類型。

基本上這個參數應該能夠包含你在JSON中發現的東西的種類:幾個原始類型(字符串,整數,布爾值,空),或者零個或多個項目的列表,或零個或多個項目。 (對於這個問題,地圖可以被限制爲具有字符串鍵,這似乎是斯卡拉如何喜歡的東西)

我最初的想法只是使用Map[string, Any]作爲頂級對象,但這是對我來說似乎不完全正確。實際上,我不想在其中添加任何類的任意對象;我只想要上面列出的元素。同時,我認爲在Java中,我真的能夠得到的最接近的數字是Map<String, ?>,我知道Scala的一位作者設計了Java的泛型。

我特別好奇的一件事是其他類似系統的函數式語言如何處理這類問題。我有一種感覺,我真正想在這裏做的是創建一套我可以模式匹配的案例類,但我無法想象這將會如何。

我有編程斯卡拉,但說實話,我的眼睛開始在協變/逆變的東西上看一看,我希望有人能更清楚簡潔地向我解釋這一點。

回答

14

你想知道你想要某種案例類來爲你的數據類型建模。在函數式語言中,這些東西被稱爲「抽象數據類型」,你可以閱讀關於Haskell如何通過谷歌搜索使用它們的所有信息。斯卡拉相當於Haskell的ADT使用密封的特徵和案例類。

讓我們來看看Scala標準庫或Scala編程書中的rewrite of the JSON parser combinator。不是使用Map [String,Any]來表示JSON對象,而是使用抽象數據類型JsValue來重新聲明JSON值,而不是使用Any來表示任意JSON值。 JsValue具有幾個亞型,表示可能種JSON值:JsStringJsNumberJsObjectJsArrayJsBooleanJsTrueJsFalse),和JsNull

處理此表單的JSON數據涉及模式匹配。由於JsValue是密封的,編譯器會警告你,如果你沒有處理所有的情況。例如,對於toJson的代碼,這需要一個JsValue和返回值的String表示的方法,看起來是這樣的:

def toJson(x: JsValue): String = x match { 
    case JsNull => "null" 
    case JsBoolean(b) => b.toString 
    case JsString(s) => "\"" + s + "\"" 
    case JsNumber(n) => n.toString 
    case JsArray(xs) => xs.map(toJson).mkString("[",", ","]") 
    case JsObject(m) => m.map{case (key, value) => toJson(key) + " : " + toJson(value)}.mkString("{",", ","}") 
    } 

模式匹配都讓我們確保我們正在處理每一個案件,並也「解開」其JsType的潛在價值。它提供了一種類型安全的方式來了解我們處理過的每個案例。此外,如果您在編譯時知道您正在處理的JSON數據的結構,則可以執行一些非常酷的操作,如n8han's extractors。非常強大的東西,檢查出來。

1

那麼,有幾種方法可以解決這個問題。我可能只是使用Map[String, Any],這應該適用於您的目的(只要地圖是從collection.immutable而不是collection.mutable)。不過,如果你真的想通過一些痛苦,也可以給出這方面的類型:

sealed trait InnerData[+A] { 
    val value: A 
} 

case class InnerString(value: String) extends InnerData[String] 
case class InnerMap[A, +B](value: Map[A, B]) extends InnerData[Map[A, B]] 
case class InnerBoolean(value: Boolean) extends InnerData[Boolean] 

現在,假設你正在閱讀的JSON data場到一個名爲jsData Scala的領域,你將給這一領域的以下類型:

val jsData: Map[String, Either[Int, InnerData[_]] 

你拉場出來的jsData每一次,你需要模式匹配,檢查值是否爲Left[Int]型或Right[InnerData[_]](兩個子類型的的)。一旦獲得了內部數據,您就可以在上進行模式匹配,即以確定其是否代表InnerString,InnerMapInnerBoolean

從技術上講,無論如何你必須做這種模式匹配才能使用數據,一旦你把它從JSON中拉出來。良好類型的方法的好處是編譯器會檢查你,以確保你沒有錯過任何可能性。缺點是你不能跳過不可能性(如'eggs'映射到Int)。另外,所有這些包裝對象都有一些額外的開銷,所以要小心。

注意,Scala並允許你定義一個類型別名應該削減的LOC爲此所需的量:

type DataType[A] = Map[String, Either[Int, InnerData[A]]] 

val jsData: DataType[_] 

添加一些隱式轉換爲使API漂亮,你應該所有漂亮和花花公子。

+0

你能詳細介紹一下這個[Int,InnerData [A]]類型嗎?我不明白爲什麼它是Int,首先,因爲我想要的基元是來自集合(Int,String,Boolean,null)。謝謝! – 2009-04-09 20:06:30

+0

要麼在Java中:-) http://www.ibm.com/developerworks/java/library/j-ft13/index.html – ZiglioUK 2012-08-05 10:29:08

1

JSON在「編程斯卡拉」中的組合分析章節中用作示例。

+0

我沒有看到該部分,但生成的數據類型是一種來自於解析一個JSON字符串,我不一定會有一個字面的JSON字符串來解析;調用`render()`的代碼可能有任意的數據,它是從其他來源彙編而來的。 – 2009-04-09 19:39:20

相關問題