Play For Scala 開發指南 - 第9章 Json 開發

Play Json 簡介html

Play 內置了一套JSON庫,以幫助開發者簡化JSON操做。目前Play的JSON庫包含如下功能:ajax

  • Json對象與字符串之間互轉數據庫

  • Json對象和Case Class之間互轉json

  • Json數據校驗api

  • Json格式之間互轉數組

Play的JSON庫並不依賴於Play環境,能夠單獨使用,經過以下方式能夠將它引入到本身的項目:安全

libraryDependencies += "com.typesafe.play" %% "play-json" % playVersion

基本JSON類型服務器

全部的基本JSON類型都繼承自JsValue trait。Play JSON 庫提供的基本類型以下:app

  • JsString微服務

  • JsNumber

  • JsBoolean

  • JsObject

  • JsArray

  • JsNull

在日程開發中,咱們不多跟這些JSON基本類型打交道。由於在Play中對於基本類型T(例如 String, Int, ...)以及Seq[T]已經提供了默認的隱式轉換, 能夠自動將其轉換成對應的JSON類型,例如:

//基本類型值
Json.obj("name" -> JsString("joymufeng"))
//能夠簡寫成:
Json.obj("name" -> "joymufeng")

//序列類型值
Json.obj("emails" -> JsArray(Seq(JsString("a"), JsString("b"))))
//能夠簡寫成:
Json.obj("emails" -> Seq("a", "b"))

在Play的JSON庫裏,整形和浮點型都使用JsNumber表示,這是一個略爲糟糕的設計,由於會致使JSON數據沒法在多語言環境下共享。例如經過Java代碼向MongoDB寫入了一個整形數值,可是通過Play的JSON庫修改後變成了浮點型,Java代碼再次讀取時便會報錯。

基本的JSON操做

構建一個JsObject對象

//直接構建
val json = 
  Json.obj(
    "name" -> "joymufeng", 
    "emails" -> Json.arr("joymufeng@163.com"), 
    "address" -> Json.obj(
      "province" -> "JiangSu", 
      "city" -> "NanJing"
    )
  )
  
//從JSON字符串構建
val json = 
  Json.parse("""{
    "name": "joymufeng", 
    "emails": ["joymufeng@163.com"], 
    "address": {
      "province": "JiangSu", 
      "city" -> "NanJing"
     }
  }""")

讀寫操做

//讀取指定路徑值
val city = (json \ "address" \ "city").as[String]

//若是指定路徑不存在則返回None
val cityOpt = (json \ "address" \ "city").asOpt[String]

//讀取數組內容
val emails = (json \ "emails").as[List[String]]

//讀取數組的第1個元素
val email = (json \ "emails")(0)

//更新指定路徑值
var obj = Json.obj("a" -> 1)
obj ++= Json.obj("b" -> 2)  //obj: {"a":1,"b":2}

格式化輸出

//格式化輸出
val prettyStr = Json.prettyPrint(obj)

JsObject 與 Case Class 互轉

Json Format 宏

Play雖然爲基本類型T以及Seq[T]提供了默認的隱式轉換,可是對於用戶自定義的 Case Class,因爲沒法事先知曉,須要須要用戶本身聲明隱式轉換對象。Play 爲開發者提供了 Format 宏,只須要一行代碼即可以完成聲明操做。假設咱們有以下兩個 Case Class:

case class Address(province: String, city: String)
case class Person(name: String, emails: List[String], address: Address)

咱們只須要聲明以下兩個隱式的Format對象就能夠了,在運行時,隱式的 Format 對象會自動完成編解碼操做:

import play.api.libs.json.Json
    
implicit val addressFormat = Json.format[Address]    
implicit val personFormat = Json.format[Person]

Format 宏的展開是在編譯期執行的,一方面提高了類型的安全性,另外一方,區別於 Java 的反射機制,Format 宏是在編譯器生成編解碼器,在運行期有更高的執行效率。關於 Scala 宏的更多內容請參考官方文檔

常見互轉操做

將上面兩個隱式 Format 對象導入到當前做用域,咱們即可以自由地在 JsObject 和 Case Class 之間進行互轉:

val person = Person("joymufeng", List("joymufeng@163.com"), Address("JiangSu", "NanJing"))

//將 Case Class 轉換成 Json
val json = Json.toJson[Person](person)  

//將 Json 轉換成 Case Class
val p1 = Json.fromJson[Person](json).get

//或者
val p2 = json.as[Person]
val p3 = json.asOpt[Person].get

咱們發現Json.fromJson[Person](json)返回的類型並非Person而是JsResult[Person],這是由於從 Json 到Case Class的轉換可能會發生錯誤,JsResult有兩個子類JsSuccessJsError,分別用來處理成功和失敗兩種狀況:

Json.fromJson[Person](json) match {     
  //轉換成功 
  case p: JsSuccess[Person] => println(p)   
  
  //轉換失敗   
  case e: JsError => println(e.errors)
}

