【问题标题】:Play framework JSON reads: How to read either String or Int?播放框架 JSON 读取:如何读取 String 或 Int?
【发布时间】:2018-01-03 16:53:24
【问题描述】:

rest api 的 JS 客户端可以将 int 和 string 作为某个字段的值发送。

{
   field1: "123",
   field2: "456"
}

{
   field1: 123,
   field2: 456
}

这里是 json 请求正文应转换为 case 类的 play 动作:

  case class Dto(field1: Int, field2: Int)
  object Dto {
    implicit val reads = Json.reads[Dto]
  } 

  def create = Action.async(BodyParsers.parse.json) { implicit request =>
    request.body.validate[Dto].map {
      dto => someService.doStuff(dto).map(result => Ok(Json.toJson(result)))
    }.recoverTotal {
      e => jsErrorToBadRequest(e)
    }
  }

如果我发送带有 int 值的 json 值,它可以正常工作。但是如果 field1 或 field2 是字符串(“123”、“456”),它会失败,因为 request.body.validate 需要 Int。

但问题是JS客户端从输入字段发送值,输入字段转换为字符串。

处理整数或字符串的最佳方法是什么? (所以这个动作在这两种情况下都应该将 json 转换为 dto)

【问题讨论】:

    标签: json scala playframework playframework-2.2


    【解决方案1】:

    您还可以定义一个更宽容的Reads[Int]。 并用它来定义你的Reads[Dto]

    1) 定义一个更宽容的Reads[Int]

      import play.api.data.validation.ValidationError
      import play.api.libs.json._
      import scala.util.{Success, Try}
    
      // Define a more tolerant Reads[Int]
      val readIntFromString: Reads[Int] = implicitly[Reads[String]]
          .map(x => Try(x.toInt))
          .collect (ValidationError(Seq("Parsing error"))){
              case Success(a) => a
          }
    
     val readInt: Reads[Int] = implicitly[Reads[Int]].orElse(readIntFromString)
    

    例子:

    readInt.reads(JsNumber(1))
    // JsSuccess(1,)
    
    readInt.reads(JsString("1"))
    //  JsSuccess(1,)
    
    readInt.reads(JsString("1x"))
    // JsError(List((,List(ValidationError(List(Parsing error),WrappedArray())))
    

    2) 使用您更宽容的Reads[Int] 来定义您的Reads[Dto]

    implicit val DtoReads = 
        (JsPath \ "field1").read[Int](readInt) and 
        (JsPath \ "field2").read[Int](readInt)
    

    编辑:与米尔豪斯解决方案的差异:

    • 如果 field1string 并且 field2int 使用此解决方案,您将获得 JsSuccessJsError用millhouse的解决方案

    • 如果此解决方案的两个字段都无效,您将收到一个 JsError,其中每个字段都包含一个错误。使用millhouse 的解决方案,您将得到第一个错误。

    【讨论】:

    • 在 play 2.6 上使用 JsonValidationError 而不是 ValidationError
    【解决方案2】:

    使用 or 组合器(在此处记录 https://www.playframework.com/documentation/2.6.x/ScalaJsonCombinators)和很少的读取功能实际上很容易。

    case class Customer(name: String, number: Int)
    
    object Customer {
      val readIntFromString: Reads[Int] = implicitly[Reads[String]]
        .map(x => x.toInt)
    
      import play.api.libs.functional.syntax._
      import play.api.libs.json._
    
      implicit val routeReads: Reads[Customer] =
        ((__ \ "name").read[String] and
          ((__ \ "number").read[Int] or
            (__ \ "number").read[Int](readIntFromString)))(Customer.apply _)
    }
    

    【讨论】:

      【解决方案3】:

      您需要为您的 Dto 自定义 Reads 实现 - 即 Reads[Dto]。我总是喜欢从您通过Json.reads[Dto] 获得的“内置”(宏生成)开始 - 然后从那里开始;例如:

      object Dto {
        val basicReads = Json.reads[Dto]
      
        implicit val typeCorrectingReads = new Reads[Dto]{
      
          def reads(json: JsValue): JsResult[Dto] = {
      
            def readAsInteger(fieldName:String):JsResult[Int] = {
              (json \ fieldName).validate[String].flatMap { s =>
                // We've got a String, but it might not be convertible to an int...
                Try(s.toInt).map(JsSuccess(_)).getOrElse {
                  JsError(JsPath \ fieldName, s"Couldn't convert string $s to an integer")
                }
              }
            }
      
            basicReads.reads(json).orElse {
              for {
                f1 <- readAsInteger("field1")
                f2 <- readAsInteger("field2")
              } yield {
                Dto(f1, f2)
              }
            }
          }
        }
      }
      

      通过这种方式,您可以让basicReads 在“快乐的情况下”完成工作。如果不成功,我们尝试将字段视为String 实例,然后最终尝试转换为Int

      请注意,我们尽可能在由“其他人”创建的JsResult 范围内工作,因此我们会很快失败。

      【讨论】:

        猜你喜欢
        • 2016-06-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-03
        • 2016-03-07
        • 2018-01-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多