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-jsonJackson is provided out of the box through the pekko-http-spray-jsonpekko-http-jackson module. Integration with other JSON libraries are supported by the community. See the list of current community extensions for Apache Pekko HTTP.

Jackson Support

To make use of the support module for (un)marshalling from and to JSON with Jackson, add a library dependency onto:

sbt
val PekkoHttpVersion = "1.0.1"
libraryDependencies += "org.apache.pekko" %% "pekko-http-jackson" % PekkoHttpVersion
Gradle
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-jackson_${versions.ScalaBinary}"
}
Maven
<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-jackson_${scala.binary.version}</artifactId>
  </dependency>
</dependencies>

Use org.apache.pekko.http.javadsl.marshallers.jackson.Jackson.unmarshaller(T.class) to create an Unmarshaller<HttpEntity,T>Unmarshaller[HttpEntity,T] which expects the request body (HttpEntity) to be of type application/json and converts it to T using Jackson.

sourceimport org.apache.pekko.http.javadsl.marshallers.jackson.Jackson;
import org.apache.pekko.http.javadsl.model.StatusCodes;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static org.apache.pekko.http.javadsl.server.Directives.*;
import static org.apache.pekko.http.javadsl.unmarshalling.StringUnmarshallers.INTEGER;

public static Route appRoute(final Map<Integer, Pet> pets) {
  PetStoreController controller = new PetStoreController(pets);

  // Defined as Function in order to refer to [pets], but this could also be an ordinary method.
  Function<Integer, Route> existingPet =
      petId -> {
        Pet pet = pets.get(petId);
        return (pet == null)
            ? reject()
            : complete(StatusCodes.OK, pet, Jackson.<Pet>marshaller());
      };

  // The directives here are statically imported, but you can also inherit from AllDirectives.
  return concat(
      path("", () -> getFromResource("web/index.html")),
      pathPrefix(
          "pet",
          () ->
              path(
                  INTEGER,
                  petId ->
                      concat(
                          // demonstrates different ways of handling requests:

                          // 1. using a Function
                          get(() -> existingPet.apply(petId)),

                          // 2. using a method
                          put(
                              () ->
                                  entity(
                                      Jackson.unmarshaller(Pet.class),
                                      thePet -> putPetHandler(pets, thePet))),
                          // 2.1. using a method, and internally handling a Future value
                          path(
                              "alternate",
                              () ->
                                  put(
                                      () ->
                                          entity(
                                              Jackson.unmarshaller(Pet.class),
                                              thePet -> putPetHandler(pets, thePet)))),

                          // 3. calling a method of a controller instance
                          delete(() -> controller.deletePet(petId))))));
}

Use org.apache.pekko.http.javadsl.marshallers.jackson.Jackson.marshaller(T.class) to create a Marshaller<T,RequestEntity>Marshaller[T,RequestEntity] which can be used with RequestContext.complete or RouteDirectives.complete to convert a POJO to an HttpResponse.

sourceimport org.apache.pekko.http.javadsl.marshallers.jackson.Jackson;
import org.apache.pekko.http.javadsl.model.StatusCodes;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static org.apache.pekko.http.javadsl.server.Directives.*;
import static org.apache.pekko.http.javadsl.unmarshalling.StringUnmarshallers.INTEGER;

private static Route putPetHandler(Map<Integer, Pet> pets, Pet thePet) {
  pets.put(thePet.getId(), thePet);
  return complete(StatusCodes.OK, thePet, Jackson.<Pet>marshaller());
}

private static Route alternativeFuturePutPetHandler(Map<Integer, Pet> pets, Pet thePet) {
  pets.put(thePet.getId(), thePet);
  CompletableFuture<Pet> futurePet = CompletableFuture.supplyAsync(() -> thePet);
  return completeOKWithFuture(futurePet, Jackson.<Pet>marshaller());
}

Refer to this file in the sources for the complete example.

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:

sbt
val PekkoHttpVersion = "1.0.1"
libraryDependencies += "org.apache.pekko" %% "pekko-http-spray-json" % PekkoHttpVersion
Gradle
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}"
}
Maven
<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:

Scala
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)
Java
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:

Scala
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

In the above example the JsonEntityStreamingSupport class is used to obtain the proper framing, though you could also pick the framing manually by using org.apache.pekko.stream.javadsl.Framing or org.apache.pekko.stream.javadsl.JsonFraming. Framing stages are used to “chunk up” the pieces of incoming bytes into appropriately sized pieces of valid JSON, which then can be handled easily by a not-streaming JSON serializer such as jackson in the example. This technique is simpler to use and often good enough rather than writing a fully streaming JSON parser (which also is possible).

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.