scalaplayframeworkplay-json

How to format generic type as json using play json api?


I have a function which take a json string, parse it, fetch some values based on the condition and convert JsValue of different types:

trait Module
case class Module1(dp: String, qu: String, us: String) extends Module
case class Module2(dp: String, qu: String) extends Module
case class Module3(dp: String) extends Module
case class EmptyModule() extends Module
def getModule: (String, String) => String = (str1: String, str2: String) => {
  modJsonString = if(str1.nonEmpty && str2.nonEmpty){
    val modObject = Json.parse(str).as[JsObject]
    val dp = (modObject \ "dp").as[String]
    val qu = (modObject \ "qu").as[String]
    val us = (modObject \ "us").as[String]
    Module1(dp,qu,us)
  } else if(str1.nonEmpty){
    val dp = (modObject \ "dp").as[String]
    val qu = (modObject \ "qu").as[String]
    Module2(dp,qu)
  } else if(str2.nonEmpty){
    val dp = (modObject \ "dp").as[String]
    Module2(dp)
  }else EmptyModule()
  implicit val mod = Json.format[Module]
  val jsonValue = Json.toJson(modJsonString)
  jsonValue.toString()
}

While executing this function, I am getting error:

No apply function found for Module
implicit val mod = Json.format[Module]

Can anyone pls help to format a generic class or trait.


Solution

  • There are several ways to implement a Writes for a trait (I'm not talking about Reads as your example only needs a serializer).

    The "most usual" is to implement one for each possible subtype and implement one for the trait that reuses the ones of each subtype.

    Something like:

    implicit val writes1: Writes[Module1] = ...
    implicit val writes2: Writes[Module2] = ...
    ...
    
    implicit val writes: Writes[Module] = Writes {
      case m: Module1 => writes1.apply(m)
      case m: Module2 => writes2.apply(m)
      ...
    }
    

    Depending on whether you'll need the serializers for the subtypes on their own they can be kept as is or made private or even inlined in the serializer of the trait.

    I believe there are other ways, including some libraries that may generate all of this for you but I won't cover this here as I don't know them much.


    That being said, your whole code could probably be simplified a lot.

    Your original code doesn't compile and there are some unknowns (where I left ???) but it could look like the following:

    
    implicit val format1: OFormat[Module1] = Json.format[Module1]
    implicit val format2: OFormat[Module2] = Json.format[Module2]
    ...
    
    def getModule: (String, String) => String = (str1: String, str2: String) => {
      val modJsonString = if(str1.nonEmpty && str2.nonEmpty){
        Json.parse(???).as[Module1]
      } else if(str1.nonEmpty){
        Json.parse(str1).as[Module2]
      } else if(str2.nonEmpty){
        Json.parse(str2).as[Module3]
      }else EmptyModule()
      
      Json.toJson(modJsonString).toString()
    }