標籤:

Spray中對複雜JSON的序列化與反序列化

在Spray中,倘若我們希望REST服務支持JSON格式的request與response,通常使用Spray提供的Json4sSupport,只需要Spray的Route繼承它即可。它基本上可以應付常規的Scala類(多數情況是case class)與Json格式之間的序列化與反序列化。

倘若需要支持Scala的枚舉類型,或者Joda框架提供的Time類型,可以利用Json4s的擴展,只需要在項目依賴文件sbt中添加該依賴:

val json4sExt = "org.json4s" %% "json4s-ext" % json4sVersionn

那麼就可以定義一個類,用於對Json的支持:

object MyJsonSupport extends Json4sSupport {nn implicit val formats = Serialization.formats(ShortTypeHints(List()))nn implicit lazy val json4sFormats: Formats =n org.json4s.ext.JodaTimeSerializers.all +n new EnumNameSerializer(FieldType)n}n

上述代碼的FieldType就是一個自定義的枚舉類型。

在項目中,我碰到了一個複雜的Json結構,它是一個遞歸結構,且內部嵌套的對象還存在類型定義的多態。若從函數角度來講,那種類型結構就是Algebraic Data Type的product類型。

大致的Json結構如下所示:

[n {n "fieldId": -1,n "fieldType": "metric",n "function": {n "functionName": "sum",n "functionType": "buildin",n "parameters": [n {n //此時參數為FieldParameter類型n "fieldId": 4 n }n ]n }n }n {n "fieldId": -1,n "fieldType": "metric",n "function": {n "functionName": "yearOnYearBasis",n "functionType": "udaf",n "parameters": [n {n "fieldId": 4n },n {n "fieldId": 5n },n {n //此時參數為ConstantParameter類型n "classType": "timestamp", n "value": "2015-01-01 00:00:00"n },n {n "classType": "timestamp",n "value": "2015-12-31 11:59:59"n }n ]n }n }n]n

這個Json數據代表一個Field的數組。Field下定義了一個Option的Function。Function可以接收多個參數(Parameter),而參數存在三種類型,分別為:FieldParameter,ConstantParameter,FunctionParameter。其中FunctionParameter尤其特殊,它實際上又是一個Function類型,形成了一種遞歸嵌套。

Json數據對應的Scala類如下:

case class Field(fieldId: ID, fieldType: String, function: Option[Function] = None)nn case class Function(functionName: String, functionType: String, parameters: List[Parameter])nn sealed trait Parameternn case class FieldParameter(fieldId: ID) extends Parameternn case class ConstantParameter(classType: String, value: String) extends Parameternn case class FunctionParameter(function: Function) extends Parametern

我們需要在給REST服務傳入Json數據時,可以根據Json中parameters下傳遞的名稱,來判斷實例化哪一種類型的Parameter。在Scala中,其實是一個典型的模式匹配。

在Json4s中,可以認為Json值其實是一個它封裝的JObject對象。一個JObject可以包含多個JField對象。此外,Json4s支持自定義序列化器。這就為類型多態提供了實現的可能。除了Parameter類型外,其餘類型符合標準的Scala類(即使包含了Option類型),Json4s內置的序列化器已經支持;故而只需要為Parameter定義序列化器即可。

由於要支持序列化與反序列化,因此在模式匹配中需要支持兩個方向的相互轉換與映射,即在JObject與case class之間定義。

對FunctionParameter的處理比較特殊,因為它的參數(即Json4s中的JField)又是另外一個對象。而Json4s中僅僅為基本類型提供了定義,例如JInt、JString等。在JField中的值都被定義為JValue,所以可以通過調用JValue的extract方法將JValue提取為Scala對象;通過調用Extraction.decompose方法將Scala對象轉換為JValue對象。代碼如下所示:

class ParameterSerializer extends CustomSerializer[Parameter](format => ( {n case JObject(JField("fieldId", JInt(fieldId)) :: Nil) => FieldParameter(fieldId.toInt)n case JObject(JField("classType", JString(classType)) :: JField("value", JString(value)) :: Nil) => ConstantParameter(classType, value)n case JObject(List(JField("function", function) :: Nil) => FunctionParameter(function.extract[Function])n}, {n case FieldParameter(fieldId) => JObject(JField("fieldId", JInt(fieldId)) :: Nil)n case ConstantParameter(classType, value) => JObject(JField("classType", JString(classType)) :: JField("value", JString(value)) :: Nil)n case FunctionParameter(function) => JObject(JField("function", Extraction.decompose(Function)) :: Nil)n}))n

最後,只需要將ParameterSerializer的實例追加到前面的json4sFormats中即可。

推薦閱讀:

scala 逆變有什麼用?
Dotty 開發環境搭建
如何看待 Scala.js?
如何評價 scala native?

TAG:Scala |