Search code examples
jsonscalahttpakkaunmarshalling

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


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!


Solution

  • 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!