🥰
Scala の http4s で Json を扱う例
コード
Server.scala
//> using scala "3.2.0"
//> using lib "org.typelevel::cats-core:2.8.0"
//> using lib "org.typelevel::cats-effect:3.3.14"
//> using lib "org.http4s::http4s-core:1.0.0-M37"
//> using lib "org.http4s::http4s-dsl:1.0.0-M37"
//> using lib "org.http4s::http4s-ember-server:1.0.0-M37"
//> using lib "org.http4s::http4s-circe:1.0.0-M37"
//> using lib "io.circe::circe-core:0.15.0-M1"
//> using lib "io.circe::circe-generic:0.15.0-M1"
import cats.effect._
import cats.syntax.all._
import com.comcast.ip4s._
import org.http4s._
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.http4s.ember.server._
import io.circe._
import io.circe.syntax._
import org.http4s.circe.CirceEntityCodec._
case class Payload(
foo: Int,
bar: String,
buzz: Buzz
) derives Decoder,
Encoder.AsObject
case class Buzz(
hoge: Double,
fuga: Option[Int],
piyo: String
)
object Main extends IOApp:
val extractBuzzHoge = (p: Payload) => Ok(p.buzz.hoge.asJson)
val example = HttpRoutes
.of[IO] { case req @ POST -> Root / "example" =>
req.as[Payload] >>= extractBuzzHoge
}
.orNotFound
def run(args: List[String]): IO[ExitCode] = EmberServerBuilder
.default[IO]
.withHost(ipv4"0.0.0.0")
.withPort(port"8080")
.withHttpApp(example)
.build
.useForever
.as(ExitCode.Success)
scala-cli Server.scala
解説
利用するライブラリ
//> using scala "3.2.0"
//> using lib "org.typelevel::cats-core:2.8.0"
//> using lib "org.typelevel::cats-effect:3.3.14"
//> using lib "org.http4s::http4s-core:1.0.0-M37"
//> using lib "org.http4s::http4s-dsl:1.0.0-M37"
//> using lib "org.http4s::http4s-ember-server:1.0.0-M37"
//> using lib "org.http4s::http4s-circe:1.0.0-M37"
//> using lib "io.circe::circe-core:0.15.0-M1"
//> using lib "io.circe::circe-generic:0.15.0-M1"
ここでは Scala のバージョンや利用するライブラリを指定している.
cats-effect は非同期ランタイム、 http4s は Scala で http を扱うライブラリ、circe は Scala で json を扱うためのライブラリである.
sbt プロジェクトで利用する場合は以下のようになる.
build.sbt
lazy val server = project.in(file("."))
.settings(
scalaVersion := "3.2.0",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.8.0",
"org.typelevel" %% "cats-effect" % "3.3.14",
"org.http4s" %% "http4s-core" % "1.0.0-M37",
"org.http4s" %% "http4s-dsl" % "1.0.0-M37",
"org.http4s" %% "http4s-ember-server" % "1.0.0-M37",
"org.http4s" %% "http4s-ember-circe" % "1.0.0-M37",
"io.circe" %% "circe-core" % "0.15.0-M1",
"io.circe" %% "circe-generic" % "0.15.0-M1",
)
)
また、以下のインポートをすることで、型が circe の Encoder 型クラスを持っていれば io.circe.json
から、http の Response へよしなに変換してくれる.
import org.http4s.circe.CirceEntityCodec._
↓ で Json
から Response
に変換されている.
object Main extends IOApp:
val extractBuzzHoge = (p: Payload) => Ok(p.buzz.hoge.asJson)
利用する Json に対応する型を定義する
以下のような json に対応する Payload
型とBuzz
型を定義する.
example.json
{
"foo" : 1,
"bar" : "....",
"buzz" : {
"hoge" : 1.0,
"fuga" : null,
"piyo" "piyopiyo"
}
}
case class Payload(
foo: Int,
bar: String,
buzz: Buzz
) derives Decoder,
Encoder.AsObject
case class Buzz(
hoge: Double,
fuga: Option[Int],
piyo: String
)
Payload
の derives
節で io.circe.Decoder
, io.circe.Encoder.AsObject
の型クラスを導出している. import io.circe.syntax._
とすることで、Payload
型のクラスを asJson
で io.circe.Json
にしたり、io.circe.Json
から Payload
型に変換できるようになる.
ルーティング
/example
example.json
のような json を受け取って、payload.buzz.hoge を取り出して返すエンドポイントは以下のように書ける.
object Main extends IOApp:
val extractBuzzHoge = (p: Payload) => Ok(p.buzz.hoge.asJson)
val example = HttpRoutes
.of[IO] { case req @ POST -> Root / "example" =>
req.as[Payload] >>= extractBuzzHoge
}
.orNotFound
最後に、http サーバーに example サービスをバインディングしてサーバーを起動している.
def run(args: List[String]): IO[ExitCode] = EmberServerBuilder
.default[IO]
.withHost(ipv4"0.0.0.0")
.withPort(port"8080")
.withHttpApp(example)
.build
.useForever
.as(ExitCode.Success)
Discussion