Search code examples
scalaspray-json

Fail-safe wrapper for spary-json parseJson func


I want to write fail-safe wrapper for spary-json str.parseJson.convertTo[A]. It must have logic - "when I can't parse json as case class A, I try parse it as case class Error"

def parse(str:String) = 
   try {
     str.parseJson.convertTo[A]
   } catch {
     case e:Exception => str.parseJson.convertTo[Error]
   }

but also I want to make class A a parameter.

def parse[A<:Obj](str:String):Obj = {
  import JsonProtocols._
  try {
    str.parseJson.convertTo[A]
  } catch {
    case e:Exception => str.parseJson.convertTo[Error]
  }
}

using:

...
trait Obj

case class Error(error:String) extends Obj
case class DataA(a1:String, a2: Int) extends Obj
case class DataB(b1:String, b2: Boolean) extends Obj
object JsonProtocols extends DefaultJsonProtocol {
  implicit val errorFormat = jsonFormat1(Error)
  implicit val dataAFormat = jsonFormat2(DataA)
  implicit val dataBFormat = jsonFormat2(DataB)
  ...
}

...
parse[DataA]("...json...") match {
  case obj: DataA => "..."
  case obj: Error => "..."
}
...

I get compiling error:

Error:(25, 30) Cannot find JsonReader or JsonFormat type class for A
      str.parseJson.convertTo[A]

                         ^

How can I fix this error? Can I do this by another way?


Solution

  • Simplifying things, looks like that you've:

    • defined 3 case classes with appropriate JsonReaders
    • defined a generic function, which type is lower bound to Obj.

    The compiler tells you that it cannot find a JsonReader for all possible classes implementing trait Obj, because you have defined only specific JsonReaders for Error, DataA and DataB.

    To solve the problem you can use Either[T,Error] type for deserialization like:

      sealed trait Obj
      case class Error(error:String) extends Obj
      case class DataA(a1:String, a2: Int) extends Obj
      case class DataB(b1:String, b2: Boolean) extends Obj
    
      val strA = """{"a1":"foo", "a2": 1}"""
      val strB = """{"b1":"bar", "b2": false}"""
      val srtE = """{"error": "oops"}"""
    
      object JsonProtocols extends DefaultJsonProtocol {
        implicit val errorFormat = jsonFormat1(Error)
        implicit val dataAFormat = jsonFormat2(DataA)
        implicit val dataBFormat = jsonFormat2(DataB)
      }
    
      import JsonProtocols._
      val result:Obj = strA.parseJson.convertTo[Either[DataA,Error]] match {
        case Left(dataA) => dataA
        case Right(error) => error
      }