3

I am have this json object

val jsonObject = """
 {
  "name" : "camara",
  "project" : {
    "key" : "DOC",
    "name" : "Dockerfiles"
  },
  "cloneUrl" : "https://server/scm/doc/camara.git",
  "links" : {
    "clone" : [ {
      "href" : "https://server/scm/doc/camara.git",
      "name" : "http"
    }, {
      "href" : "ssh://git@server:7999/doc/camara.git",
      "name" : "ssh"
    } ],
    "self" : [ {
      "href" : "url1"
    },
    {
      "href" : "url2"
    } ]
  }
}
"""

And with this case class and Reader:

case class Project(name: String, project: String, projectUrl: List[String])

implicit val projectReader: Reads[Project] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "project" \ "name").read[String] and
  (JsPath \ "links" \ "self" \\ "href").read[List[String]])(Project.apply _)

I try to parse to this model:

Json.parse(jsonObject).validate[Project] match {
  case value: JsSuccess[Project] =>
     println(" >> " + value.get)
  case error: JsError =>
    println(error)
}

The I get this error

JsError(List((/links/self//href,List(ValidationError(error.path.result.multiple,WrappedArray())))))

I have no idea how to extract those hrefs from the self array into the Project class to look like this:

Project(camara,Dockerfiles,List(url1, url2))

I have looked everywhere, on the internet for a simple example that would help me on the right track, but I haven't managed to find anything that helps.

How can I solve this issue without changing my Project class's structure ?

2
  • Are you willing to change your case class structure or are you insisting on keeping it the way it is? If you are willing to change it, I can help :) Commented Aug 6, 2015 at 14:35
  • It is clear how to solve this problem with an extra class. But one should dislike the idea of being forced to remodel because of the API's limits. Commented Aug 6, 2015 at 15:48

2 Answers 2

3
  import play.api.libs.json._
  import play.api.libs.functional.syntax._
  val jsonObject = """
  {
    "name" : "camara",
    "project" : {
      "key" : "DOC",
      "name" : "Dockerfiles"
    },
    "cloneUrl" : "https://server/scm/doc/camara.git",
    "links" : {
      "clone" : [ {
        "href" : "https://server/scm/doc/camara.git",
        "name" : "http"
      }, {
        "href" : "ssh://git@server:7999/doc/camara.git",
        "name" : "ssh"
      } ],
      "self" : [ {
        "href" : "url1"
      },
      {
        "href" : "url2"
      } ]
    }
  }
  """
  case class Project(name: String, project: String, projectUrl: List[String])

  def multiUrls[T](implicit rt: Reads[T]) = Reads[List[T]] { js =>
    val l: List[JsValue] = (__ \ "links" \ "self" \\ "href")(js)
    Json.fromJson[List[T]](JsArray(l))
  }

implicit val projectReader: Reads[Project] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "project" \ "name").read[String] and
  multiUrls[String])(Project.apply _)

  Json.parse(jsonObject).validate[Project] match {
    case value: JsSuccess[Project] =>
       " >> " + value.get
    case error: JsError =>
      error.toString
  }
  // >> Project(camara,Dockerfiles,List(url1, url2))
Sign up to request clarification or add additional context in comments.

Comments

3

In your case the JSON object's structure does not map 1:1 onto the case class: Below "self" there is another object (with the "href" property), but this level is missing in your case class.

So you could modify your JSON data or the case class.

If you want to preserve both, you can parse into a "parse only" case class, and convert this one into the "target" case class after parsing:

  case class Url(href: String)

  case class ParseProject0(
    name: String,
    project: String,
    projectUrl: List[Url]) {

    def toProject: Project = {
      Project(name, project, projectUrl.map(_.href))
    }
  }

  implicit val urlReader: Reads[Url] = (
    (JsPath \ "href").read[String].map(v => Url(v)))

  implicit val projectReader: Reads[ParseProject] = (
    (JsPath \ "name").read[String] and
      (JsPath \ "project" \ "name").read[String] and
      (JsPath \ "links" \ "self").read[List[Url]])(ParseProject.apply _)

  val parsed = Json.parse(jsonObject)
  val result = parsed.validate[ParseProject] match {
    case value: JsSuccess[ParseProject] =>
      val p = value.get.toProject
      p

    case error: JsError =>
      error
  }

Anyway, looking at your "clone" property in your JSON object, it seems that you need that additional class Url anyway, as that object contains more than one property.

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.