§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 にアクセスするには、リクエストにapplication/json
のContent-Type
ヘッダーが必要です。`tolerantJson`ボディパーサーを使用することで、この制約を緩和できます。
上記の例は、型付き検証関数を使用してボディパーサーを使用することで、さらに簡潔にすることができます。HTTP との JSON ドキュメントのsavePlaceConcise の例を参照してください。
§要件
マクロは、次の要件を満たすクラスとトレイトで機能します。
Scala 2.x のクラス
apply
メソッドとunapply
メソッドを持つコンパニオンオブジェクトが必要です。unapply
の戻り値の型は、apply
メソッドの引数の型と一致する必要があります。apply
メソッドのパラメーター名は、JSON で必要なプロパティ名と同じである必要があります。
Scala 3.1.x のクラス:(+3.1.2-RC2)
_ <: Product
へのConversion
を提供する必要があります。- 有効な
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
を定義する必要があります。
2 つのネーミング戦略が提供されています。1 つはデフォルトで、クラスプロパティの名前をそのまま使用します。
もう1つは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 変換器
このドキュメントに誤りを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインを読んだ後、プルリクエストを送信してください。ご質問やアドバイスがありましたら、コミュニティフォーラムでコミュニティと議論を始めましょう。