Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse json list or array in scala for play framework 2.2

I am writing some RESTful API test cases and have little experience working with the scala playframwork.

Here is an example of my JSON.

  [ {
   "size" : "5082",
   "date-created" : "Wed Nov 19 17:10:39 CST 2014",
   "id" : "546d236fb84e894eefd8f769",
   "content-type" : "image/png",
   "filename" : "chrome-on-windows.PNG"
 }, {
   "size" : "15684",
   "date-created" : "Mon Jan 12 17:28:02 CST 2015",
   "id" : "54b4588266b3d11b1c1e9db6",
   "content-type" : "image/png",
   "filename" : "logos_ncsa.png"
 }, {
   "size" : "1267871",
   "date-created" : "Mon Jan 12 17:28:03 CST 2015",
   "id" : "54b4588366b3d11b1c1e9dba",
   "content-type" : "image/jpg",
   "filename" : "morrowplots.jpg"
 } ]

As you can see I have a list/Array of JSON items. I want to grab the id for the "morrowplots.jpg" file and store that into a variable to be used for successful API calls.

So I setup my code to look like the following. The result variable in the code below is the JSON string you see above.

  case class FileName(size: String, datecreated: String, id: String,    contenttype: String, filename: String)

  implicit val fileReads: Reads[FileName] = (
  (__ \\ "size").read[String] and
  (__ \\ "datecreated").read[String] and
  (__ \\ "id").read[String] and
  (__ \\ "content-type").read[String] and
  (__ \\ "filename").read[String]
  )(FileName.apply _)

  val json: JsValue = Json.parse(contentAsString(result))

  val nameResult: JsResult[FileName] = json.validate[FileName](fileReads)
  info("Right after validate")
    nameResult match {
      case s: JsSuccess[FileName] => {
        val testfile: FileName = s.get
        // Do something with testfile
        info("Success")
      }
      case e: JsError => {
        info("Error")
        info("Errors: " + JsError.toFlatJson(e).toString())
      }
    }

This gives me the following error.

[info] + Errors: {"objsize":[{"msg":"error.path.result.multiple","args":[]}],"objfilename":[{"msg":"error.path.resul t.multiple","args":[]}],"objid":[{"msg":"error.path.result.multiple","args":[]}],"objcontent-type":[{"msg":"error.path .result.multiple","args":[]}],"obj*datecreated":[{"msg":"error.path.missing","args":[]}]}

So how do I fix this List/Array issue and how do I search by filename to get the id?

Thanks in advance.

like image 977
Eugene Roeder Avatar asked Dec 25 '22 23:12

Eugene Roeder


2 Answers

I am no expert on Play, so this might not be idiomatic however it should solve your problem. First, your json is date-created versus your scala expecting datecreated. Second, you should only use one slash for your Reads. Next, you need to run validate against a List[FileName].

As to the filename search, you can now extract the list from the JsSuccess and run a filter against it.

The final code will look something like this

case class FileName(size: String, datecreated: String, id: String, contenttype: String, filename: String)

  implicit val fileReads: Reads[FileName] = (
  (__ \ "size").read[String] and
  (__ \ "date-created").read[String] and
  (__ \ "id").read[String] and
  (__ \ "content-type").read[String] and
  (__ \ "filename").read[String]
  )(FileName)

  val json: JsValue = Json.parse(contentAsString(result))

  val nameResult = json.validate[List[FileName]]

  val list = nameResult match {
      case JsSuccess(list : List[FileName], _) => list
      case e: JsError => {
        info("Errors: " + JsError.toFlatJson(e).toString())
        List()
      }
    }

  list filter(_.filename contains "morrow")
like image 31
Justin Pihony Avatar answered Apr 27 '23 18:04

Justin Pihony


First, you may decide to use the built-in json utilities rather than perform manual parsing.

  case class FileName(size: String, datecreated: String, id: String,    contenttype: String, filename: String)
  object FileName {
    implicit val formatFileName = Json.format[FileName]
  }

There, you have all you need to parse a basic json object. The compiler will use a macro (IIRC) to generate a code equivalent to the one you wrote by hand. As you don't have exotic validation on your fields, manual writing of the Reads and Writes classes are not necessary.

Then you can read it this way :

    def readString(str: String) = {
      val jsr: JsResult[Seq[FileName]] = Json.parse(str).validate[Seq[FileName]]
      jsr.fold(
        error => {
          ???
        },
        success => {
          ???
        }
      )
    }

jsr is here a JsResult. It can either be a JsSuccess or a JsError.

Note the complete type, too. As you have an array as your input, you should put your output un a collection, a Seq for instance.

You can fold on your JsResult. fold expects two functions. One is for the error case, it has type Seq[(JsPath, Seq[ValidationError])] => X, where X is the return type of your function. It shows you all the problems that have prevented your json to be translated to a Seq[FileName].

The other one is for the success case. It has type Seq[FileName] => X, with the same X than before.

You can now decide what to put in these two functions.

As pointed out by Justin, you can write it with a match too. It may be easier, even if less functional :

    def readString(str: String) = {
      val jsr: JsResult[Seq[FileName]] = Json.parse(str).validate[Seq[FileName]]
      jsr match {
        case JsResult(seq) => ???
        case e: JsError => ???
      }
    } 
like image 124
Agemen Avatar answered Apr 27 '23 19:04

Agemen