【六 處理JSON】 1. JSON基礎

JSON基礎

現代的web應用通常都面臨大量的JSON(JavaScript Object Notation)格式的數據操做。爲此Play提供了JSON庫。html

JSON是一種輕量級的數據交換格式,一個典型的JSON結構以下所示:web

{
  "name" : "Watership Down",
  "location" : {
    "lat" : 51.235685,
    "long" : -1.309197
  },
  "residents" : [ {
    "name" : "Fiver",
    "age" : 4,
    "role" : null
  }, {
    "name" : "Bigwig",
    "age" : 6,
    "role" : "Owsla"
  } ]
}

點擊這裏查看有關JSON的更多信息。 json

Play的JSON庫

play.api.libs.json 包中提供了構造JSON數據的數據結構及轉換數據的工具類。此package提供了以下功能:api

自動轉換:從case class和json相互轉換的模板代碼。能夠從這裏入手來快速開始。數組

自定義邏輯校驗安全

自動解析:從request body中解析JSON,遇到異常的JSON格式或者不正確的content-type頭時會自動產生錯誤。數據結構

此JSON庫也能夠脫離Play框架獨立使用。只須要在sbt文件中加入:libraryDependencies += "com.typesafe.play" %% "play-json" % playVersionapp

支持高度的自定義框架

這個包裏邊還內置了以下類型:ide

JsValue

此特質表明一個JSON值。下面的case class都繼承了JsValue,分別表明一種有效的JSON type:

JsString

JsNumber

JsBoolean

JsObject

JsArray

JsNull

你可使用上面這些JsValue類型構造任意的JSON結構。

Json

Json對象提供了一系列工具方法,主要用途是對JsValue的轉換。

JsPath

表明着JsValue中的一個路徑,相似XML中的XPath做用。主要用於遍歷Jsalue結構和隱式的轉換器。

JsValue轉換

使用字符串轉換Json對象

import play.api.libs.json._

val json: JsValue = Json.parse("""
  {
    "name" : "Watership Down",
    "location" : {
      "lat" : 51.235685,
      "long" : -1.309197
    },
    "residents" : [ {
      "name" : "Fiver",
      "age" : 4,
      "role" : null
    }, {
      "name" : "Bigwig",
      "age" : 6,
      "role" : "Owsla"
    } ]
  }
  """)

使用類轉換Json

import play.api.libs.json._

val json: JsValue = JsObject(Seq(
  "name" -> JsString("Watership Down"),
  "location" -> JsObject(Seq("lat" -> JsNumber(51.235685), "long" -> JsNumber(-1.309197))),
  "residents" -> JsArray(IndexedSeq(
    JsObject(Seq(
      "name" -> JsString("Fiver"),
      "age" -> JsNumber(4),
      "role" -> JsNull
    )),
    JsObject(Seq(
      "name" -> JsString("Bigwig"),
      "age" -> JsNumber(6),
      "role" -> JsString("Owsla")
    ))
  ))
))

Json.obj和Json.arr可使得構造過程更加容易。注意大多數值不須要顯示地使用JsValue來進行包裝,工廠方法會自動隱式轉換:

import play.api.libs.json.{ JsNull, Json, JsString, JsValue }

val json: JsValue = Json.obj(
  "name" -> "Watership Down",
  "location" -> Json.obj("lat" -> 51.235685, "long" -> -1.309197),
  "residents" -> Json.arr(
    Json.obj(
      "name" -> "Fiver",
      "age" -> 4,
      "role" -> JsNull
    ),
    Json.obj(
      "name" -> "Bigwig",
      "age" -> 6,
      "role" -> "Owsla"
    )
  )
)

writes轉換器

轉換爲JsValue的過程由Json.toJson[T](T)(implicit writes: Writes[T])執行。該功能依賴於隱式的Writes[T]轉換器。

Play的JSON API內置了經常使用類型的轉換器,如Int,Double,Sring以及Boolean。也支持對已經支持類型的集合作轉換。

import play.api.libs.json._

// basic types
val jsonString = Json.toJson("Fiver")
val jsonNumber = Json.toJson(4)
val jsonBoolean = Json.toJson(false)

// collections of basic types
val jsonArrayOfInts = Json.toJson(Seq(1, 2, 3, 4))
val jsonArrayOfStrings = Json.toJson(List("Fiver", "Bigwig"))

若是要支持自定義的轉換器,必須在做用域中隱式提供相應的Writes轉換器:

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])

 

import play.api.libs.json._

implicit val locationWrites = new Writes[Location] {
  def writes(location: Location) = Json.obj(
    "lat" -> location.lat,
    "long" -> location.long
  )
}

implicit val residentWrites = new Writes[Resident] {
  def writes(resident: Resident) = Json.obj(
    "name" -> resident.name,
    "age" -> resident.age,
    "role" -> resident.role
  )
}

implicit val placeWrites = new Writes[Place] {
  def writes(place: Place) = Json.obj(
    "name" -> place.name,
    "location" -> place.location,
    "residents" -> place.residents
  )
}

val place = Place(
  "Watership Down",
  Location(51.235685, -1.309197),
  Seq(
    Resident("Fiver", 4, None),
    Resident("Bigwig", 6, Some("Owsla"))
  )
)

val json = Json.toJson(place)

你也可使用 combinator 模式:

注意:combinator模式具體介紹請看這裏

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))

implicit val residentWrites: Writes[Resident] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "age").write[Int] and
  (JsPath \ "role").writeNullable[String]
)(unlift(Resident.unapply))

implicit val placeWrites: Writes[Place] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "location").write[Location] and
  (JsPath \ "residents").write[Seq[Resident]]
)(unlift(Place.unapply))

