JSON marshalling
When exercising the app, you interacted with JSON payloads. How does the example app convert data between JSON format and data that can be used by Scala classes? The answer begins in the JsonFormats
object:
sourceimport spray.json.RootJsonFormat
import spray.json.DefaultJsonProtocol
object JsonFormats {
// import the default encoders for primitive types (Int, String, Lists etc)
import DefaultJsonProtocol._
implicit val userJsonFormat: RootJsonFormat[User] = jsonFormat3(User.apply)
implicit val usersJsonFormat: RootJsonFormat[Users] = jsonFormat1(Users.apply)
implicit val actionPerformedJsonFormat: RootJsonFormat[ActionPerformed] = jsonFormat1(ActionPerformed.apply)
}
We’re using the Spray JSON library here, which allows us to define json marshallers (or formats
which is what Spray JSON calls them) in a type-safe way. In other words, if we don’t provide a format instance for a type, yet we’d try to return it in a route by calling complete(someValue)
the code would not compile - saying that it does not know how to marshal the SomeValue
type. This has the up-side of us being completely in control over what we want to expose, and not exposing some type accidentally in our HTTP API.
To handle the two different payloads, the trait defines two implicit values; userJsonFormat
and usersJsonFormat
. Defining the formatters as implicit
ensures that the compiler can map the formatting functionality with the case classes to convert.
The jsonFormatX
methods come from Spray JSON. The X
represents the number of parameters in the underlying case classes:
sourcefinal case class User(name: String, age: Int, countryOfResidence: String)
final case class Users(users: immutable.Seq[User])
We won’t go into how the formatters are implemented - this is done for us by the library. All you need to remember for now is to define the formatters as implicit and that the formatter used should map the number of parameters belonging to the case class it converts.
With these formatters defined, we can import them into the scope of the routes definition so that they are available where we want to use them:
sourceclass UserRoutes(userRegistry: ActorRef[UserRegistry.Command])(implicit val system: ActorSystem[_]) {
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import JsonFormats._
While we used Spray JSON in this example, various other libraries are supported via the Akka HTTP JSON project, including Jackson, Play JSON or circe.
Each library comes with different trade-offs in performance and user-friendlieness. Spray JSON is generally the fastest, though it requires you to write the format values explicitly. If you’d rather make “everything” automatically marshallable into JSON values you might want to use Jackson or Circe instead.
If you’re not sure, we recommend sticking to Spray JSON as it’s the closest in philosophy to Akka HTTP - being explicit about all capabilities.
Now that we’ve examined the example app thoroughly, let’s test a few remaining use cases.