文件

§JSON 自動對應

如果 JSON 直接對應到一個類別,我們提供一個方便的巨集,讓您不必手動撰寫 Reads[T]Writes[T]Format[T]。假設有以下案例類別

case class Resident(name: String, age: Int, role: Option[String])

以下巨集會根據結構和欄位名稱建立一個 Reads[Resident]

import play.api.libs.json._

implicit val residentReads: Reads[Resident] = Json.reads[Resident]

編譯時,巨集會檢查指定的類別並
注入以下程式碼,就像您手動撰寫的一樣

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

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

這是在編譯時進行的,因此您不會損失任何類型安全性或效能。
對於 Writes[T]Format[T] 存在類似的巨集

import play.api.libs.json._

implicit val residentWrites: OWrites[Resident] = Json.writes[Resident]
import play.api.libs.json._

implicit val residentFormat: Format[Resident] = Json.format[Resident]

因此,執行案例類別自動轉換為 JSON 的完整範例如下

import play.api.libs.json._

implicit val residentWrites: OWrites[Resident] = Json.writes[Resident]

val resident = Resident(name = "Fiver", age = 4, role = None)

val residentJson: JsValue = Json.toJson(resident)

而將 JSON 自動解析為案例類別的完整範例如下

import play.api.libs.json._

implicit val residentReads: Reads[Resident] = Json.reads[Resident]

// In a request, a JsValue is likely to come from `request.body.asJson`
// or just `request.body` if using the `Action(parse.json)` body parser
val jsonString: JsValue = Json.parse(
  """{
  "name" : "Fiver",
  "age" : 4
}"""
)

val residentFromJson: JsResult[Resident] =
  Json.fromJson[Resident](jsonString)

residentFromJson match {
  case JsSuccess(r: Resident, path: JsPath) =>
    println("Name: " + r.name)

  case e @ JsError(_) =>
    println("Errors: " + JsError.toJson(e).toString())
}

值類別 也受支援。給定以下基於 String 值的值類別

final class IdText(val value: String) extends AnyVal

然後也可以使用以下巨集產生 Reads[IdText](因為 String 已受支援)

import play.api.libs.json._

implicit val idTextReads: Reads[IdText] = Json.valueReads[IdText]

對於案例類別,對於 Writes[T]Format[T] 存在類似的巨集

import play.api.libs.json._

implicit val idTextWrites: Writes[IdText] = Json.valueWrites[IdText]
import play.api.libs.json._

implicit val idTextFormat: Format[IdText] = Json.valueFormat[IdText]

注意:要能夠從 request.body.asJson 存取 JSON,請求必須具有 Content-Type 標頭為 application/json。您可以使用 `tolerantJson` 主體解析器 來放寬此限制。

透過使用具有類型化驗證功能的主體解析器,可以讓上述範例更簡潔。請參閱 HTTP 中的 JSON 文件中的 savePlaceConcise 範例

§需求

巨集適用於符合以下需求的類別和特質。

Scala 2.x 中的類別

Scala 3.1.x 中的類別:(+3.1.2-RC2)

案例類別自動符合這些需求。對於自訂類別或特徵,您可能必須實作它們。

特徵也可以支援,但前提是它是一個密封的特徵,而且子類型符合先前的需求

sealed trait Role
case object Admin                            extends Role
case class Contributor(organization: String) extends Role

密封家族實例的 JSON 表示法包含一個辨識欄位,用來指定有效的子類型(一個文字欄位,預設名稱為 _type)。

val adminJson = Json.parse("""
  { "_type": "scalaguide.json.ScalaJsonAutomatedSpec.Admin" }
""")

val contributorJson = Json.parse("""
  {
    "_type":"scalaguide.json.ScalaJsonAutomatedSpec.Contributor",
    "organization":"Foo"
  }
""")

// Each JSON objects is marked with the _type,
// indicating the fully-qualified name of sub-type

然後巨集就能產生 Reads[T]OWrites[T]OFormat[T]

import play.api.libs.json._

// First provide instance for each sub-types 'Admin' and 'Contributor':
implicit val adminFormat = OFormat[Admin.type](Reads[Admin.type] {
  case JsObject(_) => JsSuccess(Admin)
  case _           => JsError("Empty object expected")
}, OWrites[Admin.type] { _ =>
  Json.obj()
})

implicit val contributorFormat: OFormat[Contributor] = Json.format[Contributor]

// Finally able to generate format for the sealed family 'Role'
implicit val roleFormat: OFormat[Role] = Json.format[Role]

§自訂命名策略

若要使用自訂命名策略,您需要定義一個隱含的 JsonConfiguration 物件和一個 JsonNaming

提供兩種命名策略:預設策略,使用類別屬性的名稱原樣,
以及 JsonNaming.SnakeCase 案例。

可以使用下列方式使用非預設策略

import play.api.libs.json._

implicit val config: JsonConfiguration = JsonConfiguration(SnakeCase)

implicit val userReads: Reads[PlayUser] = Json.reads[PlayUser]
import play.api.libs.json._

implicit val config: JsonConfiguration = JsonConfiguration(SnakeCase)

implicit val userWrites: OWrites[PlayUser] = Json.writes[PlayUser]
import play.api.libs.json._

implicit val config: JsonConfiguration = JsonConfiguration(SnakeCase)

implicit val userFormat: OFormat[PlayUser] = Json.format[PlayUser]

也可以設定特徵表示法,使用自訂名稱作為辨識欄位,或將子類型的名稱編碼為此欄位的值

val adminJson = Json.parse("""
  { "admTpe": "admin" }
""")

val contributorJson = Json.parse("""
  {
    "admTpe":"contributor",
    "organization":"Foo"
  }
""")

為此,可以在已解析的 JsonConfiguration 中定義 discriminatortypeNaming 設定

import play.api.libs.json._

implicit val cfg: JsonConfiguration = JsonConfiguration(
  // Each JSON objects is marked with the admTpe, ...
  discriminator = "admTpe",
  // ... indicating the lower-cased name of sub-type
  typeNaming = JsonNaming { fullName =>
    fullName.drop(39 /* remove pkg */ ).toLowerCase
  }
)

// First provide instance for each sub-types 'Admin' and 'Contributor':
implicit val adminFormat = OFormat[Admin.type](Reads[Admin.type] {
  case JsObject(_) => JsSuccess(Admin)
  case _           => JsError("Empty object expected")
}, OWrites[Admin.type] { _ =>
  Json.obj()
})

implicit val contributorFormat: OFormat[Contributor] = Json.format[Contributor]

// Finally able to generate format for the sealed family 'Role'
implicit val roleFormat: OFormat[Role] = Json.format[Role]

§實作您自己的命名策略

若要實作您自己的命名策略,您只需要實作 JsonNaming 特徵

import play.api.libs.json._

object OpenCollective extends JsonNaming {
  override def apply(property: String): String = s"opencollective_$property"
}

implicit val config: JsonConfiguration = JsonConfiguration(OpenCollective)

implicit val customWrites: OFormat[PlayUser] = Json.format[PlayUser]

§自訂巨集以輸出 null

可以設定巨集在 Json 中輸出 null 值,而不是移除空白欄位

import play.api.libs.json._

implicit val config: JsonConfiguration         = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
implicit val residentWrites: OWrites[Resident] = Json.writes[Resident]

下一步:JSON 轉換器


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