Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read a json response from a akka-http response entity using json4s

I am trying to invoke the google geocoding api and retrieve the response.

lazy val geoCodingConnectionFlow: Flow[HttpRequest, HttpResponse, Any] =
  Http().outgoingConnectionHttps(config.getString("services.geoCodingApiHost"), config.getInt("services.geoCodingApiPort"))

def geoCodingRequest(request: HttpRequest): Future[HttpResponse] = Source.single(request).via(geoCodingConnectionFlow).runWith(Sink.head)
/**
 * This call to google service is limited
 * @see https://developers.google.com/maps/documentation/geocoding/#Limits
 */
def ?(l: GeoLocation)(implicit ec: ExecutionContext): Future[Either[String, List[Result]]] = {
  val latlang = s"17.3644264,78.3896741"
  import org.json4s.native.Serialization
  import org.json4s.NoTypeHints
  import akka.http.scaladsl.model._
  import akka.http.scaladsl.unmarshalling._
  implicit val materializer = ActorMaterializer()
  implicit val executor = system.dispatcher
  implicit val formats = Serialization.formats(NoTypeHints)
  geoCodingRequest(RequestBuilding.Get(s"${config.getString("services.geoCodingApiUrlPart")}?latlng=$latlang&key=${config.getString("services.geoCodingApiKey")}")).flatMap { response =>
    val nonBinaryType = ContentTypes.`application/json`
    def responseEntity: HttpEntity = response.entity
    response.status match {
      case OK if (response.entity.contentType == ContentTypes.`application/json`) => Unmarshal(response.entity).to[List[Result]].map(Right(_)) 
      case BadRequest => Future.successful(Left(s"$latlang: incorrect Latitude and Longitude format"))
      case _ => Unmarshal(response.entity).to[String].flatMap { entity =>
        val error = s"Google GeoCoding request failed with status code ${response.status} and entity $entity"
        Future.failed(new IOException(error))
      }
    }
  }
}

}

I am getting the following compilation error when trying to execute this!

Service.scala:78: could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.Unmarshaller[akka.http.scaladsl.model.ResponseEntity,List[com.thenewmotion.geocode.Result]]
          case OK if(response.entity.contentType ==  ContentTypes.`application/json`)=> Unmarshal(response.entity).to[List[Result]].map(Right(_))

Please help me to get the result parsed into the following Result case classes:

package com.thenewmotion.geocode

case class Address(
  long_name: String,
  short_name: String,
  types: List[String]
)

case class Result(
  address_components: List[Address],
  formatted_address: String,
  types: List[String]
)

case class Response(
  results: List[Result],
  status: Status
) {
  def allResults = status match {
    case Ok => Right(results)
    case e: Error => Left(e)
  }
}

/** @see https://developers.google.com/maps/documentation/geocoding/#StatusCodes */
sealed trait Status

case object Ok extends Status

sealed trait Error extends Status
case object ZeroResults    extends Error
case object OverQuotaLimit extends Error
case object Denied         extends Error
case object InvalidRequest extends Error
case class OtherError(description: String) extends Error

                                                                                                                     ^
like image 287
Phani Avatar asked Mar 13 '23 05:03

Phani


1 Answers

As said in your error message, you need to provide an implicit Unmarshaller[akka.http.scaladsl.model.ResponseEntity,List[com.thenewmotion.geocode.Result]] otherwise the framework won't know how to convert the response entity into your model List[com.thenewmotion.geocode.Result].

Alternatively, you can use the built in unmarshaller to convert the entity to String first, then use spray-json to parse the json string into the target model:

import akka.http.scaladsl.unmarshalling.Unmarshal
import spray.json._

implicit val modelJsonReader = new JsonReader[List[com.thenewmotion.geocode.Result]] {
// See https://github.com/spray/spray-json on how to implement JsonReader
}

def parseJson(str: String): List[com.thenewmotion.geocode.Result] = {
  // parse using spray-json
  str.parseJson.convertTo[List[com.thenewmotion.geocode.Result]]
}

response.status match {
  case OK if (response.entity.contentType == ContentTypes.`application/json`) =>
    Unmarshal(response.entity).to[String].map { jsonString =>
      Right(parseJson(jsonString))
    }
  case BadRequest => Future.successful(Left(s"$latlang: incorrect Latitude and Longitude format"))
  case _ => Unmarshal(response.entity).to[String].flatMap { entity =>
    val error = s"Google GeoCoding request failed with status code ${response.status} and entity $entity"
    Future.failed(new IOException(error))
  }
}
like image 103
PH88 Avatar answered Mar 16 '23 03:03

PH88