§JSON 基礎
現代網路應用程式通常需要以 JSON(JavaScript 物件表示法)格式來剖析和產生資料。Play 透過其 JSON 函式庫 來支援這項功能。
JSON 是一種輕量級的資料交換格式,其外觀如下
{
"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 資料的資料結構,以及用於在這些資料結構和其他資料表示法之間進行轉換的公用程式。此套件的一些功能包括
- 自動轉換為大小寫寫法,並從大小寫寫法轉換為案例類別,而樣板程式碼最少。如果您想快速執行並將程式碼減到最少,這可能是開始的地方。
- 自訂驗證同時剖析。
- 自動剖析請求主體中的 JSON,如果內容無法剖析或提供不正確的 Content-type 標頭,則會自動產生錯誤。
- 可以在 Play 應用程式之外作為獨立函式庫使用。只要將
libraryDependencies += "org.playframework" %% "play-json" % playVersion
新增到您的build.sbt
檔案即可。 - 高度自訂化。
此套件提供下列類型
§JsValue
這是一個表示任何 JSON 值的特質。JSON 函式庫有一個擴充 JsValue
的案例類別,用來表示每個有效的 JSON 類型
使用各種 JsValue
類型,您可以建立任何 JSON 結構的表示。
§Json
Json
物件提供一些工具程式,主要是用於轉換到 JsValue
結構和從 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(
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,JsObject}
val json: JsObject = 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"
)
)
)
您也可以使用 Json.newBuilder
來建立 JsObject
import play.api.libs.json.{ JsNull, Json, JsString, JsObject }
def asJson(active: Boolean): JsObject = {
val builder = Json.newBuilder
builder ++= Seq(
"name" -> "Watership Down",
"location" -> Json.obj(
"lat" -> 51.235685D, "long" -> -1.309197D))
if (active) {
builder += "active" -> true
}
builder += "residents" -> Seq(
Json.obj(
"name" -> "Fiver",
"age" -> 4,
"role" -> JsNull
),
Json.obj(
"name" -> "Bigwig",
"age" -> 6,
"role" -> "Owsla"
))
builder.result()
}
§使用 Writes 轉換器
Scala 到 JsValue
轉換是由工具程式方法 Json.toJson[T](T)(implicit writes: Writes[T])
執行的。此功能仰賴 Writes[T]
類型的轉換器,它可以將 T
轉換為 JsValue
。
Play JSON API 提供了大多數基本類型的隱式 Writes
,例如 Int
、Double
、String
和 Boolean
。它還支援對任何類型 T
的集合的 Writes
,其中存在 Writes[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"))
若要將您自己的模型轉換為 JsValue
,您必須定義隱式 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: Writes[Location] = new Writes[Location] {
def writes(location: Location) = Json.obj(
"lat" -> location.lat,
"long" -> location.long
)
}
implicit val residentWrites: Writes[Resident] = new Writes[Resident] {
def writes(resident: Resident) = Json.obj(
"name" -> resident.name,
"age" -> resident.age,
"role" -> resident.role
)
}
implicit val placeWrites: Writes[Place] = 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]
)(l => (l.lat, l.long))
implicit val residentWrites: Writes[Resident] = (
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "role").writeNullable[String]
)(r => (r.name, r.age, r.role))
implicit val placeWrites: Writes[Place] = (
(JsPath \ "name").write[String] and
(JsPath \ "location").write[Location] and
(JsPath \ "residents").write[Seq[Resident]]
)(p => (p.name, p.location, p.residents))
§遍歷 JsValue 結構
您可以遍歷 JsValue
結構並提取特定值。語法和功能類似於 Scala XML 處理。
注意:以下範例套用於先前範例中建立的 JsValue 結構。
§簡單路徑 \
將 \
運算子套用至 JsValue
會傳回對應於 JsObject
中欄位引數的屬性,或 JsArray
中該索引處的項目
val lat = (json \ "location" \ "lat").toOption
// returns some JsNumber(51.235685)
val bigwig = (json \ "residents" \ 1).toOption
// returns some {"name":"Bigwig","age":6,"role":"Owsla"}
\
運算子傳回 JsLookupResult
,它可能是 JsDefined
或 JsUndefined
。您可以串連多個 \
運算子,如果找不到任何中間值,結果將會是 JsUndefined
。在 JsLookupResult
上呼叫 get
會嘗試在定義時取得值,如果未定義則會擲回例外狀況。
您也可以使用直接查詢 apply
方法(如下)來取得物件中的欄位或陣列中的索引。與 get
相同,如果值不存在,此方法將會擲回例外狀況。
§遞迴路徑 \\
套用 \\
運算子會在目前物件和所有後代中查詢欄位。
val names = json \\ "name"
// returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))
§直接查詢
你可以使用 .apply
算子來擷取 JsArray
或 JsObject
中的值,它與簡單路徑 \
算子相同,但它直接傳回值(而不是將它包裝在 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 為基本型別提供 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
由兩個類別實作
您可以套用各種模式來處理驗證結果
val json = { ... }
val nameResult: JsResult[String] = (json \ "name").validate[String]
// Pattern matching
nameResult match {
case JsSuccess(name, _) => println(s"Name: $name")
case e: JsError => println(s"Errors: ${JsError.toJson(e)}")
}
// 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(s"field: ${x._1}, errors: ${x._2}")
}
Option.empty[String]
},
valid = Some(_)
)
§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)),)
§使用簡單的元組
簡單的 JSON 物件可以讀取為簡單元組,並從簡單元組寫入。
import play.api.libs.json._
val tuple3Reads: Reads[(String, Int, Boolean)] =
Reads.tuple3[String, Int, Boolean]("name", "age", "isStudent")
val tuple3Writes: OWrites[(String, Int, Boolean)] =
OWrites.tuple3[String, Int, Boolean]("name", "age", "isStudent")
val tuple3ExampleJson: JsObject =
Json.obj("name" -> "Bob", "age" -> 30, "isStudent" -> false)
val tuple3Example = Tuple3("Bob", 30, false)
tuple3Writes.writes(tuple3Example) mustEqual tuple3ExampleJson
tuple3Reads.reads(tuple3ExampleJson) mustEqual JsSuccess(tuple3Example)
下一步:JSON 與 HTTP
在此文件中發現錯誤?此頁面的原始碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享?請前往 我們的社群論壇 與社群展開對話。