1

I need help to parse a json string into scala class using playJson

I wrote a formatter but I don't know how to handle the nested arrays.

Where Document Case class is

case class Document(content: String, score: Double, size: Int, path:String)

and formatter

implicit val similarHashFormatter: Format[SimilarHash] = (
  ((__ \ "hits" \ "hits" \\ "fields")(0) \ "content_hash")(0).format[String] and
  (__ \ "hits"  \ "hits" \\ "_score").format[Double] and
  ((__ \ "hits" \ "hits" \\  "fields")(0) \ "ast_size")(0).format[Int] and
  ((__ \ "hits" \ "hits" \\ "fields")(0) \ "path")(0).format[String]
) (SimilarHash.apply, unlift(SimilarHash.unapply))

This is my source json

{
  "hits": {
    "hits": [
      {
        "score": 1.5204661,
        "fields": {
          "size": [
            557645
          ],
          "path": [
            "/user/ubuntu/app
          ],
          "content": [
            "images"
          ]
        }
      },
      {        
        "score": 1.5199462,
        "fields": {
          "size": [
            556835
          ],
          "path": [
            "/user/ubuntu/app
          ],
          "content": [
            "documents"
          ]
        }
      }
    ]
  }
}

Any idea ?

2
  • what does your scala class definition look like? most times it's easiest to use the Json.format macro on your case class to handle more complex models. Commented Feb 10, 2017 at 21:39
  • @josephpconley I just added the Document case class, any example how to use Json.format macro ? Commented Feb 10, 2017 at 21:45

2 Answers 2

1

I figured out the solution based on oblivion's comment but without creating multiple reads.

  implicit val docReader: Reads[Document] = (
        (__ \ "fields" \ "content")(0).read[String] and
        (__  \ "_score").read[Double] and
        ((__ \  "fields") \ "size")(0).read[Int] and
        ((__ \ "fields") \ "path")(0).read[String]
      ) (Document.apply _)


  implicit val docsReader: Reads[Documents] = (
      (__ \ "hits" \ "max_score").read[Double] and
      (__ \ "hits" \ "hits").read[Seq[Document]]
    ) (Documents.apply _)

... and finally

val response = Json.parse(inputStream).asOpt[Documents]
Sign up to request clarification or add additional context in comments.

Comments

0

You could do this by creating custom Reads separately for each field as follow:

  import play.api.libs.json._
  import play.api.libs.functional.syntax._

  case class Document(content: String, score: Double, size: Int, path:String)

  val jsonString = """{
                         "hits": {
                             "hits": [
                                 {
                                     "score": 1.5204661,
                                     "fields": {
                                         "size": [
                                             557645
                                         ],
                                         "path": [
                                             "/user/ubuntu/app"
                                         ],
                                         "content": [
                                             "images"
                                         ]
                                     }
                                 },
                                 {
                                     "score": 1.5199462,
                                     "fields": {
                                         "size": [
                                             556835
                                         ],
                                         "path": [
                                             "/user/ubuntu/app"
                                         ],
                                         "content": [
                                             "documents"
                                         ]
                                     }
                                 }
                             ]
                         }
                     }"""

  val playJson = Json.parse(jsonString)

  val contentReads = new Reads[String] {
    override def reads(json: JsValue): JsResult[String] = json \ "hits" match {
      case JsDefined(o: JsObject) =>
        o \ "hits" match {
          case JsDefined(arr: JsArray) =>
            arr.value.head \ "fields" match {
              case JsDefined(fieldObj: JsObject) =>
                fieldObj \ "content" match {
                  case JsDefined(contentArr: JsArray) =>
                    JsSuccess(Json.stringify(contentArr.value.head))
                  case _ => JsError( """Can't read hits \ hits \ fields \ content""")
                }
              case _ => JsError( """Can't read hits \ hits \ fields""")
              case _ => JsError( """Can't read hits \ hits""")
            }
          case _ => JsError("Can't read hits")
        }
    }
  }

  val sizeReads = new Reads[Int] {
    override def reads(json: JsValue): JsResult[Int] = json \ "hits" match {
      case JsDefined(o: JsObject) =>
        o \ "hits" match {
          case JsDefined(arr: JsArray) =>
            arr.value.head \ "fields" match {
              case JsDefined(fieldObj: JsObject) =>
                fieldObj \ "size" match {
                  case JsDefined(contentArr: JsArray) =>
                    JsSuccess(Json.stringify(contentArr.value.head).toInt)
                  case _ =>  JsError("""Can't read hits \ hits \ fields \ size""")
                }
              case _ => JsError("""Can't read hits \ hits \ fields""")
            }
          case _ => JsError("""Can't read hits \ hits""")
        }
      case _ => JsError("Can't read hits")
    }
  }

  val scoreReads = new Reads[Double] {
    override def reads(json: JsValue): JsResult[Double] = json \ "hits" match {
      case JsDefined(o: JsObject) =>
        o \ "hits" match {
          case JsDefined(arr: JsArray) =>
            arr.value.head \ "score" match {
              case JsDefined(score: JsValue) =>
                JsSuccess(Json.stringify(score).toDouble)
              case _ =>  JsError("""Can't read hits \ hits \ score""")
            }
          case _ => JsError("""Can't read hits \ hits""")
        }
      case _ => JsError("Can't read hits")
    }
  }

  val pathReads = new Reads[String] {
    override def reads(json: JsValue): JsResult[String] = json \ "hits" match {
      case JsDefined(o: JsObject) =>
        o \ "hits" match {
          case JsDefined(arr: JsArray) =>
            arr.value.head \ "fields" match {
              case JsDefined(fieldObj: JsObject) =>
                fieldObj \ "path" match {
                  case JsDefined(contentArr: JsArray) =>
                    JsSuccess(Json.stringify(contentArr.value.head))
                  case _ =>  JsError("""Can't read hits \ hits \ fields \ path""")
                }
              case _ => JsError("""Can't read hits \ hits \ fields""")
            }
          case _ => JsError("""Can't read hits \ hits""")
        }
      case _ => JsError("Can't read hits")
    }
  }

  implicit val documentReads: Reads[Document] = (
  contentReads and
  scoreReads and
  sizeReads and
  pathReads
  )(Document.apply _)

  val document = playJson.validate[Document].get
//result: Document("images",1.5204661,557645,"/user/ubuntu/app")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.