業務の都合上の話ですが、23要素以上のフラットな形式のJSONリクエストパラメータをうまくケースクラスにマッピングして当てはめる方法を少し調べました。
その時のメモです。
Scala/Play初心者のため、よい実装があったらぜひ教えてください。
検討ケースとしては以下です。
- (Pattern1)パラメータが22個以下: Playのデファクトスタンダードな実装
- (Pattern2)パラメータが23個以上: for-yieldを使ってケースクラスにマッピングする
- (Pattern3)パラメータが23個以上: play-json-extensionを使ってケースクラスにマッピングする
※前提事項: 1.の実装の場合,23要素以上に対応できません。
結果としてはパラメータ異常時のレスポンスが異なりました。
以降は重要だと思われる箇所を太字にして記載しています。
なお、リクエストでもらったJSONのパラメータをケースクラスにマッピングした後にレスポンスで返すような単純な実装で試してみました。
コントローラは以下のようになっています。
BadRequest(Json.obj("result" -> "failure", "error" -> JsError.toJson(error)))
でエラーの情報を返しています。
package pattern
import javax.inject.{Inject, Singleton}
import play.api.libs.json._
import play.api.mvc.{AbstractController, Action, ControllerComponents}
@Singleton
class TestParamsController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
// pattern1
// Jsonパラメータが22個の制約を受けない
def pattern1: Action[JsValue] = Action(parse.json) { implicit request =>
import Pattern1._
request.body.validate[Pattern1].map { form =>
Ok(Json.obj("result" -> Json.toJson(form)))
}.recoverTotal { error =>
BadRequest(Json.obj("result" -> "failure", "error" -> JsError.toJson(error)))
}
}
// Pattern2
// Jsonパラメータ22個の制約を突破する
// for-yieldで実装
def pattern2: Action[JsValue] = Action(parse.json) { implicit request =>
import Pattern2._
request.body.validate[Pattern2].map { form =>
Ok(Json.obj("result" -> Json.toJson(form)))
}.recoverTotal { error =>
BadRequest(Json.obj("result" -> "failure", "error" -> JsError.toJson(error)))
}
}
// Pattern3
// Jsonパラメータ22個の制約を突破する。
// play-json-extensionで実装
def pattern3: Action[JsValue] = Action(parse.json) { implicit request =>
import Pattern3._
request.body.validate[Pattern3].map { form =>
Ok(Json.obj("result" -> Json.toJson(form)))
}.recoverTotal { error =>
BadRequest(Json.obj("result" -> "failure", "error" -> JsError.toJson(error)))
}
}
}
ルーティングは以下のように定義しています。
POST /pattern1 pattern.TestParamsController.pattern1
POST /pattern2 pattern.TestParamsController.pattern2
POST /pattern3 pattern.TestParamsController.pattern3
build.sbt
にはplay-json-extensionを追加しています。
libraryDependencies += "ai.x" %% "play-json-extensions" % "0.10.0"
(Pattern1)パラメータが22個以下: Playのデファクトスタンダードな実装
普通のパターンです。
実装
package pattern
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Pattern1(p1: String, p2: String)
object Pattern1 {
implicit val jsonReads: Reads[Pattern1] = (
(JsPath \ "p1").read[String] and
(JsPath \ "p2").read[String]
) (Pattern1.apply _)
implicit val jsonWrites: Writes[Pattern1] = (p: Pattern1) =>
Json.obj(
"p1" -> p.p1,
"p2" -> p.p2
)
}
レスポンスの確認
正常系
リクエスト
POST http://localhost:9000/pattern1
Content-Type: application/json
{
"p1": "this is p1",
"p2": "this is p2"
}
レスポンス
{
"result": {
"p1": "this is p1",
"p2": "this is p2"
}
}
異常系
全部のパラメータp1
,p2
に不正な値を設定してリクエストします。
全部のパラメータをチェックしてくれていることがわかります。
リクエスト
POST http://localhost:9000/pattern1
Content-Type: application/json
{
"p1": 1,
"p2": 2
}
レスポンス
{
"result": "failure",
"error": {
"obj.p2": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p1": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
]
}
}
(Pattern2)パラメータが23個以上: for-yieldを使ってケースクラスにマッピングする
実装
package pattern
import play.api.libs.json._
import play.api.libs.json.Reads._
case class Pattern2(
p1: String,
p2: String,
p3: String,
p4: String,
p5: String,
p6: String,
p7: String,
p8: String,
p9: String,
p10: String,
p11: String,
p12: String,
p13: String,
p14: String,
p15: String,
p16: String,
p17: String,
p18: String,
p19: String,
p20: String,
p21: String,
p22: String,
p23: String,
)
object Pattern2 {
implicit val jsonWrites: Writes[Pattern2] = (p: Pattern2) => {
Json.obj(
"p1" -> p.p1,
"p2" -> p.p2,
"p3" -> p.p3,
"p4" -> p.p4,
"p5" -> p.p5,
"p6" -> p.p6,
"p7" -> p.p7,
"p8" -> p.p8,
"p9" -> p.p9,
"p10" -> p.p10,
"p11" -> p.p11,
"p12" -> p.p12,
"p13" -> p.p13,
"p14" -> p.p14,
"p15" -> p.p15,
"p16" -> p.p16,
"p17" -> p.p17,
"p18" -> p.p18,
"p19" -> p.p19,
"p20" -> p.p20,
"p21" -> p.p21,
"p22" -> p.p22,
"p23" -> p.p23
)
}
// JSON読み込み、パラメータチェックのためのimplicit peter
implicit val jsonReads: Reads[Pattern2] = {
for {
p1 <- (JsPath \ "p1").read[String]
p2 <- (JsPath \ "p2").read[String]
p3 <- (JsPath \ "p3").read[String]
p4 <- (JsPath \ "p4").read[String]
p5 <- (JsPath \ "p5").read[String]
p6 <- (JsPath \ "p6").read[String]
p7 <- (JsPath \ "p7").read[String]
p8 <- (JsPath \ "p8").read[String]
p9 <- (JsPath \ "p9").read[String]
p10 <- (JsPath \ "p10").read[String]
p11 <- (JsPath \ "p11").read[String]
p12 <- (JsPath \ "p12").read[String]
p13 <- (JsPath \ "p13").read[String]
p14 <- (JsPath \ "p14").read[String]
p15 <- (JsPath \ "p15").read[String]
p16 <- (JsPath \ "p16").read[String]
p17 <- (JsPath \ "p17").read[String]
p18 <- (JsPath \ "p18").read[String]
p19 <- (JsPath \ "p19").read[String]
p20 <- (JsPath \ "p20").read[String]
p21 <- (JsPath \ "p21").read[String]
p22 <- (JsPath \ "p22").read[String]
p23 <- (JsPath \ "p23").read[String]
} yield Pattern2(
p1,
p2,
p3,
p4,
p5,
p6,
p7,
p8,
p9,
p10,
p11,
p12,
p13,
p14,
p15,
p16,
p17,
p18,
p19,
p20,
p21,
p22,
p23
)
}
}
レスポンスの確認
正常系
リクエスト
POST http://localhost:9000/pattern2
Content-Type: application/json
{
"p1": "this is p1",
"p2": "this is p2",
"p3": "this is p3",
"p4": "this is p4",
"p5": "this is p5",
"p6": "this is p6",
"p7": "this is p7",
"p8": "this is p8",
"p9": "this is p9",
"p10": "this is p10",
"p11": "this is p11",
"p12": "this is p12",
"p13": "this is p13",
"p14": "this is p14",
"p15": "this is p15",
"p16": "this is p16",
"p17": "this is p17",
"p18": "this is p18",
"p19": "this is p19",
"p20": "this is p20",
"p21": "this is p21",
"p22": "this is p22"
}
レスポンス
{
"result": {
"p1": "this is p1",
"p2": "this is p2",
"p3": "this is p3",
"p4": "this is p4",
"p5": "this is p5",
"p6": "this is p6",
"p7": "this is p7",
"p8": "this is p8",
"p9": "this is p9",
"p10": "this is p10",
"p11": "this is p11",
"p12": "this is p12",
"p13": "this is p13",
"p14": "this is p14",
"p15": "this is p15",
"p16": "this is p16",
"p17": "this is p17",
"p18": "this is p18",
"p19": "this is p19",
"p20": "this is p20",
"p21": "this is p21",
"p22": "this is p22",
"p23": "this is p23"
}
}
異常系
全部のパラメータp1
~p23
に不正な値を設定してリクエストします。
全部エラーのパラメータですが、レスポンスからは最初のパラメータp1
の情報しか確認できません。
リクエスト
POST http://localhost:9000/pattern2
Content-Type: application/json
{
"p1": 1,
"p2": 2,
"p3": 3,
"p4": 4,
"p5": 5,
"p6": 6,
"p7": 7,
"p8": 8,
"p9": 9,
"p10": 10,
"p11": 11,
"p12": 12,
"p13": 13,
"p14": 14,
"p15": 15,
"p16": 16,
"p17": 17,
"p18": 18,
"p19": 19,
"p20": 20,
"p21": 21,
"p22": 22,
"p23": 23
}
レスポンス
{
"result": "failure",
"error": {
"obj.p1": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
]
}
}
(Pattern3)パラメータが23個以上: play-json-extensionを使ってケースクラスにマッピングする
play-json-extensionsを使っています。
https://github.com/xdotai/play-json-extensions
実装
package pattern
import ai.x.play.json.Jsonx
case class Pattern3(
p1: String,
p2: String,
p3: String,
p4: String,
p5: String,
p6: String,
p7: String,
p8: String,
p9: String,
p10: String,
p11: String,
p12: String,
p13: String,
p14: String,
p15: String,
p16: String,
p17: String,
p18: String,
p19: String,
p20: String,
p21: String,
p22: String,
p23: String,
)
object Pattern3 {
implicit val jsonExampleFormat1 = Jsonx.formatCaseClassUseDefaults[Pattern3]
}
レスポンスの確認
正常系
リクエスト
POST http://localhost:9000/pattern3
Content-Type: application/json
{
"p1": "this is p1",
"p2": "this is p2",
"p3": "this is p3",
"p4": "this is p4",
"p5": "this is p5",
"p6": "this is p6",
"p7": "this is p7",
"p8": "this is p8",
"p9": "this is p9",
"p10": "this is p10",
"p11": "this is p11",
"p12": "this is p12",
"p13": "this is p13",
"p14": "this is p14",
"p15": "this is p15",
"p16": "this is p16",
"p17": "this is p17",
"p18": "this is p18",
"p19": "this is p19",
"p20": "this is p20",
"p21": "this is p21",
"p22": "this is p22",
"p23": "this is p23"
}
レスポンス
{
"result": {
"p1": "this is p1",
"p2": "this is p2",
"p3": "this is p3",
"p4": "this is p4",
"p5": "this is p5",
"p6": "this is p6",
"p7": "this is p7",
"p8": "this is p8",
"p9": "this is p9",
"p10": "this is p10",
"p11": "this is p11",
"p12": "this is p12",
"p13": "this is p13",
"p14": "this is p14",
"p15": "this is p15",
"p16": "this is p16",
"p17": "this is p17",
"p18": "this is p18",
"p19": "this is p19",
"p20": "this is p20",
"p21": "this is p21",
"p22": "this is p22",
"p23": "this is p23"
}
}
異常系
全部のパラメータp1
~p23
に不正な値を設定してリクエストします。
Pattern2と同様、全部エラーのパラメータになっています。
結果はPattern2と異なり、レスポンスからパラメータp1
~p23
のチェック結果を確認できます。
リクエスト
POST http://localhost:9000/pattern3
Content-Type: application/json
{
"p1": 1,
"p2": 2,
"p3": 3,
"p4": 4,
"p5": 5,
"p6": 6,
"p7": 7,
"p8": 8,
"p9": 9,
"p10": 10,
"p11": 11,
"p12": 12,
"p13": 13,
"p14": 14,
"p15": 15,
"p16": 16,
"p17": 17,
"p18": 18,
"p19": 19,
"p20": 20,
"p21": 21,
"p22": 22,
"p23": 23
}
レスポンス
{
"result": "failure",
"error": {
"obj.p1": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p2": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p3": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p4": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p5": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p6": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p7": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p8": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p9": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p10": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p11": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p12": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p13": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p14": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p15": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p16": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p17": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p18": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p19": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p20": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p21": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p22": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
],
"obj.p23": [
{
"msg": [
"error.expected.jsstring"
],
"args": []
}
]
}
}
いまのところ、Pattern3でケースクラスにマッピングした後にバリデーションチェックを自前実装するのが良さそう。という自己結論。
何かいい実装方法あったら教えて頂けると助かります。。。