JSON basics

現代的Web應用經常需要解析并生成JSON格式的數據 (JavaScript 對象符號)。Play通過它的JSON library支持這個功能。

JOSN是一個輕量級的數據交換格式,像下面這樣:

{
"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.org.

Play的JSON庫

play.api.libs.json 包包含了為展示JSON數據的數據結構及這些數據結構和其他數據展現實現之間的實用工具侯嘀。這個包的一些功能是:

  • 使用最小樣板自動轉換為Case類和從Case類轉換為JSON庆冕。如果你想用最小的代碼快速的起步和運行殷蛇,這可能是個開始的地方
  • 解析時自定義驗證翁授。
  • 在請求Body中對JSON的自動解析,如果內容不可被解析或提供了不正確的Content-type會自動生成錯誤晾咪。
  • 可以作為獨立的可在Play應用以外的地方使用。只需要在你的 build.sbt文件中加上libraryDependencies += "com.typesafe.play" %% "play-json" % playVersion贮配。
  • 高度可自定義谍倦。

這個包提供了下列類型:

JsValue

這是表示任何JSON值的特質。JSON庫擴展了JsValue的Case類來展示每一個有效的JSON類型:

使用多樣的JsValue類型泪勒,你可以構建任何JSON結構的展現形式昼蛀。

Json

Json對象主要為JsValue結構和對象轉換提供了實用工具.

JsPath

表示JsValue結構內的路徑,類似于XML的XPath圆存。這是用來遍歷 JsValue 結構和模式的隱式轉換器叼旋。

轉換為JsValue

使用字符串解析

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"
} ]
}
""")

使用類構造

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(Seq(
JsObject(Seq(
"name" -> JsString("Fiver"),
"age" -> JsNumber(4),
"role" -> JsNull
)),
JsObject(Seq(
"name" -> JsString("Bigwig"),
"age" -> JsNumber(6),
"role" -> JsString("Owsla")
))
))
))

Json.objJson.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轉換器

ScalaJsValue轉換是通過Json.toJson[T](T)(implicit writes: Writes[T])實用方法執(zhí)行的。這個功能取決于可以吧T轉換為JsValueWrites[T]類型的轉換器油讯。

Play的JSON API為大多數基本類型提供隱式的Writes详民,如Int, Double, String, 和 Boolean.它也支持有Writes[T]的任何類型T的集合。

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"))
To convert your own models to JsValues, you must define implicit Writesconverters and provide them in scope.
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)

另外陌兑,你可以使用組合模式定義你的Writes

注意:組合模式在 JSON Reads/Writes/Formats Combinators詳細介紹沈跨。

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結構

你可以遍歷JsValue結構并提取特定的值。語法和功能類似于Scala XML的處理兔综。

注意:下面的例子使用了前面例子中創(chuàng)建的JsValue結構饿凛。

簡單路徑\

對JsValue使用\ 操作將返回與字段參數相對應的屬性,假如這是一個JsObject软驰。

val lat = (json \ "location" \ "lat").get
// returns JsNumber(51.235685)

遍歷路徑 \

使用 \操作將會在當前對象和所有子中的尋找字段涧窒。

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

索引查找(適用于JsArrays)

你可以在JsArray中使用帶索引的操作取值。

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

從JsValue轉換

使用字符串工具

Minified:

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"}]}

Readable:

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為基本類型提供了Reads

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方法(使用Reads類型的參數)。這個方法及進行驗證也進行轉換恭陡,返回JsResult類型的值蹬音。JsResult被兩個類實現了:

  • JsSuccess: 表示成功驗證/轉換并封裝結果。
  • 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 轉換為模型著淆,你必須定義隱式的Reads[T],在這里T是你的模型的類型拴疤。

注意:曾經實現了Reads并自定義了驗證的模式在JSON Reads/Writes/Formats Combinators中有詳細介紹永部。

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)),)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市呐矾,隨后出現的幾起案子苔埋,更是在濱河造成了極大的恐慌,老刑警劉巖蜒犯,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件组橄,死亡現場離奇詭異,居然都是意外死亡罚随,警方通過查閱死者的電腦和手機玉工,發(fā)現死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淘菩,“玉大人遵班,你說我怎么就攤上這事∶楣矗” “怎么了费奸?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長进陡。 經常有香客問我愿阐,道長,這世上最難降的妖魔是什么趾疚? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任缨历,我火速辦了婚禮,結果婚禮上糙麦,老公的妹妹穿的比我還像新娘辛孵。我一直安慰自己,他們只是感情好赡磅,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布魄缚。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冶匹。 梳的紋絲不亂的頭發(fā)上习劫,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音嚼隘,去河邊找鬼诽里。 笑死,一個胖子當著我的面吹牛飞蛹,可吹牛的內容都是我干的谤狡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼卧檐,長吁一口氣:“原來是場噩夢啊……” “哼墓懂!你這毒婦竟也來了?” 一聲冷哼從身側響起霉囚,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤拒贱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后佛嬉,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡闸天,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年暖呕,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苞氮。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡湾揽,死狀恐怖,靈堂內的尸體忽然破棺而出笼吟,到底是詐尸還是另有隱情库物,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布贷帮,位于F島的核電站戚揭,受9級特大地震影響,放射性物質發(fā)生泄漏撵枢。R本人自食惡果不足惜民晒,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锄禽。 院中可真熱鬧潜必,春花似錦、人聲如沸沃但。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宵晚。三九已至垂攘,卻和暖如春维雇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搜贤。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工谆沃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仪芒。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓唁影,卻偏偏與公主長得像,于是被迫代替她去往敵國和親掂名。 傳聞我的和親對象是個殘疾皇子据沈,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內容