Scala、ZIO、Sttp、Circe:如何将错误响应映射到自定义Trait
问题解决思路
核心原因
Circe对密封特质(sealed trait)的自动解码依赖鉴别器字段来区分不同子类。你的服务器返回的错误JSON中没有携带标识子类类型的字段,导致Circe无法判断应该解码为GeneralTrait的哪个实现类,从而抛出DeserializationException。而直接使用具体类时,Circe无需鉴别器,直接匹配字段即可完成解码,所以能正常工作。
两种解决方案
方案1:添加鉴别器字段并配置Circe
如果可以修改服务器返回的错误响应结构,在JSON中加入鉴别器字段(比如"type"),同时配置Circe使用该字段派生编解码器:
- 服务器返回的错误JSON示例:
{ "type": "FirstError", "msg": "请求参数错误", "code": 400 }
- 修改Circe编解码器配置:
import io.circe._ import io.circe.generic.extras._ import io.circe.generic.extras.semiauto._ // 配置鉴别器字段名称 implicit val circeConfig: Configuration = Configuration.default.withDiscriminator("type") // 先派生子类编解码器 implicit val firstErrorCodec: Codec[FirstError] = deriveConfiguredCodec // 再派生密封特质编解码器 implicit val generalTraitCodec: Codec[GeneralTrait] = deriveConfiguredCodec
这样Circe就能通过type字段识别对应的子类,完成GeneralTrait的自动解码。
方案2:手动根据状态码解码
如果无法修改服务器响应,可先将错误体解析为原始Json,再结合HTTP状态码手动匹配解码:
basicRequest.auth .get(...) .response(asJsonEither[Json, MyResponse]) // 错误体先解析为原始Json .send(backend) .map(_.body.left.map { case HttpError(jsonBody, statusCode) => statusCode match { // 根据状态码匹配对应的子类解码 case StatusCode.BadRequest => jsonBody.as[FirstError].fold( _ => SomeSerializationError("FirstError格式错误"), identity ) // 添加其他状态码对应的子类处理逻辑 case StatusCode.NotFound => jsonBody.as[SecondError].fold(...) case _ => SomeSerializationError("未知错误类型") } case DeserializationException(_, _) => SomeSerializationError("反序列化失败") }) .flatMap(ZIO.fromEither(_))
这种方式跳过了Circe对密封特质的自动鉴别,直接通过业务逻辑(状态码)绑定子类,更灵活适配无鉴别器的响应结构。
内容的提问来源于stack exchange,提问作者Developus
相关产品推荐
相关产品推荐

