I'm trying to retrieve some data from a local server through akka library in Scala. Data are returned from server in JSON format, but I'm unable to unmarshall them in a custom type.
Custom class is Profiles, that contains a list of Profiles.
case class Profile(
Name: String,
Surname: String,
Mail: String,
Age: Int,
Town: String,
Role: String,
PrimaryInstr: String,
SecondaryInstr: String,
PrimaryGenre: String,
SecondaryGenre: String,
Influences: String,
RecordLabel: String,
GigAvailability: String,
RehearseAvailability: String,
RecordingExperience: String,
MusicalAge: Int)
case class Profiles(profiles: Vector[Profile])
I've tried with the following code to unmarshall to Profiles, but it does not compile because of the error
could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.Unmarshaller[ResponseEntity, Profiles]
import akka.http.scaladsl.Http
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.client.RequestBuilding.Get
import akka.http.scaladsl.model.HttpResponse
...
def getProfiles = {
var req = Get("http://localhost:9090/profiles")
val responseFuture: Future[HttpResponse] = Http().singleRequest(req)
responseFuture
.onComplete {
case Success(response) =>
println(response.entity)
//Here I want actually Unmarshall to Profiles, not to String
var responseAsString = Unmarshal(response.entity).to[String] //Tried here with Profiles
println(responseAsString)
case Failure(_) => sys.error("something wrong")
}
...
}
Unmarshalling with [String] the code produces this output (abbreviated with "...").
HttpEntity.Strict(application/json,[{"Name":"Amadeus","Surname":"Rapisarda", ..., "MusicalAge":9},{"Name":"Federico","Surname":"D'Ambrosio", ..., "MusicalAge":24}]) FulfilledFuture([{"Name":"Amadeus","Surname":"Rapisarda", ..., "MusicalAge":9},{"Name":"Federico","Surname":"D'Ambrosio", ..., "MusicalAge":24}])
How I could obtain a Profiles object? Thanks in advance!
I finally found a solution that works fine.
1 - Add these dependencies in the build.sbt file
val AkkaVersion = "2.6.9"
val AkkaHttpVersion = "10.2.0"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion,
"com.typesafe.akka" %% "akka-stream" % AkkaVersion,
"com.typesafe.akka" %% "akka-http" % AkkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion
)
2 - Add these imports in your file
import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.client.RequestBuilding.Get
import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
import akka.http.scaladsl.unmarshalling.Unmarshal
import scala.util.{Failure, Success}
// for JSON serialization/deserialization following dependency is required:
// "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.7"
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
import scala.concurrent.Future
3 - Define your custom model (in my case only Profile model)
final case class Profile(
Name: String,
Surname: String,
Mail: String,
Age: Int,
Town: String,
Role: String,
PrimaryInstr: String,
SecondaryInstr: String,
PrimaryGenre: String,
SecondaryGenre: String,
Influences: String,
RecordLabel: String,
GigAvailability: String,
RehearseAvailability: String,
RecordingExperience: String,
MusicalAge: Int)
4 - Define your custom "unmarshaller": count the number of attributes of your custom model, say n and use jsonFormatn(yourCustomType). So in this case we have 16 attributes -->
implicit val profileFormat = jsonFormat16(Profile)
5 - make http request. Ensure that your response contains a JSON object or a JSON array of objetcs that match your model. Use this code to retrieve the response and convert it into your custom model.
def getProfiles = {
//Make request
var req = Get("http://localhost:9090/profiles")
//Save Response in a Future object
val responseFuture: Future[HttpResponse] = Http().singleRequest(req)
//When the Future is fulfilled
responseFuture.onComplete {
case Success(response) =>
//Here your code if there is a response
//Convert your response body (response.entity) in a profile array. Note that it is a Future object
var responseAsProfiles: Future[Array[Profile]]= Unmarshal(response.entity).to[Array[Profile]]
//When the Future is fulfilled
responseAsProfiles.onComplete{
_.get match {
case profiles: Array[Profile] =>
//If response was a array of Profiles you can work with profiles
profiles.foreach[Profile] { profile =>
println(profile)
profile
}
case _ => println("error")
}
}
case Failure(_) =>
//Here your code if there is not a response
sys.error("something wrong")
}
}
Hope this will help someone! Bye!