文件

§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 資料的資料結構,以及用於在這些資料結構和其他資料表示法之間進行轉換的公用程式。此套件的一些功能包括

此套件提供下列類型

§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.objJson.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,例如 IntDoubleStringBoolean。它還支援對任何類型 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,它可能是 JsDefinedJsUndefined。您可以串連多個 \ 運算子,如果找不到任何中間值,結果將會是 JsUndefined。在 JsLookupResult 上呼叫 get 會嘗試在定義時取得值,如果未定義則會擲回例外狀況。

您也可以使用直接查詢 apply 方法(如下)來取得物件中的欄位或陣列中的索引。與 get 相同,如果值不存在,此方法將會擲回例外狀況。

§遞迴路徑 \\

套用 \\ 運算子會在目前物件和所有後代中查詢欄位。

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

§直接查詢

你可以使用 .apply 算子來擷取 JsArrayJsObject 中的值,它與簡單路徑 \ 算子相同,但它直接傳回值(而不是將它包裝在 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 轉換為 TWrites[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


在此文件中發現錯誤?此頁面的原始碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享?請前往 我們的社群論壇 與社群展開對話。