Search code examples
jsonscalatraitsplay-json

Json body converted to sealed trait


I have a Play! endpoint which can receive a json body as 3 or 4 forms (I tried using generic type, but not working).

Controller:

def getChartData = Action.async(parse.json) { request =>
   // ERROR: can not cast JsValue to type ChartDataRequest (which is sealed trait)
   service.getChartData(request.body.asInstanceOf[ChartDataRequest]).map {
     data => Ok(Json.toJson(data))
   }.recover { 
     case _ => InternalServerErrror
   }
}

Service:

def getChartData(request: ChartDataRequest): Future[data_type] = {
  if (request.isInstanceOf[PieChartRequest]) {
    //
  } else if (request.isInstanceOf[BarChartRequest]) {
    //
  } else {
    //
  }
}

dtos:

sealed trait ChartDataRequest

final case class PieChartRequest(field1: String, field2: String, ....) 
   extends ChartDataRequest

final case class BarChartRequest(field1: String, field2: String, ....) 
   extends ChartDataRequest

I found here the solution to use sealed traits, but can't do it well.

In this point, I can not convert the JsValue to ChartDataRequest type. I can use a field "classType" in my json and then using the match pattern to create the specified object (PieDataRequest or BarDataRequest) but I think this is not the best solution.

Inside all my controller methods where I send objects as json body, I use the play validator, but have the same problem, and I removed it from code.

// ChartDataRequest can have PieDataRequest or BarDataRequest type
request.body.validate[ChartDataRequest] match {
    case JsSuccess(value, _) => // call the service
    case JsError(_) => Future(BadRequest("Invalid json body"))
 }

thanks


Solution

  • You can follow this:

            sealed trait ChartDataRequest
    
      final case class PieChartRequest(field1: String) extends ChartDataRequest
    
      final case class BarChartRequest(field2: String) extends ChartDataRequest
    
      final case object WrongData extends ChartDataRequest
    
      import play.api.libs.json._
      import play.api.libs.functional.syntax._
    
      implicit val ChartDataRequests: Reads[ChartDataRequest] = {
        val pc = Json.reads[PieChartRequest]
        val bc = Json.reads[BarChartRequest]
        __.read[PieChartRequest](pc).map(x => x: ChartDataRequest) |
          __.read[BarChartRequest](bc).map(x => x: ChartDataRequest)
    
      }
    
    
      def getChartData(request: ChartDataRequest) = {
    
        request match {
          case _: PieChartRequest =>
            Future("PieChartRequest")(defaultExecutionContext)
          case _: BarChartRequest =>
            Future("BarChartRequest")(defaultExecutionContext)
          case _ =>
            Future("WrongData")(defaultExecutionContext)
        }
      }
    
      def getChartDataAction = Action.async(parse.json) { request =>
        // you can separate this to a new function
        val doIt = request.body.asOpt[JsObject].fold[ChartDataRequest](
          WrongData
        ){
          jsObj =>
            jsObj.asOpt[ChartDataRequest].fold[ChartDataRequest](
             WrongData
            )(identity)
        }
    
        getChartData(doIt).map {
          data => Ok(Json.toJson(data))
        }(defaultExecutionContext).recover {
          case _ => InternalServerError
        }(defaultExecutionContext)
      }