JSON Support
Apache Pekko HTTP’s marshalling and unmarshalling infrastructure makes it rather easy to seamlessly convert application-domain objects from and to JSON. Integration with spray-json is provided out of the box through the pekko-http-spray-json
module. Integration with other JSON libraries are supported by the community. See the list of current community extensions for Apache Pekko HTTP.
spray-json Support¶
The SprayJsonSupport
trait provides a FromEntityUnmarshaller[T]
and ToEntityMarshaller[T]
for every type T
that an implicit spray.json.RootJsonReader
and/or spray.json.RootJsonWriter
(respectively) is available for.
To enable automatic support for (un)marshalling from and to JSON with spray-json, add a library dependency onto:
val PekkoHttpVersion = "1.0.1"
libraryDependencies += "org.apache.pekko" %% "pekko-http-spray-json" % PekkoHttpVersion
def versions = [
ScalaBinary: "2.13"
]
dependencies {
implementation platform("org.apache.pekko:pekko-http-bom_${versions.ScalaBinary}:1.0.1")
implementation "org.apache.pekko:pekko-http-spray-json_${versions.ScalaBinary}"
}
<properties>
<scala.binary.version>2.13</scala.binary.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.pekko</groupId>
<artifactId>pekko-http-bom_${scala.binary.version}</artifactId>
<version>1.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.pekko</groupId>
<artifactId>pekko-http-spray-json_${scala.binary.version}</artifactId>
</dependency>
</dependencies>
Next, provide a RootJsonFormat[T]
for your type and bring it into scope. Check out the spray-json documentation for more info on how to do this.
Finally, import the FromEntityUnmarshaller[T]
and ToEntityMarshaller[T]
implicits directly from SprayJsonSupport
as shown in the example below or mix the org.apache.pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
trait into your JSON support module.
Once you have done this (un)marshalling between JSON and your type T
should work nicely and transparently.
sourceimport org.apache.pekko
import pekko.http.scaladsl.server.Directives
import pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json._
// domain model
final case class Item(name: String, id: Long)
final case class Order(items: List[Item])
// collect your json format instances into a support trait:
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val itemFormat: RootJsonFormat[Item] = jsonFormat2(Item.apply)
implicit val orderFormat: RootJsonFormat[Order] = jsonFormat1(Order.apply) // contains List[Item]
}
// use it wherever json (un)marshalling is needed
class MyJsonService extends Directives with JsonSupport {
val route =
concat(
get {
pathSingleSlash {
complete(Item("thing", 42)) // will render as JSON
}
},
post {
entity(as[Order]) { order => // will unmarshal JSON to Order
val itemsCount = order.items.size
val itemNames = order.items.map(_.name).mkString(", ")
complete(s"Ordered $itemsCount items: $itemNames")
}
})
}
Consuming JSON Streaming style APIs¶
A popular way of implementing streaming APIs is JSON Streaming (see Source Streaming for documentation on building server-side of such API).
Depending on the way the API returns the streamed JSON (newline delimited, raw sequence of objects, or “infinite array”) you may have to apply a different framing mechanism, but the general idea remains the same: consuming the infinite entity stream and applying a framing to it, such that the single objects can be easily deserialized using the usual marshalling infrastructure:
sourceimport MyJsonProtocol._
import org.apache.pekko
import pekko.http.scaladsl.unmarshalling._
import pekko.http.scaladsl.common.EntityStreamingSupport
import pekko.http.scaladsl.common.JsonEntityStreamingSupport
implicit val jsonStreamingSupport: JsonEntityStreamingSupport =
EntityStreamingSupport.json()
val input = """{"uid":1,"txt":"#Pekko rocks!"}""" + "\n" +
"""{"uid":2,"txt":"Streaming is so hot right now!"}""" + "\n" +
"""{"uid":3,"txt":"You cannot enter the same river twice."}"""
val response = HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, input))
// unmarshal:
val unmarshalled: Future[Source[Tweet, NotUsed]] =
Unmarshal(response).to[Source[Tweet, NotUsed]]
// flatten the Future[Source[]] into a Source[]:
val source: Source[Tweet, Future[NotUsed]] =
Source.fromFutureSource(unmarshalled)
sourceUnmarshaller<ByteString, JavaTweet> unmarshal = Jackson.byteStringUnmarshaller(JavaTweet.class);
JsonEntityStreamingSupport support = EntityStreamingSupport.json();
// imagine receiving such response from a service:
String payload =
"{\"uid\":1,\"txt\":\"#Pekko rocks!\"}\n"
+ "{\"uid\":2,\"txt\":\"Streaming is so hot right now!\"}\n"
+ "{\"uid\":3,\"txt\":\"You cannot enter the same river twice.\"}";
HttpEntity.Strict entity = HttpEntities.create(ContentTypes.APPLICATION_JSON, payload);
HttpResponse response = HttpResponse.create().withEntity(entity);
Source<JavaTweet, Object> tweets =
response
.entity()
.getDataBytes()
.via(support.framingDecoder()) // apply JSON framing
.mapAsync(
1, // unmarshal each element
bs -> unmarshal.unmarshal(bs, system()));
In the above example the marshalling is handled by the implicitly provided JsonEntityStreamingSupport
, which is also used when building server-side streaming APIs. You can also achieve the same more explicitly, by manually connecting the entity byte stream through a framing and then deserialization stage:
sourceimport MyJsonProtocol._
import org.apache.pekko
import pekko.http.scaladsl.unmarshalling._
import pekko.http.scaladsl.common.EntityStreamingSupport
import pekko.http.scaladsl.common.JsonEntityStreamingSupport
implicit val jsonStreamingSupport: JsonEntityStreamingSupport =
EntityStreamingSupport.json()
val input = """{"uid":1,"txt":"#Pekko rocks!"}""" + "\n" +
"""{"uid":2,"txt":"Streaming is so hot right now!"}""" + "\n" +
"""{"uid":3,"txt":"You cannot enter the same river twice."}"""
val response = HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, input))
val value: Source[Tweet, Any] =
response.entity.dataBytes
.via(jsonStreamingSupport.framingDecoder) // pick your Framing (could be "\n" etc)
.mapAsync(1)(bytes => Unmarshal(bytes).to[Tweet]) // unmarshal one by one
Pretty printing¶
By default, spray-json marshals your types to compact printed JSON by implicit conversion using CompactPrinter
, as defined in:
sourceimplicit def sprayJsonMarshallerConverter[T](writer: RootJsonWriter[T])(implicit printer: JsonPrinter =
CompactPrinter): ToEntityMarshaller[T] =
sprayJsonMarshaller[T](writer, printer)
Alternatively to marshal your types to pretty printed JSON, bring a PrettyPrinter
in scope to perform implicit conversion.
sourceimport pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json._
// domain model
final case class PrettyPrintedItem(name: String, id: Long)
object PrettyJsonFormatSupport {
import DefaultJsonProtocol._
implicit val printer: JsonPrinter = PrettyPrinter
implicit val prettyPrintedItemFormat: RootJsonFormat[PrettyPrintedItem] = jsonFormat2(PrettyPrintedItem.apply)
}
// use it wherever json (un)marshalling is needed
class MyJsonService extends Directives {
import PrettyJsonFormatSupport._
// format: OFF
val route =
get {
pathSingleSlash {
complete {
PrettyPrintedItem("pekko", 42) // will render as JSON
}
}
}
// format: ON
}
val service = new MyJsonService
// verify the pretty printed JSON
Get("/") ~> service.route ~> check {
responseAs[String] shouldEqual
"""{""" + "\n" +
""" "id": 42,""" + "\n" +
""" "name": "pekko"""" + "\n" +
"""}"""
}
To learn more about how spray-json works please refer to its documentation.