jsonscalalift

Extracting a case class with an upper bound


I want to extract a case class from a JSON String, and reuse the code for every class. Something like this question would have been perfect. But this means that I have to write for every class I want to extract.

I was hoping to do something like:

abstract class SocialMonitorParser[C <: SocialMonitorData] extends Serializable {

  def toJSON(socialMonitorData: C): String = {
    Requirements.notNull(socialMonitorData, "This field cannot be NULL!")
    implicit val formats = DefaultFormats
    write(socialMonitorData)
  }

  def fromJSON(json: String): Option[C] = {
    implicit val formats = DefaultFormats // Brings in default date formats etc.
    val jsonObj = liftweb.json.parse(json)

    try {
      val socialData = jsonObj.extract[C]
      Some(socialData)
    } catch {
      case e: Exception => {
        Logger.get(this.getClass.getName).warn("Unable to parse the following JSON:\n" + json + "\nException:\n" + e.toString())
        None
      }
    }
  }

}

But it gives me the following error:

Error:(43, 39) No Manifest available for C.
val socialData = jsonObj.extract[C]
Error:(43, 39) not enough arguments for method extract: (implicit formats: net.liftweb.json.Formats, implicit mf: scala.reflect.Manifest[C])C.
Unspecified value parameter mf.
val socialData = jsonObj.extract[C]

I was hoping I could do something like this, and maybe there is a way. But I can't wrap my head around this.

I will try to extend the question with some other information. Supposing I have Twitter and Facebook data, in case class like these:

case class FacebookData(raw_data: String, id: String, social: String) extends SocialMonitorData
case class TwitterData(...) extends SocialMonitorData{ ...}

I wish I could reuse the fromJSON and toJSON just once passing the Upper Bound type

class TwitterParser extends SocialMonitorParser[TwitterData] {

override FromJSON} class FacebookParser extends SocialMonitorParser[FacebookData]

Much obliged.


Solution

  • I'm not sure why you want SocialMonitorParser to be abstract or Serializable or how are you going to use it but if you look closer at the error you may see that the compilers wants a Manifest for C. Manifest is a Scala way to preserve type information through the type erasure enforced onto generics by JVM. And if you fix that, then the code like this compiles:

    import net.liftweb.json._
    import net.liftweb.json.Serialization._
    
    trait SocialMonitorData
    
    case class FacebookData(raw_data: String, id: String, social: String) extends SocialMonitorData
    
    
    class SocialMonitorParser[C <: SocialMonitorData : Manifest] extends Serializable {
    
      def toJSON(socialMonitorData: C): String = {
        // Requirements.notNull(socialMonitorData, "This field cannot be NULL!")
        implicit val formats = DefaultFormats
        write(socialMonitorData)
      }
    
      def fromJSON(json: String): Option[C] = {
        implicit val formats = DefaultFormats // Brings in default date formats etc.
        val jsonObj = parse(json)
    
        try {
          val socialData = jsonObj.extract[C]
          Some(socialData)
        } catch {
          case e: Exception => {
            // Logger.get(this.getClass.getName).warn("Unable to parse the following JSON:\n" + json + "\nException:\n" + e.toString())
            None
          }
        }
      }
    }
    

    and you can use it as

    def test(): Unit = {
      val parser = new SocialMonitorParser[FacebookData]
    
      val src = FacebookData("fb_raw_data", "fb_id", "fb_social")
      println(s"src = $src")
    
      val json = parser.toJSON(src)
      println(s"json = $json")
    
      val back = parser.fromJSON(json)
      println(s"back = $back")
    }
    

    to get the output exactly as one would expect.