ドキュメント

§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/jsonContent-Typeヘッダーが必要です。`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を定義する必要があります。

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"
  }
""")

そのためには、解決されたJsonConfigurationdiscriminator設定と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 変換器


このドキュメントに誤りを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインを読んだ後、プルリクエストを送信してください。ご質問やアドバイスがありましたら、コミュニティフォーラムでコミュニティと議論を始めましょう。