§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 中的類別
- 它必須有一個具有
apply
和unapply
方法的伴隨物件。 unapply
的回傳類型必須與apply
方法的引數類型相符。apply
方法的參數名稱必須與 JSON 中所需的屬性名稱相同。
Scala 3.1.x 中的類別:(+3.1.2-RC2)
- 它必須提供一個
Conversion
轉換為_ <: Product
。 - 它必須提供一個有效的
ProductOf
。
案例類別自動符合這些需求。對於自訂類別或特徵,您可能必須實作它們。
特徵也可以支援,但前提是它是一個密封的特徵,而且子類型符合先前的需求
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
中定義 discriminator
和 typeNaming
設定
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 轉換器
在此文件找到錯誤嗎?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。