Play框架JSON读取:如何读取String或Int?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Play框架JSON读取:如何读取String或Int?相关的知识,希望对你有一定的参考价值。

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)

答案

你也可以定义一个更宽容的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)

编辑:与millhouse的解决方案的差异:

  • 如果field1是一个字符串而field2是一个int这个解决方案你会得到一个JsSuccessJsError与millhouse的解决方案
  • 如果此解决方案的两个字段都无效,您将获得包含每个字段一个错误的JsError。使用millhouse的解决方案,您将收到第一个错误。
另一答案

你需要为你的Reads定制Dto实现 - 即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范围内工作,因此我们将快速失败。

另一答案

使用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 _)
}

以上是关于Play框架JSON读取:如何读取String或Int?的主要内容,如果未能解决你的问题,请参考以下文章

Play 框架:实现 RESTful 更新操作的问题

如何在 pandas 中读取大的 json?

如何在 Swift 中使用 Alamofire 读取本地 JSON

后台dwr返回一个string类型的json格式数据,前台js如何读取?

如何解析JSON使用Play框架

Delphi7中如何读取或显示Bytes类型的字段