遍歷JsValue結構體

和操做XML相似,Scala也容許遍歷JsValue結構並處理你感興趣的部分。

注意:如下示例應用於前面示例中建立的JsValue結構。

簡單路徑 \

將 \ 運算符應用於JsValue將返回與 JsObject 對應的屬性,或者JsArray中索引對應的元素:

val lat = (json \ "location" \ "lat").get
// returns JsNumber(51.235685)
val bigwig = (json \ "residents" \ 1).get
// returns {"name":"Bigwig","age":6,"role":"Owsla"}

\ 操做符返回的是一個JsLookupResult,實際內容對應 JsDefined 或者 JsUndefined。能夠鏈式調用 \,只要有一個環節找不到元素就會返回 JsUndefined。在 JsLookupResult 上嘗試調用 get 方法將返回查詢到的值,若是在 JsUndefined 上調用將會拋出異常。

也能夠直接使用 apply 方法來獲取數組中對象或索引中的字段(詳見下文 直接查詢)。與get相似,若是值不存在此方法將拋出異常。

遞歸路徑 \\

使用 \\ 操做符,將在當前對象及其全部子節點中作查詢:

val names = json \\ "name"
// returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))

直接查詢

能夠在 JsArray 或者 JsObject 上直接使用 .apply 操做符,它的效果和使用 \ 操做符相似,可是會直接返回獲取到的值,而沒有用 JsLookupResult 來包裝。若是查詢不到,會直接拋出異常。

val name = json("name")
// returns JsString("Watership Down")

val bigwig2 = json("residents")(1)
// returns {"name":"Bigwig","age":6,"role":"Owsla"}

// (json("residents")(3)
// throws an IndexOutOfBoundsException

// json("bogus")
// throws a NoSuchElementException

在某些場景下,訪問一些已知存在的JSON值,例如在一次性腳本或REPL中,這將很是有用。

JsValue到字符串

字符串轉換

val minifiedString: String = Json.stringify(json)

// {"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197},"residents":[{"name":"Fiver","age":4,"role":null},{"name":"Bigwig","age":6,"role":"Owsla"}]}

下面是可讀性更好的寫法:

val readableString: String = Json.prettyPrint(json)
{
  "name" : "Watership Down",
  "location" : {
    "lat" : 51.235685,
    "long" : -1.309197
  },
  "residents" : [ {
    "name" : "Fiver",
    "age" : 4,
    "role" : null
  }, {
    "name" : "Bigwig",
    "age" : 6,
    "role" : "Owsla"
  } ]
}

使用 JsValue.as/ asOpt

將JsValue轉換爲另外一種類型的最簡單方法是使用JsValue.as[T](implicit fjs: Reads[T]): T。這須要一個類型爲Reads[T]的隱式轉換器將JsValue轉換爲T(與Writes[T]正好相反)。與Writes同樣,JSON API提供了對基本類型的內置支持。

val name = (json \ "name").as[String]
// "Watership Down"

val names = (json \\ "name").map(_.as[String])
// Seq("Watership Down", "Fiver", "Bigwig")

若是碰到找不到路徑或其它沒法轉換的狀況,as 方法將拋出 JsResultException。更安全的方法是 JsValue.asOpt[T](implicit fjs: Reads[T]): Option[T]。

val nameOption = (json \ "name").asOpt[String]
// Some("Watership Down")

val bogusOption = (json \ "bogus").asOpt[String]
// None

雖然 asOpt 方法更加安全,可是會丟失錯誤信息。

校驗

將 JsValue 轉換爲另外一種類型的最佳方式是使用 validate 方法(它接受read類型的參數)。validate 將同時執行校驗和轉換,最後返回 JsResult 類型。JsResult 由兩個類實現:

JsSuccess:轉換成功,幷包裝了正確的result。

JsError:轉換失敗,包裝了一系列的錯誤信息。

可使用各類模式來處理校驗的結果:

val json = { ... }

val nameResult: JsResult[String] = (json \ "name").validate[String]

// Pattern matching
nameResult match {
  case s: JsSuccess[String] => println("Name: " + s.get)
  case e: JsError => println("Errors: " + JsError.toJson(e).toString())
}

// Fallback value
val nameOrFallback = nameResult.getOrElse("Undefined")

// map
val nameUpperResult: JsResult[String] = nameResult.map(_.toUpperCase())

// fold
val nameOption: Option[String] = nameResult.fold(
  invalid = {
    fieldErrors =>
      fieldErrors.foreach(x => {
        println("field: " + x._1 + ", errors: " + x._2)
      })
      None
  },
  valid = {
    name => Some(name)
  }
)

JsValue到模型

若是須要將JsValue轉換爲model,必須在做用域中提供相應的 Reads[T] 方法,T對應着你的 model。

注意:自定義校驗及轉換的 Reads 實現能夠參考這裏。 

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])

 

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double] and
  (JsPath \ "long").read[Double]
)(Location.apply _)

implicit val residentReads: Reads[Resident] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "age").read[Int] and
  (JsPath \ "role").readNullable[String]
)(Resident.apply _)

implicit val placeReads: Reads[Place] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "location").read[Location] and
  (JsPath \ "residents").read[Seq[Resident]]
)(Place.apply _)

val json = { ... }

val placeResult: JsResult[Place] = json.validate[Place]
// JsSuccess(Place(...),)

val residentResult: JsResult[Resident] = (json \ "residents")(1).validate[Resident]
// JsSuccess(Resident(Bigwig,6,Some(Owsla)),)
相關文章
相關標籤/搜索