[英]Play framework JSON reads: How to read either String or Int?
JS client of rest api can send both int and string as a value of some field. rest api的JS客户端可以将int和string作为某个字段的值发送。
{
field1: "123",
field2: "456"
}
{
field1: 123,
field2: 456
}
Here is play action with case class to which json request body should be converted: 以下是应该转换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)
}
}
In case if I send json values with int values, it works ok. 如果我发送带有int值的json值,它可以正常工作。 But in case if field1 or field2 are strings ("123", "456"), it fails, because request.body.validate expects Int.
但是如果field1或field2是字符串(“123”,“456”),则它会失败,因为request.body.validate需要Int。
But problem is that JS client sends values from input fields, and input fields are converted to strings. 但问题是JS客户端从输入字段发送值,输入字段转换为字符串。
What is the best way to handle either ints or strings? 处理整数或字符串的最佳方法是什么? (So this action should convert json to dto in both cases)
(所以这个动作应该在两种情况下都将json转换为dto)
You could also define a more tolerant Reads[Int]
. 您还可以定义更宽容的
Reads[Int]
。 And use it to define your Reads[Dto]
并用它来定义你的
Reads[Dto]
1) Define a more tolerant Reads[Int]
: 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)
Examples: 例子:
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) Use your more tolerant Reads[Int]
to define your Reads[Dto]
: 2)使用更宽容的
Reads[Int]
来定义Reads[Dto]
:
implicit val DtoReads =
(JsPath \ "field1").read[Int](readInt) and
(JsPath \ "field2").read[Int](readInt)
EDIT: Differences with millhouse's solution: 编辑:与millhouse的解决方案的差异:
if field1
is a string and field2
is an int with this solution you'll get a JsSuccess
but a JsError
with millhouse's solution 如果
field1
是一个字符串 , field2
是与此解决方案,您会得到一个int JsSuccess
但JsError
与米尔豪斯的解决方案
If both field are invalid with this solution you'll get a JsError
containing one error for each field. 如果此解决方案的两个字段都无效,您将获得包含每个字段一个错误的
JsError
。 With millhouse's solution you'll get the first error. 使用millhouse的解决方案,您将收到第一个错误。
You need a custom Reads
implementation for your Dto
- ie a Reads[Dto]
. 您需要为
Dto
实现自定义Reads
实现 - 即Reads[Dto]
。 I always like to start with the "built-in" (macro-generated) one you get via Json.reads[Dto]
- and then go from there; 我总是喜欢从
Json.reads[Dto]
获得的“内置”(宏生成)开始 - 然后从那里开始; eg: 例如:
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)
}
}
}
}
}
By doing it this way, you get the basicReads
to do the work in the "happy case". 通过这种方式,您可以获得
basicReads
来完成“快乐案例”中的工作。 If it doesn't work out, we then try treating the fields as String
instances, before finally attempting the conversion to an Int
. 如果它不成功,我们会尝试将字段视为
String
实例,然后再尝试转换为Int
。
Note how wherever possible, we're working inside the scope of a JsResult
that was created by "somebody else", so we'll fail fast. 请注意,
JsResult
,我们在由“其他人”创建的JsResult
范围内工作,因此我们将快速失败。
It is actually quite easy with or
combinator (documented here https://www.playframework.com/documentation/2.6.x/ScalaJsonCombinators ) and little reads function. 它实际上非常容易使用
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 _)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.