Add the JSON support module(s)

http4s-core does not include JSON support, but integration with three popular Scala JSON libraries are supported as modules.

Circe

The http4s team recommends circe. Only http4s-circe is required for basic interop with circe, but to follow this tutorial, install all three:

val http4sVersion = "0.17.6"

libraryDependencies ++= Seq(
  "org.http4s" %% "http4s-circe" % http4sVersion,
  // Optional for auto-derivation of JSON codecs
  "io.circe" %% "circe-generic" % "0.8.0",
  // Optional for string interpolation to JSON model
  "io.circe" %% "circe-literal" % "0.8.0"
)

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)

Argonaut

Circe is a fork of argonaut, another popular JSON library in the Scala community. The functionality is similar:

libraryDependencies += Seq(
  "org.http4s" %% "http4s-argonaut" % http4sVersion,
  // Optional for auto-derivation of JSON codecs
  "com.github.alexarchambault" %% "argonaut-shapeless_6.2" % "1.2.0-M6"
)

For those not ready to upgrade to argonaut-6.2, an http4s-argonaut61 is also available. It is source compatible, but compiled against Argonaut 6.1.

Json4s

Json4s is less functionally pure than Circe or Argonaut, but older and integrated with many Scala libraries. It comes with two backends. You should pick one of these dependencies:

libraryDependencies += "org.http4s" %% "http4s-json4s-native" % http4sVersion
libraryDependencies += "org.http4s" %% "http4s-json4s-jackson" % http4sVersion

There is no extra codec derivation library for json4s, as it generally bases its codecs on runtime reflection.

Sending raw JSON

Let’s create a function to produce a simple JSON greeting with circe:

import io.circe._
// import io.circe._

import io.circe.literal._
// import io.circe.literal._

import org.http4s._
// import org.http4s._

import org.http4s.dsl._
// import org.http4s.dsl._

def hello(name: String): Json =
  json"""{"hello": $name}"""
// hello: (name: String)io.circe.Json

val greeting = hello("world")
// greeting: io.circe.Json =
// {
//   "hello" : "world"
// }

We now have a JSON value, but we don’t have enough to render it:

scala> Ok(greeting).unsafeRun
<console>:26: error: Cannot convert from io.circe.Json to an Entity, because no EntityEncoder[io.circe.Json] instance could be found.
       Ok(greeting).unsafeRun
         ^

To encode a Scala value of type A into an entity, we need an EntityEncoder[A] in scope. The http4s-circe module includes a org.http4s.circe object, which gives us exactly this for an io.circe.Json value:

import org.http4s.circe._
// import org.http4s.circe._

Ok(greeting).unsafeRun
// res1: org.http4s.Response = Response(status=200, headers=Headers(Content-Type: application/json, Content-Length: 17))

The same EntityEncoder[Json] we use on server responses is also useful on client requests:

import org.http4s.client._
// import org.http4s.client._

POST(uri("/hello"), json"""{"name": "Alice"}""").unsafeRun
// res2: org.http4s.Request = Request(method=POST, uri=/hello, headers=Headers(Content-Type: application/json, Content-Length: 16))

Encoding case classes as JSON

These JSON literals are nice, but in real apps, we prefer to operate on case classes and use JSON as a serialization format near the edge of the world.

Let’s define a couple case classes:

case class Hello(name: String)
case class User(name: String)

To transform a value of type A into Json, circe uses an io.circe.Encoder[A]. With circe’s syntax, we can convert any value to JSON as long as an implicit Encoder is in scope:

import io.circe.syntax._
scala> Hello("Alice").asJson
<console>:36: error: could not find implicit value for parameter encoder: io.circe.Encoder[Hello]
       Hello("Alice").asJson
                      ^

Oops! We haven’t told Circe how we want to encode our case class. Let’s provide an encoder:

implicit val HelloEncoder: Encoder[Hello] =
  Encoder.instance { hello: Hello =>
    json"""{"hello": ${hello.name}}"""
  }
