解析JSON正文时播放框架奇怪行为 [英] Play Framework Strange Behavior when Parsing JSON Body

查看:135
本文介绍了解析JSON正文时播放框架奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个接受JSON正文的端点。我有这种JSON格式的隐式读写。在端点中,我对JSON进行了验证并折叠了结果!这里是:

  def updatePowerPlant(id:Int)= Action.async(parse.tolerantJson){request => 
request.body.validate [PowerPlantConfig] .fold(
errors => {
Future.successful(
BadRequest(
Json.obj(message - > sinvalid PowerPlantConfig $ {errors.mkString(,)})
).enableCors

},
success => {
dbService.insertOrUpdatePowerPlant(success).runAsync.materialize.map {
case Failure(ex)=>
InternalServerError(sError更新PowerPlant+
sReason => $ {ex .getMessage})。enableCors
case Success(result)=>
result match {
case Left(errorMessage)=>
BadRequest(Json.obj(message - > sinvalid PowerPlantConfig $ errorMessage))。enableCors
case Right(updatedConfig)=>
Ok(Json.prettyPrint(Json.toJson(updatedConfig)))。enableCors
}
}
}






$ b

所以我们可以看到,我折叠了这个错误,并返回一个BadRequest 。但是当我尝试编写一个单元测试时,我没有像我期望的那样获得HTTP状态作为BadRequest,但测试崩溃时出现如下异常:

  JsResultException(错误:List((,List(ValidationError(List(error.expected.jsnumber),WrappedArray())))))
play.api.libs.json.JsResultException:JsResultException (错误:列表((,)列表(ValidationError(List(error.expected.jsnumber),WrappedArray())))))
at play.api.libs.json.JsReadable $$ anonfun $ 2.apply(JsReadable .scala:23)
at play.api.libs.json.JsReadable $$ anonfun $ 2.apply(JsReadable.scala:23)
at play.api.libs.json.JsResult $ class.fold (JsResult.scala:73)
at play.api.libs.json.JsError.fold(JsResult.scala:13)
at play.api.libs.json.JsReadable $ class.as(JsReadable .scala:21)
at play.api.libs.json.JsDefined.as(JsLookup.scala:132)
at com.inland24.plantsim.models.package $$ anon $ 1.reads(package .scala:61)
at play.api.libs.json.JsValue $ class.validate(JsValue.scala:18)
at play.api.libs.json.JsObject.validate(JsValue.scala:76)
at com.inland24.plantsim.controllers.PowerPlantController $$ anonfun $ updatePowerPlant1 $ 1.apply(PowerPlantController.scala: 64)
at com.inland24.plantsim.controllers.PowerPlantController $$ anonfun $ updatePowerPlant1 $ 1.apply(PowerPlantController.scala:63)
at play.api.mvc.Action $ .invokeBlock(Action.scala :498)
at play.api.mvc.Action $ .invokeBlock(Action.scala:495)
at play.api.mvc.ActionBuilder $$ anon $ 2.apply(Action.scala:458)
at com.inland24.plantsim.controllers.PowerPlantControllerTest $$ anonfun $ 4 $$ anonfun $ apply $ mcV $ sp $ 11.apply(PowerPlantControllerTest.scala:313)
at com.inland24.plantsim.controllers。 PowerPlantControllerTest $$ anonfun $ 4 $$ anonfun $ apply $ mcV $ sp $ 11.apply(PowerPlantControllerTest.scala:296)
at org.scalatest.OutcomeOf $ class.outcomeOf(OutcomeOf.scala:85)
at org.scalatest.OutcomeOf $ .outcomeOf(OutcomeOf.scala:104)
at org.scalatest.Trans former.apply(Transformer.scala:22)
at org.scalatest.Transformer.apply(Transformer.scala:20)
at org.scalatest.WordSpecLike $$ anon $ 1.apply(WordSpecLike.scala: 1078)
at org.scalatest.TestSuite $ class.withFixture(TestSuite.scala:196)
at com.inland24.plantsim.controllers.PowerPlantControllerTest.withFixture(PowerPlantControllerTest.scala:40)
org.scalatest.WordSpecLike $ class.invokeWithFixture $ 1(WordSpecLike.scala:1075)
at org.scalatest.WordSpecLike $$ anonfun $ runTest $ 1.apply(WordSpecLike.scala:1088)
at org。 scalatest.WordSpecLike $$ anonfun $ runTest $ 1.apply(WordSpecLike.scala:1088)
at org.scalatest.SuperEngine.runTestImpl(Engine.scala:289)
at org.scalatest.WordSpecLike $ class。的runTest(WordSpecLike.scala:1088)
。在com.inland24.plantsim.controllers.PowerPlantControllerTest.runTest(PowerPlantControllerTest.scala:40)
。在org.scalatest.WordSpecLike $$ anonfun $ $ runTests 1.适用( WordSpecLike.scala:1147)
a t org.scalatest.WordSpecLike $$ anonfun $ runTests $ 1.apply(WordSpecLike.scala:1147)
at org.scalatest.SuperEngine $$ anonfun $ traverseSubNodes $ 1 $ 1.apply(Engine.scala:396)
at org.scalatest.SuperEngine $$ anonfun $ traverseSubNodes $ 1 $ 1.apply(Engine.scala:384)
at scala.collection.immutable.List.foreach(List.scala:392)
at org .scalatest.SuperEngine.traverseSubNodes $ 1(Engine.scala:384)
at org.scalatest.SuperEngine.org $ scalatest $ SuperEngine $$ runTestsInBranch(Engine.scala:373)
at org.scalatest.SuperEngine $$ anonfun $ traverseSubNodes $ 1 $ 1.apply(Engine.scala:410)
at org.scalatest.SuperEngine $$ anonfun $ traverseSubNodes $ 1 $ 1.apply(Engine.scala:384)
at scala.collection .immutable.List.foreach(List.scala:392)
at org.scalatest.SuperEngine.traverseSubNodes $ 1(Engine.scala:384)
at org.scalatest.SuperEngine.org $ scalatest $ SuperEngine $ $ runTestsInBranch(Engine.scala:379)
at org.scalatest.SuperEngine.runTestsImpl(Engine.scala:46 1)
at org.scalatest.WordSpecLike $ class.runTests(WordSpecLike.scala:1147)
at com.inland24.plantsim.controllers.PowerPlantControllerTest.runTests(PowerPlantControllerTest.scala:40)
at org.scalatest.Suite $ class.run(Suite.scala:1147)
at com.inland24.plantsim.controllers.PowerPlantControllerTest.org $ scalatest $ WordSpecLike $$ super $ run(PowerPlantControllerTest.scala:40)
at org.scalatest.WordSpecLike $$ anonfun $ run $ 1.apply(WordSpecLike.scala:1192)
at org.scalatest.WordSpecLike $$ anonfun $ run $ 1.apply(WordSpecLike.scala:1192)
at org.scalatest.SuperEngine.runImpl(Engine.scala:521)
at org.scalatest.WordSpecLike $ class.run(WordSpecLike.scala:1192)
at com.inland24.plantsim。 controllers.PowerPlantControllerTest.org $ scalatest $ BeforeAndAfterAll $$超$运行(PowerPlantControllerTest.scala:40)
在org.scalatest.BeforeAndAfterAll $ class.liftedTree1 $ 1(BeforeAndAfterAll.scala:213)
。在组织。 scalatest.BeforeAndAfterAll $类.RUN(BeforeAndAfterAll.scala:210)
。在com.inland24.plantsim.controllers.PowerPlantControllerTest.run(PowerPlantControllerTest.scala:40)
。在org.scalatest.tools.SuiteRunner.run(SuiteRunner.scala :45)
。在org.scalatest.tools.Runner $$ anonfun $ $ doRunRunRunDaDoRunRun 1.适用(Runner.scala:1340)
。在org.scalatest.tools.Runner $$ anonfun $ $ doRunRunRunDaDoRunRun 1.适用(Runner.scala:1334)
at scala.collection.immutable.List.foreach(List.scala:392)
at org.scalatest.tools.Runner $ .doRunRunRunDaDoRunRun(Runner.scala:1334)
在org.scalatest.tools.Runner $$ anonfun $ runOptionallyWithPassFailReporter $ 2.适用(Runner.scala:1011)
在org.scalatest.tools.Runner $$ anonfun $ runOptionallyWithPassFailReporter $ 2.适用(亚军。 scala:1010)
at org.scalatest.tools.Runner $ .withClassLoaderAndDispatchReporter(Runner.scala:1500)
at org.scalatest.tools.Runner $ .runOptionallyWithPassFailReporter(Runner.scala:1010)$ b $ org.scalatest.tools.Runner $ .run(Runner.scala:850)
at org.scalatest.tools.Runner.run(Runner.scala)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.runScalaTest2( ScalaTestRunner.java:138)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.main(ScalaTestRunner.java:28)

以下是我的单元测试:

 不更新无效的PowerPlantConfig JSON in {
//我们正在更新id = 101的PowerPlant,注意powerPlantId无效
val jsBody =

| {
| powerPlantId:invalidId,
| powerPlantName:joesan 1,
| minPower:100,
| maxPower:800,
| rampPowerRate:20.0,
| rampRateInSeconds:2秒,
| powerPlantType:RampUpType
|}
.stripMargin

结果:Future [结果] =
controller.updatePowerPlant(101)
.apply(
FakeRequest()。withBody(Json.parse(jsBody))

result.materialize.map {
case Success(succ)=>
assert(succ.header.status === BAD_REQUEST)
案例失败(_)=>
失败(更新PowerPlant时发生意外测试失败!请分析!)
}
}

任何想法为什么我没有得到预期的行为?我希望我得到一个HTTP BadRequest!

编辑:为了摆脱意外的异常,我不得不将我的代码包装到一个Try块中,我不想那么这段代码就会消除这个错误:

pre $ def $ updatePowerPlant(id:Int)= Action.async(parse .tolerantJson){request =>
scala.util.Try(请求t.body.validate [PowerPlantConfig])match {
case Failure(fail)=>
Future.successful(InternalServerError(sError updating PowerPlant+
sReason => $ {fail.getMessage})。enableCors)
case Success(succ)=>
succ.fold(
errors => {
Future.successful(
BadRequest(
Json.obj(message - > sinvalid PowerPlantConfig $ {errors.mkString(,)})
).enableCors

},
success => {
dbService.insertOrUpdatePowerPlant(成功)。 runAsync.materialize.map {
case Failure(ex)=>
InternalServerError(sError updating PowerPlant+
sReason => $ {ex.getMessage})。enableCors
case成功(结果)=>
结果匹配{
case Left(errorMessage)=>
BadRequest(Json.obj(message - > sinvalid PowerPlantConfig $ errorMessage))。enableCors
case Right(updatedConfig)=>
Ok(Json.prettyPrint(Json.toJson(updatedConfig)))。enableCors
}
}
}

}
}

但是可以看出,还有另外一个Try(....)块,我不想要这个!



这是我的定义PowerPlantConfig:

 密封trait PowerPlantConfig {
def id:Int
def name:String
def minPower:Double
def maxPower:Double
def powerPlantType:PowerPlantType
}
object PowerPlantConfig {

case class OnOffTypeConfig(
id :Int,
name:String,
minPower:Double,
maxPower:Double,
powerPlantType:PowerPlantType
)extends PowerPlantConfig

case class RampUpTypeConfig(
id:Int,
name:String,
minPower:Double,
maxPower:Double,
rampPowerRate:Double,
rampRateInSeconds:FiniteDuration ,
powerPlantType:PowerPlantType
)扩展PowerPlantConfig

case class UnknownConfig(
id:Int = -1,
name:String,
minPower:Double,
maxPower:Double,
powerPlantType :PowerPlantType
)扩展PowerPlantConfig

//代表数据库中的所有PowerPlant's
案例类PowerPlantsConfig(
snapshotDateTime:DateTime,
powerPlantConfigSeq:Seq [ PowerPlantConfig]

}

这是我的JSON读写:

 隐式val powerPlantCfgFormat:格式[PowerPlantConfig] =新格式[PowerPlantConfig] {
def reads(json:JsValue): JsResult [PowerPlantConfig] = {
val powerPlantTyp = PowerPlantType.fromString((json \powerPlantType)。as [String])
powerPlantTyp match {
case PowerPlantType.OnOffType =>
JsSuccess(OnOffTypeConfig(
id =(json \powerPlantId)。as [Int],
name =(json \powerPlantName)。as [String],
$ minPower =(json \minPower)。as [Double],
maxPower =(json \maxPower)。as [Double],
powerPlantType = powerPlantTyp
) )
case PowerPlantType.RampUpType =>
JsSuccess(RampUpTypeConfig(
id =(json \powerPlantId)。as [Int],
name =(json \powerPlantName)。as [String],
minPower =(json \minPower)。as [Double],
rampPowerRate =(json \rampPowerRate)。as [Double],
rampRateInSeconds = Duration.apply((json \rampRateInSeconds)。as [String])。asInstanceOf [FiniteDuration],
maxPower =(json \maxPower)。as [Double],
powerPlantType = powerPlantTyp
) )
案例_ =>
JsSuccess(UnknownConfig(
id =(json \powerPlantId)。as [Int],
name =(json \powerPlantName)。as [String],
$ minPower =(json \minPower)。as [Double],
maxPower =(json \maxPower)。as [Double],
powerPlantType = powerPlantTyp
) )


$ b $ def写(o:PowerPlantConfig):JsValue = {
if(o.powerPlantType == RampUpType){
Json。 obj(
powerPlantId - > o.id,
powerPlantName - > o.name,
minPower - > o.minPower,
maxPower - > o.maxPower,
的rampPowerRate - > o.asInstanceOf [RampUpTypeConfig] .rampPowerRate,
的rampRateInSeconds - > o.asInstanceOf [RampUpTypeConfig] .rampRateInSeconds.toString(),
powerPlantType - > PowerPlantType.toString(o.powerPlantType)

}
else {
Json.obj(
powerPlantId - > o.id,
powerPlantName - > o.name,
minPower - > o.minPower,
maxPower - > o.maxPower,
powerPlantType - > PowerPlantType.toString(o.powerPlantType)

}
}
}


解决方案

根据您的堆栈跟踪(第一行标记)

  JsResultException (错误:List((),List(ValidationError(List(error.expected.jsnumber),WrappedArray())))))
play.api.libs.json.JsResultException:JsResultException(errors,List((, List(ValidationError(List(error.expected.jsnumber),WrappedArray())))))
at play.api.libs.json.JsReadable $$ anonfun $ 2.apply(JsReadable.scala:23)
at play.api.libs.json.JsReadable $$ anonfun $ 2.apply(JsReadable.scala:23)
at play.api.libs.json.JsResult $ class.fold(JsResult.scala:73)
at play.api.libs.json.JsError.fold(JsResult.scala:13)
- > at play.api.libs.json.JsReadable $ class.as(JsReadable.scala:21)
at play.api.libs.json.JsDefined.as(JsLookup.scala:132)
at com .inland24.plantsim.models.package $$ anon $ 1.reads(package.scala:61)
at play.api.libs.json.JsValue $ class.validate(JsValue.scala:18)
at play.api.libs.json.JsObject.validate(JsValue.scala:76)
at com.inland24.plantsim.controllers.PowerPlantController $$ anonfun $ updatePowerPlant1 $ 1.apply(PowerPlantController.scala:64)
at com.inland24.plantsim.controllers.PowerPlantController $$ anonfun $ updatePowerPlant1 $ 1.apply(PowerPlantController.scala:63)

您在阅读格式中为 id使用作为[Int] id PowerPlantConfig 字段。
当你调用为[Int] 时,你试图强制给定的json路径来输入 Int 。如果它不能(如在你的测试中)抛出一个异常。您可以阅读和之间的区别, asOpt validate 此处例如



更新



如果您将的实现视为 asOpt validate 你会看到,所有这三者起初都是一样的,但不同在某种程度上:



validate - 我确实需要结果或包裹失败时的信息(只需调用 read json上的隐式arg)



asOpt - 我需要无论是结果还是没有,如果下面的读取用于分辨率返回分析错误,它将被忽略,因为没有设置



as - 我需要结果或异常。换句话说,我敢肯定,这总是这样的类型,如果没有,比它是一般错误



code>和 asOpt 是带有解释结果的扩展验证。



strong>



示例如何将移动到 validate 在层次结构中(两种格式 - 其中一种作为你的作为将引发异常,另一种作为 validate 抛出异常):

 密封trait PowerPlantConfig {
def id:Int
}
case (JSON):JsResult [PowerPlantConfig] = {RampUpTypeConfig(id:Int)extends PowerPlantConfig

implicit val powerPlantCfgFormat:Format [PowerPlantConfig] = new Format [PowerPlantConfig] {
def reads(json:JsValue)
JsSuccess(RampUpTypeConfig(
id =(json \powerPlantId)。as [Int]
))
}
def write(o:PowerPlantConfig): JsValue = {
Json.obj(
powerPlantId - > (json):JsResult [PowerPlantConfig] = {
}
}
val PowerPlantCfgFormatFixed:Format [PowerPlantConfig] = new Format [PowerPlantConfig] {
def reads(json:JsValue)
为{
id< - (json \powerPlantId)。validate [Int]
} yield {
RampUpTypeConfig(
id = id

}

}
def写(o:PowerPlantConfig):JsValue = {
Json.obj(
id - > o .id)
}
}


Json.parse({powerPlantId:123})。validate [PowerPlantConfig] (powerPlantCfgFormatFixed)

输出将不会出现异常,但JsFailure与预期的一样

  res1:play.api.libs.json.JsResult [PowerPlantConfig] = JsError(List((,List(ValidationError(error.expected.jsnumber,WrappedArray ))))))


I have a endpoint that takes in a JSON body. I have implicit reads and writes for this JSON format. In the endpoint, I do a validation of the JSON and fold on the result! Here it is:

def updatePowerPlant(id: Int) = Action.async(parse.tolerantJson) { request =>
    request.body.validate[PowerPlantConfig].fold(
      errors => {
        Future.successful(
          BadRequest(
            Json.obj("message" -> s"invalid PowerPlantConfig ${errors.mkString(",")}")
          ).enableCors
        )
      },
      success => {
        dbService.insertOrUpdatePowerPlant(success).runAsync.materialize.map {
          case Failure(ex) =>
            InternalServerError(s"Error updating PowerPlant " +
              s"Reason => ${ex.getMessage}").enableCors
          case Success(result) =>
            result match {
              case Left(errorMessage) =>
                BadRequest(Json.obj("message" -> s"invalid PowerPlantConfig $errorMessage")).enableCors
              case Right(updatedConfig) =>
                Ok(Json.prettyPrint(Json.toJson(updatedConfig))).enableCors
            }
        }
      }
    )
  }

So as it can be seen that I fold on the error and I return a BadRequest. But when I tried writing a unit test, I do not get the HTTP status as BadRequest as I expect, but the test crashes with an exception as below:

JsResultException(errors:List((,List(ValidationError(List(error.expected.jsnumber),WrappedArray())))))
play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(List(error.expected.jsnumber),WrappedArray())))))
    at play.api.libs.json.JsReadable$$anonfun$2.apply(JsReadable.scala:23)
    at play.api.libs.json.JsReadable$$anonfun$2.apply(JsReadable.scala:23)
    at play.api.libs.json.JsResult$class.fold(JsResult.scala:73)
    at play.api.libs.json.JsError.fold(JsResult.scala:13)
    at play.api.libs.json.JsReadable$class.as(JsReadable.scala:21)
    at play.api.libs.json.JsDefined.as(JsLookup.scala:132)
    at com.inland24.plantsim.models.package$$anon$1.reads(package.scala:61)
    at play.api.libs.json.JsValue$class.validate(JsValue.scala:18)
    at play.api.libs.json.JsObject.validate(JsValue.scala:76)
    at com.inland24.plantsim.controllers.PowerPlantController$$anonfun$updatePowerPlant1$1.apply(PowerPlantController.scala:64)
    at com.inland24.plantsim.controllers.PowerPlantController$$anonfun$updatePowerPlant1$1.apply(PowerPlantController.scala:63)
    at play.api.mvc.Action$.invokeBlock(Action.scala:498)
    at play.api.mvc.Action$.invokeBlock(Action.scala:495)
    at play.api.mvc.ActionBuilder$$anon$2.apply(Action.scala:458)
    at com.inland24.plantsim.controllers.PowerPlantControllerTest$$anonfun$4$$anonfun$apply$mcV$sp$11.apply(PowerPlantControllerTest.scala:313)
    at com.inland24.plantsim.controllers.PowerPlantControllerTest$$anonfun$4$$anonfun$apply$mcV$sp$11.apply(PowerPlantControllerTest.scala:296)
    at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)
    at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
    at org.scalatest.Transformer.apply(Transformer.scala:22)
    at org.scalatest.Transformer.apply(Transformer.scala:20)
    at org.scalatest.WordSpecLike$$anon$1.apply(WordSpecLike.scala:1078)
    at org.scalatest.TestSuite$class.withFixture(TestSuite.scala:196)
    at com.inland24.plantsim.controllers.PowerPlantControllerTest.withFixture(PowerPlantControllerTest.scala:40)
    at org.scalatest.WordSpecLike$class.invokeWithFixture$1(WordSpecLike.scala:1075)
    at org.scalatest.WordSpecLike$$anonfun$runTest$1.apply(WordSpecLike.scala:1088)
    at org.scalatest.WordSpecLike$$anonfun$runTest$1.apply(WordSpecLike.scala:1088)
    at org.scalatest.SuperEngine.runTestImpl(Engine.scala:289)
    at org.scalatest.WordSpecLike$class.runTest(WordSpecLike.scala:1088)
    at com.inland24.plantsim.controllers.PowerPlantControllerTest.runTest(PowerPlantControllerTest.scala:40)
    at org.scalatest.WordSpecLike$$anonfun$runTests$1.apply(WordSpecLike.scala:1147)
    at org.scalatest.WordSpecLike$$anonfun$runTests$1.apply(WordSpecLike.scala:1147)
    at org.scalatest.SuperEngine$$anonfun$traverseSubNodes$1$1.apply(Engine.scala:396)
    at org.scalatest.SuperEngine$$anonfun$traverseSubNodes$1$1.apply(Engine.scala:384)
    at scala.collection.immutable.List.foreach(List.scala:392)
    at org.scalatest.SuperEngine.traverseSubNodes$1(Engine.scala:384)
    at org.scalatest.SuperEngine.org$scalatest$SuperEngine$$runTestsInBranch(Engine.scala:373)
    at org.scalatest.SuperEngine$$anonfun$traverseSubNodes$1$1.apply(Engine.scala:410)
    at org.scalatest.SuperEngine$$anonfun$traverseSubNodes$1$1.apply(Engine.scala:384)
    at scala.collection.immutable.List.foreach(List.scala:392)
    at org.scalatest.SuperEngine.traverseSubNodes$1(Engine.scala:384)
    at org.scalatest.SuperEngine.org$scalatest$SuperEngine$$runTestsInBranch(Engine.scala:379)
    at org.scalatest.SuperEngine.runTestsImpl(Engine.scala:461)
    at org.scalatest.WordSpecLike$class.runTests(WordSpecLike.scala:1147)
    at com.inland24.plantsim.controllers.PowerPlantControllerTest.runTests(PowerPlantControllerTest.scala:40)
    at org.scalatest.Suite$class.run(Suite.scala:1147)
    at com.inland24.plantsim.controllers.PowerPlantControllerTest.org$scalatest$WordSpecLike$$super$run(PowerPlantControllerTest.scala:40)
    at org.scalatest.WordSpecLike$$anonfun$run$1.apply(WordSpecLike.scala:1192)
    at org.scalatest.WordSpecLike$$anonfun$run$1.apply(WordSpecLike.scala:1192)
    at org.scalatest.SuperEngine.runImpl(Engine.scala:521)
    at org.scalatest.WordSpecLike$class.run(WordSpecLike.scala:1192)
    at com.inland24.plantsim.controllers.PowerPlantControllerTest.org$scalatest$BeforeAndAfterAll$$super$run(PowerPlantControllerTest.scala:40)
    at org.scalatest.BeforeAndAfterAll$class.liftedTree1$1(BeforeAndAfterAll.scala:213)
    at org.scalatest.BeforeAndAfterAll$class.run(BeforeAndAfterAll.scala:210)
    at com.inland24.plantsim.controllers.PowerPlantControllerTest.run(PowerPlantControllerTest.scala:40)
    at org.scalatest.tools.SuiteRunner.run(SuiteRunner.scala:45)
    at org.scalatest.tools.Runner$$anonfun$doRunRunRunDaDoRunRun$1.apply(Runner.scala:1340)
    at org.scalatest.tools.Runner$$anonfun$doRunRunRunDaDoRunRun$1.apply(Runner.scala:1334)
    at scala.collection.immutable.List.foreach(List.scala:392)
    at org.scalatest.tools.Runner$.doRunRunRunDaDoRunRun(Runner.scala:1334)
    at org.scalatest.tools.Runner$$anonfun$runOptionallyWithPassFailReporter$2.apply(Runner.scala:1011)
    at org.scalatest.tools.Runner$$anonfun$runOptionallyWithPassFailReporter$2.apply(Runner.scala:1010)
    at org.scalatest.tools.Runner$.withClassLoaderAndDispatchReporter(Runner.scala:1500)
    at org.scalatest.tools.Runner$.runOptionallyWithPassFailReporter(Runner.scala:1010)
    at org.scalatest.tools.Runner$.run(Runner.scala:850)
    at org.scalatest.tools.Runner.run(Runner.scala)
    at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.runScalaTest2(ScalaTestRunner.java:138)
    at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.main(ScalaTestRunner.java:28)

Here is my unit test:

"not update for an invalid PowerPlantConfig JSON" in {
      // We are updating the PowerPlant with id = 101, Notice that the powerPlantId is invalid
      val jsBody =
        """
          |{
          |   "powerPlantId":"invalidId",
          |   "powerPlantName":"joesan 1",
          |   "minPower":100,
          |   "maxPower":800,
          |   "rampPowerRate":20.0,
          |   "rampRateInSeconds":"2 seconds",
          |   "powerPlantType":"RampUpType"
          |}
        """.stripMargin

      val result: Future[Result] =
        controller.updatePowerPlant(101)
          .apply(
            FakeRequest().withBody(Json.parse(jsBody))
          )
      result.materialize.map {
        case Success(succ) =>
          assert(succ.header.status === BAD_REQUEST)
        case Failure(_) =>
          fail("Unexpected test failure when Updating a PowerPlant! Please Analyze!")
      }
    }

Any idea why I'm not getting the expected behavior? I'm expecting that I get a HTTP BadRequest back!

EDIT: To get rid of the unexpected exception, I had to wrap my code into a Try block and I do not want that. So this piece of code gets rid of the error:

def updatePowerPlant(id: Int) = Action.async(parse.tolerantJson) { request =>
    scala.util.Try(request.body.validate[PowerPlantConfig]) match {
      case Failure(fail) =>
        Future.successful(InternalServerError(s"Error updating PowerPlant " +
          s"Reason => ${fail.getMessage}").enableCors)
      case Success(succ) =>
        succ.fold(
          errors => {
            Future.successful(
              BadRequest(
                Json.obj("message" -> s"invalid PowerPlantConfig ${errors.mkString(",")}")
              ).enableCors
            )
          },
          success => {
            dbService.insertOrUpdatePowerPlant(success).runAsync.materialize.map {
              case Failure(ex) =>
                InternalServerError(s"Error updating PowerPlant " +
                  s"Reason => ${ex.getMessage}").enableCors
              case Success(result) =>
                result match {
                  case Left(errorMessage) =>
                    BadRequest(Json.obj("message" -> s"invalid PowerPlantConfig $errorMessage")).enableCors
                  case Right(updatedConfig) =>
                    Ok(Json.prettyPrint(Json.toJson(updatedConfig))).enableCors
                }
            }
          }
        )
    }
  }

But as it can be seen that there is this additional Try(....) block and I do not want this!

Here is my definition of PowerPlantConfig:

sealed trait PowerPlantConfig {
  def id: Int
  def name: String
  def minPower: Double
  def maxPower: Double
  def powerPlantType: PowerPlantType
}
object PowerPlantConfig {

  case class OnOffTypeConfig(
    id: Int,
    name: String,
    minPower: Double,
    maxPower: Double,
    powerPlantType: PowerPlantType
  ) extends PowerPlantConfig

  case class RampUpTypeConfig(
    id: Int,
    name: String,
    minPower: Double,
    maxPower: Double,
    rampPowerRate: Double,
    rampRateInSeconds: FiniteDuration,
    powerPlantType: PowerPlantType
  ) extends PowerPlantConfig

  case class UnknownConfig(
    id: Int = -1,
    name: String,
    minPower: Double,
    maxPower: Double,
    powerPlantType: PowerPlantType
  ) extends PowerPlantConfig

  // represents all the PowerPlant's from the database
  case class PowerPlantsConfig(
    snapshotDateTime: DateTime,
    powerPlantConfigSeq: Seq[PowerPlantConfig]
  )
}

Here is my JSON reads and writes:

implicit val powerPlantCfgFormat: Format[PowerPlantConfig] = new Format[PowerPlantConfig] {
    def reads(json: JsValue): JsResult[PowerPlantConfig] = {
      val powerPlantTyp = PowerPlantType.fromString((json \ "powerPlantType").as[String])
      powerPlantTyp match {
        case PowerPlantType.OnOffType =>
         JsSuccess(OnOffTypeConfig(
            id = (json \ "powerPlantId").as[Int],
            name = (json \ "powerPlantName").as[String],
            minPower = (json \ "minPower").as[Double],
            maxPower = (json \ "maxPower").as[Double],
            powerPlantType = powerPlantTyp
          ))
        case PowerPlantType.RampUpType =>
          JsSuccess(RampUpTypeConfig(
            id = (json \ "powerPlantId").as[Int],
            name = (json \ "powerPlantName").as[String],
            minPower = (json \ "minPower").as[Double],
            rampPowerRate = (json \ "rampPowerRate").as[Double],
            rampRateInSeconds = Duration.apply((json \ "rampRateInSeconds").as[String]).asInstanceOf[FiniteDuration],
            maxPower = (json \ "maxPower").as[Double],
            powerPlantType = powerPlantTyp
          ))
        case _ =>
          JsSuccess(UnknownConfig(
            id = (json \ "powerPlantId").as[Int],
            name = (json \ "powerPlantName").as[String],
            minPower = (json \ "minPower").as[Double],
            maxPower = (json \ "maxPower").as[Double],
            powerPlantType = powerPlantTyp
          ))
      }
    }

    def writes(o: PowerPlantConfig): JsValue = {
      if (o.powerPlantType == RampUpType) {
        Json.obj(
          "powerPlantId" -> o.id,
          "powerPlantName" -> o.name,
          "minPower" -> o.minPower,
          "maxPower" -> o.maxPower,
          "rampPowerRate" -> o.asInstanceOf[RampUpTypeConfig].rampPowerRate,
          "rampRateInSeconds" -> o.asInstanceOf[RampUpTypeConfig].rampRateInSeconds.toString(),
          "powerPlantType" -> PowerPlantType.toString(o.powerPlantType)
        )
      }
      else {
        Json.obj(
          "powerPlantId" -> o.id,
          "powerPlantName" -> o.name,
          "minPower" -> o.minPower,
          "maxPower" -> o.maxPower,
          "powerPlantType" -> PowerPlantType.toString(o.powerPlantType)
        )
      }
    }
  }

解决方案

According your stacktrace (line I marked)

JsResultException(errors:List((,List(ValidationError(List(error.expected.jsnumber),WrappedArray())))))
play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(List(error.expected.jsnumber),WrappedArray())))))
at play.api.libs.json.JsReadable$$anonfun$2.apply(JsReadable.scala:23)
at play.api.libs.json.JsReadable$$anonfun$2.apply(JsReadable.scala:23)
at play.api.libs.json.JsResult$class.fold(JsResult.scala:73)
at play.api.libs.json.JsError.fold(JsResult.scala:13)
--> at play.api.libs.json.JsReadable$class.as(JsReadable.scala:21)
at play.api.libs.json.JsDefined.as(JsLookup.scala:132)
at com.inland24.plantsim.models.package$$anon$1.reads(package.scala:61)
at play.api.libs.json.JsValue$class.validate(JsValue.scala:18)
at play.api.libs.json.JsObject.validate(JsValue.scala:76)
at com.inland24.plantsim.controllers.PowerPlantController$$anonfun$updatePowerPlant1$1.apply(PowerPlantController.scala:64)
at com.inland24.plantsim.controllers.PowerPlantController$$anonfun$updatePowerPlant1$1.apply(PowerPlantController.scala:63)

you used as[Int] in your Read format for id field for PowerPlantConfig. When you call as[Int], you are trying to force the given json path to type Int. It throws an exception if it cannot (as in your test). You can read on difference betweem as, asOpt and validate here for example

Update

If you look into implementation of as, asOpt and validate you will see that all these three do at first the same thing, but than differs in a way:

validate - I do need result either result or info on failure wrapped (just call reads of implicit arg on json)

asOpt - I need either result or none, if reads under used for resolution return parse error it is ignored as not set at all

as - I need either result, or exception. In other words "I'm sure, that this is always such type, if not, than it is general error"

Both as and asOpt are "extended validate" with interpreting result.

Example

Example how to move from as to validate in hierarchy (two Formats - one as yours with as which will throw exception, and another with validate which will not throw exception):

sealed trait PowerPlantConfig {
  def id: Int
}
case class RampUpTypeConfig(id: Int) extends PowerPlantConfig

implicit val powerPlantCfgFormat: Format[PowerPlantConfig] = new Format[PowerPlantConfig] {
  def reads(json: JsValue): JsResult[PowerPlantConfig] = {
    JsSuccess(RampUpTypeConfig(
      id = (json \ "powerPlantId").as[Int]
    ))
  }
  def writes(o: PowerPlantConfig): JsValue = {
    Json.obj(
      "powerPlantId" -> o.id)
  }
}
val powerPlantCfgFormatFixed: Format[PowerPlantConfig] = new Format[PowerPlantConfig] {
  def reads(json: JsValue): JsResult[PowerPlantConfig] = {
    for {
      id <- (json \ "powerPlantId").validate[Int]
    } yield {
      RampUpTypeConfig(
        id = id
      )
    }

  }
  def writes(o: PowerPlantConfig): JsValue = {
    Json.obj(
      "id" -> o.id)
  }
}


Json.parse("""{"powerPlantId":"123"}""").validate[PowerPlantConfig](powerPlantCfgFormatFixed)

And output will be not an exception but JsFailure as expected

res1: play.api.libs.json.JsResult[PowerPlantConfig] = JsError(List((,List(ValidationError(error.expected.jsnumber,WrappedArray())))))

这篇关于解析JSON正文时播放框架奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