開發技巧

上面的代碼在轉換時須要將隱式的 Format 對象顯式地導入到當前的做用域,使用起來有些不便。咱們能夠把隱式 Format 對象定義在伴生對象中,這樣的話就能夠在任意位置執行轉換而無需導入隱式對象:

import play.api.libs.json.Json

case class Address(province: String, city: String)
case object Address {
  implicit val addressFormat = Json.format[Address] 
}

case class Person(name: String, emails: List[String], address: Address)
case object Person {
  implicit val personFormat = Json.format[Person]
}

編譯器會自動到目標類型和源類型的伴生對象中獲取隱式的 Format 對象,無需手動導入。

上面的方法須要針對每一個 Case Class 建立一個伴生對象,編寫起來比較繁瑣。咱們也能夠在包對象(package object)中建立隱式的 Format 對象,假設 Address 和 Person 都定義在 models 包下,則咱們能夠爲 models 包建立一個包對象,並在其中建立隱式的 Format 對象:

package object models {
  import play.api.libs.json.Json
    
  implicit val addressFormat = Json.format[Address]    
  implicit val personFormat = Json.format[Person]
}

因爲編譯器會自動從源類型或目標類型的父對象中查找隱式轉換,因此定義在包對象中的隱式 Format 對象會被自動加載,而無需顯示導入。

更多的隱式轉換來源請參考官方的總結的隱式轉換規則

Json 請求與 Json 響應

Json是目前使用最爲普遍的數據交換格式,利用 Play 的 Json 庫,咱們能夠開發很是健壯的 RESTful 應用。

構建 Json 請求

藉助jQuery能夠很容易構建一個請求體爲 Json 的 Post 請求:

$.ajax({
  type: 'post',
  dataType: 'json',
  contentType: 'application/json; charset=utf-8',
  data: {...},
  url: '/post',
  success: function(res){
    //請求成功處理
  }, error: function(e){
    //請求失敗處理
  }
});

須要注意,客戶端在執行 Post 請求時必須明確指定Content-Type請求頭,不然服務器端沒法正確識別。

處理 Json 請求

在服務器端,咱們能夠經過以下方式接收 Json 請求:

def doReciveJson = Action { implicit request =>
    request.body.asJson match {
      case Some(obj) =>
        obj.validate[Person] match {
          case JsSuccess(person, _) =>
            Ok(Json.obj("status" -> 0))
          case JsError(_) =>
            Ok(Json.obj("status" -> 1, "msg" -> "Json數據校驗失敗!"))
        }
      case None => Ok(Json.obj("status" -> 1, "msg" -> "請求格式有誤!"))
    }
  }

再次提醒,客戶端 Post 請求必須攜帶Content-Type請求頭,不然服務器端在執行request.body.asJson代碼時將沒法正確解析出 Json 數據。經過request.body.as*方法,咱們能夠將請求體轉換成不一樣的數據格式,前提是請求的Content-Type內容必須與目標數據格式一致。下面列舉常見的as*方法所要求的Content-Type類型,供你們開發時參閱:

  • asJson:text/json 或 application/json

  • asText:text/plain

  • asFormUrlEncoded:application/x-www-form-urlencoded

  • asXml:application/xml

  • asMultipartFormData:multipart/form-data

  • asRaw:其它類型

在服務器端,咱們能夠構建一個 Json 對象,而且直接做爲響應寫回客戶端,Play 會自動添加合適的響應頭:

Ok(Json.obj("status" -> 0))

在生成 Json 響應時,咱們並無明確指定字符編碼格式,這是因爲按照 RFC 7159 規範,Play 使用默認的 UTF-8 對 Json 內容進行編碼,客戶端能夠經過檢測 Json 內容的前4個字節自動檢測出 UTF-8 字符編碼,繼而能夠正確解碼 Json 內容。

RFC 7159規定在爲 Json 指定 Content-Type 時無需指定編碼格式,而且指定編碼格式是非法操做。客戶端能夠根據 Json 內容的前4個字節自動檢測出正確的編碼格式。

小結

隨着NoSQL數據庫和微服務的不斷普及,JSON數據在Web開發中顯得愈來愈重要。藉助 MongoDB 等 BSON數據庫,咱們能夠實現全棧式 Json 開發,大大簡化了數據的處理流程。例如對於複雜的業務數據,如繪圖工具導出的 Json 數據,咱們能夠直接入庫,省去中間的 Case Class 相互轉換過程。在 Json 處理領域,Play 和 Scala 有着自然的優點,一方面經過 Scala 的優雅語法以及 Play 的 Json DSL,咱們能夠輕鬆地構建和處理 Json;另外一方面,相比於 Java 的反射機制,利用 Scala 語言提供的編譯器期 Macro,能夠大大提高運行時處理速度,爲開發高性能的響應式系統提供了底層的技術保障。

相關文章
相關標籤/搜索