// HelloEncoder: io.circe.Encoder[Hello] = io.circe.Encoder$$anon$13@1c3f7eff

Hello("Alice").asJson
// res4: io.circe.Json =
// {
//   "hello" : "Alice"
// }

That was easy, but gets tedious for applications dealing in lots of types. Fortunately, circe can automatically derive an encoder for us, using the field names of the case class as key names in a JSON object:

import io.circe.generic.auto._
// import io.circe.generic.auto._

User("Alice").asJson
// res5: io.circe.Json =
// {
//   "name" : "Alice"
// }

Equipped with an Encoder and .asJson, we can send JSON in requests and responses for our case classes:

Ok(Hello("Alice").asJson).unsafeRun
// res6: org.http4s.Response = Response(status=200, headers=Headers(Content-Type: application/json, Content-Length: 17))

POST(uri("/hello"), User("Bob").asJson).unsafeRun
// res7: org.http4s.Request = Request(method=POST, uri=/hello, headers=Headers(Content-Type: application/json, Content-Length: 14))

Receiving raw JSON

Just as we needed an EntityEncoder[JSON] to send JSON from a server or client, we need an EntityDecoder[JSON] to receive it.

The org.http4s.circe._ package provides an implicit EntityDecoder[Json]. This makes it very easy to decode a request or response body to JSON using the as syntax:

Ok("""{"name":"Alice"}""").as[Json].unsafeRun
// res8: io.circe.Json =
// {
//   "name" : "Alice"
// }

POST(uri("/hello"),"""{"name":"Bob"}""").as[Json].unsafeRun
// res9: io.circe.Json =
// {
//   "name" : "Bob"
// }

Like sending raw JSON, this is useful to a point, but we typically want to get to a typed model as quickly as we can.

Decoding JSON to a case class

To get from an HTTP entity to Json, we use an EntityDecoder[Json]. To get from Json to any type A, we need an io.circe.Decoder[A]. http4s-circe provides the jsonOf function to make the connection all the way from HTTP to your type A. Specifically, jsonOf[A] takes an implicit Decoder[A] and makes a EntityDecoder[A]:

Ok("""{"name":"Alice"}""").as(jsonOf[User]).unsafeRun
// res10: User = User(Alice)

POST(uri("/hello"), """{"name":"Bob"}""").as(jsonOf[User]).unsafeRun
// res11: User = User(Bob)

Note the argument to as is in parentheses instead of square brackets, as it’s a function call instead of a type. We are implicitly summoning a Decoder[A], but explicitly declaring that A is encoded as JSON.

Putting it all together

A Hello world service

Our hello world service will parse a User from a request and offer a proper greeting.

import io.circe._
import io.circe.generic.auto._
import io.circe.syntax._

import org.http4s._
import org.http4s.circe._
import org.http4s.dsl._

case class User(name: String)
case class Hello(greeting: String)

val jsonService = HttpService {
  case req @ POST -> Root / "hello" =>
    for {
      // Decode a User request
      user <- req.as(jsonOf[User])
      // Encode a hello response
      resp <- Ok(Hello(user.name).asJson)
    } yield (resp)
}

import org.http4s.server.blaze._
val builder = BlazeBuilder.bindHttp(8080).mountService(jsonService, "/")
val blazeServer = builder.run

A Hello world client

Now let’s make a client for the service above:

import org.http4s.client.blaze._
import fs2.Task
import io.circe.generic.auto._

val httpClient = PooledHttp1Client()
// Decode the Hello response
def helloClient(name: String): Task[Hello] = {
  // Encode a User request
  val req = POST(uri("http://localhost:8080/hello"), User(name).asJson)
  // Decode a Hello response
  httpClient.expect(req)(jsonOf[Hello])
}

Finally, we post User("Alice") to our Hello service and expect Hello("Alice") back:

val helloAlice = helloClient("Alice")
// helloAlice: fs2.Task[Hello] = Task

helloAlice.unsafeRun
// res18: Hello = Hello(Alice)