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
有兩個子類JsSuccess
和JsError
,分別用來處理成功和失敗兩種狀況:
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,能夠大大提高運行時處理速度,爲開發高性能的響應式系統提供了底層的技術保障。