I am sending this json to my scala code but I am getting error `` when the json is getting parsed.
{"practice-question":{"title":"dummy title","description":"<p>some D</p><p><strong>with bold</strong></p><ol><li><strong>no 1</strong></li></ol>","tags":["javascript"],"references":["dummy references"],"hints":["<p><u>some hint</u></p>"],"image":["data:image/png;base64,iVBORw0],"answer":[{"filename":"c.js","answer":"function c(){\n c;\n}"}],"success-test":"dummy success test","fail-test":"dummy fail test"}}))
The json validation starts at
val questionOption = jsonBody.validateOpt[Question] //should give JsSuccess or JsError
It should call the following Reads
implicit val questionReads:Reads[Question] = (JsPath \ "practice-question").read[PracticeQuestion](PracticeQuestionReads)
.map((x:PracticeQuestion)=>Question.apply (x))
The Reads for PracticeQuestion is
implicit object PracticeQuestionReads extends Reads[PracticeQuestion] {
def reads(json:JsValue):JsResult[PracticeQuestion] = {
println(s"validating json ${json}")
val structurallyValidated = json.validateOpt[PracticeQuestion](practiceQuestionValidateStructureReads) //check that structurally the json maps to PracticeQuestion
println(s"structurally validated ${structurallyValidated}")
val structuralValidationResult = structurallyValidated match {
case JsSuccess(questionOpt,path)=>{
val result = questionOpt.map(question=>{
//TODOM - should check size of other lists (hints, references etc.)
if(question.image.length <0 || question.image.length > 3)
{
JsError("invalid no. of images")
}
else {
JsSuccess(question)
}
}).getOrElse(JsError("Error in validating Json"))
result
}
case JsError(errors) =>{
//TODOM - replace print with logger.
println("errors in json structure: "+errors)
JsError(errors)
}
}
structuralValidationResult match {
case JsSuccess(question,path)=>{
//TODOM - check that for other mandatory fields (esp in Lists)are not empty
if(question.answer.length == 0){
JsError("Missing Answer")
} else {
JsSuccess(question);
}
}
case JsError(errors) =>{
JsError(errors)
}
}
}
}
and Reads used in PracticeQuestionReads is
implicit val practiceQuestionValidateStructureReads: Reads[PracticeQuestion] = {
println("in conversion");
val p = //p is of type FunctionBuilder[Reads]#CanBuild. This is an intermediary object used to create Reads[PracticeQuestion].
(JsPath \ "question-id").readNullable[UUID] and
(JsPath \ "description").read[String] and
(JsPath \ "hints").read[List[String]] and
(JsPath \ "image").read[List[String]] and //while image data is optional, this field is always present i.e. will get at least image:[]
(JsPath \ "success-test").read[String] and
(JsPath \ "fail-test").read[String] and
(JsPath \ "tags").read[Set[String]] and
(JsPath \ "title").read[String] and
(JsPath \ "answer").read[List[AnswerSection]] and
(JsPath \ "references").read[List[String]] and
(JsPath \ "question-creator").readNullable[QuestionCreator] and
(JsPath \ "creation-year").readNullable[Long] and //year/month are for internal consumption so not worried about receiving it from a client
(JsPath \ "creation-month").readNullable[Long] and
(JsPath \ "creation-hour").readNullable[Long] and
(JsPath \ "creation-minute").readNullable[Long]
//apply method of CanBuildX with a function to translate individual values to your model, this will return your complex Reads
val q = p.apply((PracticeQuestion.apply _))
q
}
To me the structure etc. look ok. What am I doing wrong?
UPDATE, after two months, the error occurred but for a different. I am running a test and am sending a json from the test. The error is
Uninitialized field: C:\Users\manuc\Documents\manu\codingjedi\code_related\code\frontend\web\app\controllers\AnswerController.scala: 34
scala.UninitializedFieldError: Uninitialized field: C:\Users\manuc\Documents\manu\codingjedi\code_related\code\frontend\web\app\controllers\AnswerController.scala: 34
at controllers.AnswerController.logger(AnswerController.scala:34)
at controllers.AnswerController.$anonfun$maxAllowedBodySize$1(AnswerController.scala:33)
at scala.runtime.java8.JFunction0$mcJ$sp.apply(JFunction0$mcJ$sp.java:12)
at scala.Option.getOrElse(Option.scala:121)
at controllers.AnswerController.<init>(AnswerController.scala:33)
at UnitSpecs.ControllerSpecs.AnswerControllerSpecTestEnv.<init>(AnswerControllerSpecTestEnv.scala:98)
at UnitSpecs.ControllerSpecs.AnswerControllerUnitSpec.$anonfun$new$2(AnswerControllerUnitSpec.scala:67)
at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85)
at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83)
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.withFixture(TestSuite.scala:196)
at org.scalatest.TestSuite.withFixture$(TestSuite.scala:195)
at org.scalatest.WordSpec.withFixture(WordSpec.scala:1881)
at org.scalatest.WordSpecLike.invokeWithFixture$1(WordSpecLike.scala:1076)
at org.scalatest.WordSpecLike.$anonfun$runTest$1(WordSpecLike.scala:1088)
at org.scalatest.SuperEngine.runTestImpl(Engine.scala:289)
at org.scalatest.WordSpecLike.runTest(WordSpecLike.scala:1088)
at org.scalatest.WordSpecLike.runTest$(WordSpecLike.scala:1070)
at UnitSpecs.ControllerSpecs.AnswerControllerUnitSpec.org$scalatest$BeforeAndAfterEach$$super$runTest(AnswerControllerUnitSpec.scala:32)
at org.scalatest.BeforeAndAfterEach.runTest(BeforeAndAfterEach.scala:221)
at org.scalatest.BeforeAndAfterEach.runTest$(BeforeAndAfterEach.scala:214)
at UnitSpecs.ControllerSpecs.AnswerControllerUnitSpec.runTest(AnswerControllerUnitSpec.scala:32)
at org.scalatest.WordSpecLike.$anonfun$runTests$1(WordSpecLike.scala:1147)
at org.scalatest.SuperEngine.$anonfun$runTestsInBranch$1(Engine.scala:396)
at scala.collection.immutable.List.foreach(List.scala:389)
at org.scalatest.SuperEngine.traverseSubNodes$1(Engine.scala:384)
at org.scalatest.SuperEngine.runTestsInBranch(Engine.scala:373)
at org.scalatest.SuperEngine.$anonfun$runTestsInBranch$1(Engine.scala:410)
at scala.collection.immutable.List.foreach(List.scala:389)
at org.scalatest.SuperEngine.traverseSubNodes$1(Engine.scala:384)
at org.scalatest.SuperEngine.runTestsInBranch(Engine.scala:379)
at org.scalatest.SuperEngine.runTestsImpl(Engine.scala:461)
at org.scalatest.WordSpecLike.runTests(WordSpecLike.scala:1147)
at org.scalatest.WordSpecLike.runTests$(WordSpecLike.scala:1146)
at org.scalatest.WordSpec.runTests(WordSpec.scala:1881)
at org.scalatest.Suite.run(Suite.scala:1147)
at org.scalatest.Suite.run$(Suite.scala:1129)
at org.scalatest.WordSpec.org$scalatest$WordSpecLike$$super$run(WordSpec.scala:1881)
at org.scalatest.WordSpecLike.$anonfun$run$1(WordSpecLike.scala:1192)
at org.scalatest.SuperEngine.runImpl(Engine.scala:521)
at org.scalatest.WordSpecLike.run(WordSpecLike.scala:1192)
at org.scalatest.WordSpecLike.run$(WordSpecLike.scala:1190)
at UnitSpecs.ControllerSpecs.AnswerControllerUnitSpec.org$scalatest$BeforeAndAfterAll$$super$run(AnswerControllerUnitSpec.scala:32)
at org.scalatest.BeforeAndAfterAll.liftedTree1$1(BeforeAndAfterAll.scala:213)
at org.scalatest.BeforeAndAfterAll.run(BeforeAndAfterAll.scala:210)
at org.scalatest.BeforeAndAfterAll.run$(BeforeAndAfterAll.scala:208)
at UnitSpecs.ControllerSpecs.AnswerControllerUnitSpec.org$scalatestplus$play$BaseOneAppPerSuite$$super$run(AnswerControllerUnitSpec.scala:32)
at org.scalatestplus.play.BaseOneAppPerSuite.run(BaseOneAppPerSuite.scala:46)
at org.scalatestplus.play.BaseOneAppPerSuite.run$(BaseOneAppPerSuite.scala:41)
at UnitSpecs.ControllerSpecs.AnswerControllerUnitSpec.run(AnswerControllerUnitSpec.scala:32)
at org.scalatest.tools.SuiteRunner.run(SuiteRunner.scala:45)
at org.scalatest.tools.Runner$.$anonfun$doRunRunRunDaDoRunRun$13(Runner.scala:1340)
at org.scalatest.tools.Runner$.$anonfun$doRunRunRunDaDoRunRun$13$adapted(Runner.scala:1334)
at scala.collection.immutable.List.foreach(List.scala:389)
at org.scalatest.tools.Runner$.doRunRunRunDaDoRunRun(Runner.scala:1334)
at org.scalatest.tools.Runner$.$anonfun$runOptionallyWithPassFailReporter$24(Runner.scala:1031)
at org.scalatest.tools.Runner$.$anonfun$runOptionallyWithPassFailReporter$24$adapted(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:133)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.main(ScalaTestRunner.java:27)
Process finished with exit code 0
The error seem to be for line val logger = LoggerFactory.getLogger(this.getClass.getName) which to be honest doesn't make sense.
Related
For this class
case class AnswerOfAPracticeQuestion (question_id: UUID,
image: Option[List[String]],
notes: Option[String],
answer:List[AnswerSection],
answer_id:Option[UUID],
answeredBy: Option[Answerer],
creationYear:Option[Long],
creationMonth:Option[Long],
){
}
the Reads method fails when I send this json to it
{
"new-answer":{
"images":[
"image 1",
"image 2"
],
"notes":"some notes",
"answer":[
{
"filename":"f1",
"answer":"a1"
},
{
"filename":"f2",
"answer":"a2"
}
],
"answer-id":"11111111-1111-1111-1111-111111111112",
"question-id":"11111111-1111-1111-1111-111111111111",
"creator-id":"11111111-1111-1111-1111-111111111113"
}
}
Reads method is
implicit val answerOfAPracticeQuestionReads: Reads[AnswerOfAPracticeQuestion] = (
(JsPath \ "question-id").read[UUID] and
(JsPath \ "images").readNullable[List[String]] and
(JsPath \ "notes").readNullable[String] and
(JsPath \ "answer").read[List[AnswerSection]] and //FAILS FOR THIS LINE
(JsPath \ "answer-id").readNullable[UUID] and
(JsPath \ "answerer").readNullable[Answerer] and
(JsPath \ "creation-year").readNullable[Long] and //year/month are for internal consumption so not worried about receiving it from a client
(JsPath \ "creation-month").readNullable[Long]
)(AnswerOfAPracticeQuestion.apply _)
Reads for AnswerSection is
implicit val answerSectionReads:Reads[AnswerSection] = (
(JsPath \ "filename").read[String] and
(JsPath \ "answer").read[String]
)(AnswerSection.apply _)
The error is
java.lang.ExceptionInInitializerError
at models.AnswerImplicits$.<init>(Answers.scala:92)
at models.AnswerImplicits$.<clinit>(Answers.scala)
at models.AnswerImplicits$AnswerAPIReads$.reads(Answers.scala:108)
at play.api.libs.json.ConstraintReads.$anonfun$optionWithNull$1(JsConstraints.scala:110)
at play.api.libs.json.Reads$$anon$8.reads(Reads.scala:132)
at play.api.libs.json.JsLookupResult.validateOpt(JsLookup.scala:164)
at play.api.libs.json.JsLookupResult.validateOpt$(JsLookup.scala:162)
at play.api.libs.json.JsDefined.validateOpt(JsLookup.scala:176)
at play.api.libs.json.JsValue.validateOpt(JsValue.scala:20)
at play.api.libs.json.JsValue.validateOpt$(JsValue.scala:19)
at play.api.libs.json.JsObject.validateOpt(JsValue.scala:124)
at controllers.AnswerController.$anonfun$newAnswer$1(AnswerController.scala:399)
at com.mohiva.play.silhouette.api.actions.SecuredActionBuilder.$anonfun$invokeBlock$8(SecuredAction.scala:216)
at com.mohiva.play.silhouette.api.actions.SecuredRequestHandlerBuilder.$anonfun$invokeBlock$2(SecuredAction.scala:93)
at com.mohiva.play.silhouette.api.RequestHandlerBuilder.handleInitializedAuthenticator(RequestHandler.scala:169)
at com.mohiva.play.silhouette.api.RequestHandlerBuilder.handleBlock(RequestHandler.scala:119)
at com.mohiva.play.silhouette.api.RequestHandlerBuilder.handleBlock$(RequestHandler.scala:117)
at com.mohiva.play.silhouette.api.actions.SecuredRequestHandlerBuilder.handleBlock(SecuredAction.scala:52)
at com.mohiva.play.silhouette.api.actions.SecuredRequestHandlerBuilder.$anonfun$invokeBlock$1(SecuredAction.scala:93)
at scala.concurrent.Future.$anonfun$flatMap$1(Future.scala:302)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:37)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at scala.concurrent.impl.ExecutionContextImpl$AdaptedForkJoinTask.exec(ExecutionContextImpl.scala:140)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.pollAndExecAll(ForkJoinPool.java:1021)
at java.util.concurrent.ForkJoinPool$WorkQueue.execLocalTasks(ForkJoinPool.java:1046)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1058)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: scala.UninitializedFieldError: Uninitialized field: C:\Users\manuc\Documents\manu\codingjedi\code_related\code\frontend\web\app\models\Answers.scala: 196
at models.AnswerImplicits$.answerOfAPracticeQuestionWrites(Answers.scala:196)
at models.PracticeQuestionImplicits$.<init>(PracticeQuestion.scala:139)
at models.PracticeQuestionImplicits$.<clinit>(PracticeQuestion.scala)
... 29 more
Futures timed out after [5000 milliseconds]
java.util.concurrent.TimeoutException: Futures timed out after [5000 milliseconds]
...
If I compare the implicit methods, in the pics below, you'll notice that the reads, there is an additional canBuildForm while there is nothing like it in writes
Reads
Writes
In the following code, if I use (), I don't need to explicitly call apply of CanBuilder
implicit val pReads:Reads[P] = (
(JsPath \ "id").readNullable[UUID] and
(JsPath \ "description").read[String] and
...
)(P.apply _)
But if I use {}, I have to call apply. Why?
implicit val pReads:Reads[P] = {
(JsPath \ "id").readNullable[UUID] and
(JsPath \ "description").read[String] and
...
}.apply((P.apply _))
I'm trying to set up a JSON response that I got from a weather API to fit in a model class I defined in order to use it easily, but I can't get to do it.
Here is the class :
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Forecast(var main: String, var description: String, var temp: Int, var tempMin: Int, var tempMax: Int)
object Forecast {
implicit val forecastJsonFormat: Reads[Forecast] = (
(JsPath \ "weather" \\"main").read[String] and
(JsPath \ "weather" \\"description").read[String] and
(JsPath \ "main" \\"temp").read[Int] and
(JsPath \ "main" \\"temp_min").read[Int] and
(JsPath \ "main" \\"temp_max").read[Int]
) (Forecast.apply _)
}
and this is the code in the controller :
def weather = Action.async {
futureResponse.map(response => {
val jsonString = response.json.toString()
val jsonObject = Json.parse(jsonString)
// TODO: Create t [Forecast] Object which represents the response.json data to send it to the view below
Ok(views.html.weather(t))
})}
example of the response.json I'am getting :
{"coord":{"lon":37.62,"lat":55.75},"weather":[{"id":600,"main":"Snow","description":"light snow","icon":"13n"},{"id":701,"main":"Mist","description":"mist","icon":"50n"}],"base":"stations","main":{"temp":269.15,"pressure":1024,"humidity":92,"temp_min":268.15,"temp_max":270.15},"visibility":3100,"wind":{"speed":2,"deg":200},"clouds":{"all":90},"dt":1546266600,"sys":{"type":1,"id":9029,"message":0.0029,"country":"RU","sunrise":1546235954,"sunset":1546261585},"id":524901,"name":"Moscow","cod":200}
You have to change main to Seq[String] and description to Seq[String] and temp, tempMin, tempMax to Double
I used a different way to create the reads here, but this way will throw an exception if the format is different than the expected format.
case class Forecast(main: Seq[String], description: Seq[String], temp: Double, tempMin: Double, tempMax: Double)
object Forecast {
val reads = new Reads[Forecast] {
override def reads(json: JsValue): JsResult[Forecast] = {
val main = (json \ "weather" \\ "main").map(_.as[String]).toList
val description = (json \ "weather" \\ "description").map(_.as[String]).toList
val temp = (json \ "main" \ "temp").as[Double]
val tempMin = (json \ "main" \ "temp_min").as[Double]
val tempMax = (json \ "main" \ "temp_max").as[Double]
JsSuccess(Forecast(main, description, temp, tempMin, tempMax))
}
}
}
or you can use the same way you are using, but parse the list in different way:
val forecastJsonFormat: Reads[Forecast] = (
(JsPath \ "weather").read[List[Map[String, JsValue]]].map(_.map(_("main").as[String])) and
(JsPath \ "weather").read[List[Map[String, JsValue]]].map(_.map(_("description").as[String])) and
(JsPath \ "main" \ "temp").read[Double] and
(JsPath \ "main" \ "temp_min").read[Double] and
(JsPath \ "main" \ "temp_max").read[Double]
) (Forecast.apply _)
I finally got to do it and here is how :
In the model I have my case class defined and a companion object that parses the JSON response I got from the web API to my class arguments
Model code :
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Forecast(main: String, description: String, temp: Double, tempMin: Double, tempMax: Double)
object Forecast {
implicit val forecastReads: Reads[Forecast] = (
(JsPath \ "weather" \\ "main").read[String] and
(JsPath \ "weather" \\ "description").read[String] and
(JsPath \ "main" \ "temp").read[Double] and
(JsPath \ "main" \ "temp_min").read[Double] and
(JsPath \ "main" \ "temp_max").read[Double]
) (Forecast.apply _)
}
In the controller code I added a pattern matching and here it is !
Controller code :
def weather = Action.async {
futureResponse.map(response => {
val parseResult = Json.fromJson[Forecast](response.json)
parseResult match {
case JsSuccess(forecast, JsPath) => Ok(views.html.weather(forecast))
case JsError(error) => InternalServerError("Something went wrong!!") // Note that I'm not sure this result exists in Play...
}
})
}
I get the "time" from mysql and need to render it to fe, but I get the "time" in the form of timestamp and I want it displayed as yyyy-MM-dd HH:mm:ss, how can I do it in the server code as follows?
object JDItem {
val jditem = {
get[Long]("id") ~
get[String]("name") ~
get[String]("url") ~
get[Double]("price") ~
get[Int]("commentnum") ~
get[Int]("likerate") ~
get[DateTime]("time") ~
get[String]("category") map {
case id ~ name ~ url ~price~
commentnum~likerate~time~category => JDItem(id,name,url,price,
commentnum,likerate,time,category)
}
}
implicit val JDItemWrites: Writes[JDItem] = (
(JsPath \ "id").write[Long] and
(JsPath \ "name").write[String] and
(JsPath \ "url").write[String] and
(JsPath \ "price").write[Double] and
(JsPath \ "commentnum").write[Int] and
(JsPath \ "likerate").write[Int] and
(JsPath \ "time").write[DateTime] and
(JsPath \ "category").write[String]
)(unlift(JDItem.unapply))
def getJson(category:String,sort:String,DescOrAsc:String):JsObject = DB.withConnection{ implicit c =>
val list = SQL("select id,name,url,price,commentnum,likerate,time,category from "+category+" order by "+sort+" "+DescOrAsc+" limit 100").as(jditem *)
val json:JsValue = Json.toJson(list)
val jsobject = Json.obj("total"-> list.length,"rows"-> json)
jsobject
}
}
Since version 2.3.8 of Anorm, Joda & Java8 temporal types are supported in result parser; see Anorm column: get[DateTime].
I need to serialize a String and an Option[Boolean]:
val myWrites = (
(__ \ "box").write(
(
(__ \ "name").write[String] ~
(__ \ "default").writeNullable[Boolean]
).tupled
)
)
If Option[Boolean] is Some then I'd expect
{
"box": {
"name": "John",
"default": true
}
}
... while if Option[Boolean] is None I'd expect
{
"box": {
"name": "John"
}
}
Given the following variables...
val name = "John"
val default = Some(true)
... how do I pass them to the Writes? I've tried this:
myWrites.writes(name, defaul)
... but it doesn't compile:
No Json serializer found for type play.api.libs.functional.FunctionalBuilder[play.api.libs.json.OWrites]#CanBuild2[String,Option[Boolean]].
Try to implement an implicit Writes or Format for this type.
[error] (__ \ "box").write(
I think its just a typo in your writes. you have defaul vs default
I was able to use
import play.api.libs.json._
import play.api.libs.functional.syntax._
val myWrites = (
(__ \ "box").write(
(
(__ \ "name").write[String] ~
(__ \ "default").writeNullable[Boolean]
).tupled
)
)
myWrites.writes("hi",Some(true))
and I got back
res0: play.api.libs.json.JsObject = {"box":{"name":"hi","default":true}}