k0pernikus
k0pernikus

Reputation: 66609

How to make manifest available for a generic type in scala?

Given a scala http client being parsed through json4s, I had the following working code to transform a Future of a HttpResponse into a Future of a case class:

  def getJobIds(): Future[Seq[Job]] = {
    ... 
    var req = HttpRequest(
      method = HttpMethods.GET,
      uri = uri
    )

    val res: Future[HttpResponse] = Http().singleRequest(req)

    res.flatMap {
      case response@HttpResponse(StatusCodes.OK, _, _, _) => {
        val buffer = ByteString("")

        response.entity.dataBytes
          .runFold(buffer)(_ ++ _)
          .map(_.utf8String)
          .map(
            (body: String) => {
              parse(body).as[Seq[Job]]
            }
          )
      }
      case _: Any => throw new Throwable("Error!")
    }
  }

Now I wanted to extract the logic to transform the HttpResponse to a case classes using generics in order to not repeat myself for other calls:

  def getJobIds(): Future[Seq[Job]] = {
    ...
    val res: Future[HttpResponse] = Http().singleRequest(req)
    transformResponse[Seq[Job]](res)
  }

  private def transformResponse[T](r: Future[HttpResponse]): Future[T] = {
    r.flatMap {
      case response@HttpResponse(StatusCodes.OK, _, _, _) => {
        val buffer = ByteString("")

        response.entity.dataBytes
          .runFold(buffer)(_ ++ _)
          .map(_.utf8String)
          .map(parse(_).extract[T])
      }
      case _: Any => throw new Throwable("Error!")
    }
  }

Yet this now throws:

 Metronome.scala:46:32: No Manifest available for T

for the line

.map(parse(_).extract[T])

How do I make the code work with the use of generics?

Upvotes: 1

Views: 1067

Answers (1)

k0pernikus
k0pernikus

Reputation: 66609

The issues comes from json4s extract method.

If you look it up you'll find that its defintion is:

  def extract[A](implicit formats: Formats, mf: scala.reflect.Manifest[A]): A =
    Extraction.extract(jv)(formats, mf)

For some reason, the implicit Manifest look-up works for the concrete type, yet for the generic case you'll have to define the implicit manifest for your T type:

  private def transformResponse[T](r: Future[HttpResponse])
                                  (implicit ev: scala.reflect.Manifest[T]): Future[T] = {
    r.flatMap {
      case response@HttpResponse(StatusCodes.OK, _, _, _) => {
        val buffer = ByteString("")

        response.entity.dataBytes
          .runFold(buffer)(_ ++ _)
          .map(_.utf8String)
          .map(parse(_).extract[T])
      }
      case _: Any => throw new Throwable("Error!")
    }

Now your code will compile and run.

Upvotes: 4

Related Questions