How to unmarshall custom object from Http Response Entity in Scala?

Viewed 2346

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!

1 Answers

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!

Related