Question

I have a list of courses from coursera api:

{"elements":[
   {"id":69, 
    "shortName":"contraception",
    "name":"Contraception: Choices, Culture and Consequences",
    "links":{}
   },
   ...
 ]
 }

I want to convert it to a document that look like so ( i use <--- arrrows as comments):

{"Courses":[
   {
      "Type" : "Course",
      "Title" : "contraception"    <---- short name
      "Content" : {"id":69,        <---- the original course
            "shortName":"contraception",
             "name":"Contraception: Choices, Culture and Consequences",
             "links":{}
             }
   }, 
   ...
]}

Is it possible to perform this with json only api from play? Here is how I do it presently (with conversion to scala lists).

val courses = (response.json \ "elements")
      .as[List[JsValue]]
      .map { course =>
      // This is how we want our document to look
      Json.obj(
        "Type" -> "Course",
        "Provider" -> "Coursera",
        "Title" -> (course \ "name"),
        "Content" -> course
      )
    }
 // then put this into the final json object with "Courses" ...
Was it helpful?

Solution

This can be done with Json Transformers API:

It's more messy, but this is as close as I got:

val json = Json.parse( """
    { "elements":[
           {"id":69,
            "shortName":"contraception",
            "name":"Contraception: Choices, Culture and Consequences",
            "links":{}
           },
           {"id":100,
            "shortName":"test",
            "name":"Test of name",
            "links":{}
           }
        ]
     }
                       """)

// Maps single course to app course
val apiCourseToAppCourse = (
  (__ \ 'Type).json.put(JsString("Course")) and
    (__ \ 'Title).json.copyFrom((__ \ 'shortName).json.pick) and
    (__ \ 'Content).json.copyFrom(__.json.pickBranch)
  ).reduce

// Maps an array of elements to an array of courses
val apiCoursesToAppCourses = of[JsArray].map {
  case JsArray(arr) =>
    // Map elements to courses
    JsArray(arr.map {
      case course: JsObject =>
        apiCourseToAppCourse.reads(course).getOrElse(JsString("Failed to read the course"))
    })

}

// Maps the API result to a valid result
val apiToCourses = (__ \ 'Courses).json.copyFrom((__ \ 'elements).json.pick(apiCoursesToAppCourses))

// Validate JSON and send result
json.transform(apiToCourses) match {
  case JsSuccess(success, p) =>
    Ok(Json.toJson(success))
  case JsError(errors) =>
    Ok(errors + "")
}

You can copy code and put it in a Controller, I have it working. I don't like so much the array mapping but that's the way it's being done in the documentation example.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top