使用Circe为http4s POST请求添加JSON请求体时遇类型不匹配问题
我太懂你遇到的这个坑了!之前我在折腾http4s的时候也踩过一模一样的雷——照着官方教程改POST请求,想用Circe的JSON字面量当请求体,结果直接把Json对象传给Request的body参数就炸了,报错信息是不是这样的:
[error] found : io.circe.Json
[error] required: org.http4s.EntityBody[cats.effect.IO]
[error] (which expands to) fs2.Stream[cats.effect.IO, Byte]
这问题根源很简单:http4s的Request里的body字段要的是字节流类型(也就是EntityBody[IO],本质是fs2.Stream[IO, Byte]),但Circe的Json只是纯内存里的数据结构,根本不是流类型。要搞定它,得靠http4s-circe这个工具库帮我们做转换。
第一步:先把依赖加上
以sbt为例,在你的构建文件里添加上http4s-circe的依赖(版本号要和你用的http4s主版本对应上):
libraryDependencies += "org.http4s" %% "http4s-circe" % "0.18.29"
第二步:用正确姿势生成请求
有两种好用的写法,都能解决类型不匹配的问题:
写法一:用withBody(推荐,更简洁)
Request的withBody方法会自动利用http4s-circe提供的隐式编码器,把Json转换成符合要求的请求体,还会帮你自动设置Content-Type: application/json的请求头:
import cats.effect.IO import org.http4s._ import org.http4s.circe._ // 关键导入,提供隐式转换能力 import io.circe.Json import io.circe.literal._ val body = json"""{"hello":"world"}""" // withBody返回IO[Request[IO]],如果需要同步拿到Request可以用unsafeRunSync,否则保留IO上下文即可 val req = Request[IO](method = Method.POST, uri = Uri.uri("/")) .withBody(body) .unsafeRunSync()
写法二:手动转换为EntityBody
如果你想直接给body参数赋值,可以用asEntityBody扩展方法把Json转成EntityBody[IO]:
import cats.effect.IO import org.http4s._ import org.http4s.circe._ import io.circe.Json import io.circe.literal._ val body = json"""{"hello":"world"}""" val entityBody: EntityBody[IO] = body.asEntityBody // 显式完成类型转换 val req = Request[IO](method = Method.POST, uri = Uri.uri("/"), body = entityBody)
这样就能顺利生成带JSON请求体的POST请求了,本质就是让http4s帮你把Circe的Json序列化成字节流,同时处理好HTTP请求的相关头信息。
内容的提问来源于stack exchange,提问作者Herman J. Radtke III

