LoginSignup
2
2

More than 5 years have passed since last update.

Play Framework2.6のJsonで23要素以上のデシリアライズとシリアライズ

Last updated at Posted at 2018-10-12

業務の都合上の話ですが、23要素以上のフラットな形式のJSONリクエストパラメータをうまくケースクラスにマッピングして当てはめる方法を少し調べました。
その時のメモです。
Scala/Play初心者のため、よい実装があったらぜひ教えてください。

検討ケースとしては以下です。

  1. (Pattern1)パラメータが22個以下: Playのデファクトスタンダードな実装
  2. (Pattern2)パラメータが23個以上: for-yieldを使ってケースクラスにマッピングする
  3. (Pattern3)パラメータが23個以上: play-json-extensionを使ってケースクラスにマッピングする

※前提事項: 1.の実装の場合,23要素以上に対応できません。

結果としてはパラメータ異常時のレスポンスが異なりました
以降は重要だと思われる箇所を太字にして記載しています。

なお、リクエストでもらったJSONのパラメータをケースクラスにマッピングした後にレスポンスで返すような単純な実装で試してみました。
コントローラは以下のようになっています。
BadRequest(Json.obj("result" -> "failure", "error" -> JsError.toJson(error)))でエラーの情報を返しています。

TestParamsController.scala
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を追加しています。

build.sbt
libraryDependencies += "ai.x" %% "play-json-extensions" % "0.10.0"

(Pattern1)パラメータが22個以下: Playのデファクトスタンダードな実装

普通のパターンです。

実装

Pattern1.scala
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を使ってケースクラスにマッピングする

実装

Pattern2.scala
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

実装

Patten3.scala
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と異なり、レスポンスからパラメータp1p23のチェック結果を確認できます

リクエスト

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でケースクラスにマッピングした後にバリデーションチェックを自前実装するのが良さそう。という自己結論。

何かいい実装方法あったら教えて頂けると助かります。。。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2