How do I define a hierarchy for a generic json-spray parser?

247 Views Asked by At

We have an API that always has a total-count and entities in the response, but the entities are of different types. What I'm trying to do is make the parsing & converting to case-classes more generic.

So trying with the following types

case class StandardReturn[A](
    `total-count`: Double,
    entities: List[A]
)

case class System(
    id: String,
    name: String
)

And the following example:

object SystemProtocol extends DefaultJsonProtocol {
  implicit val systemFormat: RootJsonFormat[System] = 
    jsonFormat2(System)
  implicit def entityFormat[A: JsonFormat] =
    jsonFormat(StandardReturn.apply[A], "total-count", "entities")
}
import SystemProtocol._

val response = """{
  "total-count": 10,
  "entities": [
    { "id": "1", "name": "me" }
  ]
}"""

class Example {
  def transform[A: JsonReader](entityString: String) =
    entityString.parseJson
      .convertTo[A]
      .entities  // Where I'm running into trouble
}
object Example {
  val transformed = new Example().transform[StandardReturn[System]](response)
}
Example.transformed

Which is understandably giving me

Error:(34, 42) value entities is not a member of type parameter A
entityString.parseJson.convertTo[A].entities  // Where I'm running into trouble
                                    ^

How would I set up the case classes / types so that transform could be assured that entities will always exist after converting to type A (where A is StandardReturn[A])? I'm not too familiar with scala's type system, thank you for help.

1

There are 1 best solutions below

0
On BEST ANSWER

In your Code, the type parameter A has no bounds except the context bound to JsonReader (an implicit parameter of type JsonReader[A]). Therefore, as you already mentioned, A could be anything, so you can't call the entities method. If you convert to StandardReturn[A] instead of just A, this problem is resolved.

def transform[A: JsonReader](entityString: String) =
  entityString.parseJson
    .convertTo[StandardReturn[A]] <- just convert to what you actually want
    .entities

Additionally, you have to replace the type parameter in new Example().transform[StandardReturn[System]](response) by just System instead of StandardReturn[System], as the method above was changed.

The compiler now needs an implicit parameter of type JsonReader[StandardFormat[System]]. But in the implicit scope, there is only the systemFormat of type JsonReader[System] (in SystemProtocol). The compiler does not give up yet: he tries to find an implicit conversion from JsonReader[System] to JsonReader[StandardFormat[System]], and that is just the method entityFormat you defined. Yeah!

One last remark: You could further simplify the method entityFormat if you replace jsonFormat(StandardReturn.apply[A], "total-count", "entities") by jsonFormat2(StandardReturn.apply[A]), as noted in the documentation.