jsonscalaplayframework-2.0coursera-api

Convert a document with Play Json


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" ...

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.