You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Circe解码JSON字段中嵌套的JSON字符串?

Circe解析嵌套字符串JSON为类型化对象的优雅实现

给定如下JSON:

{"key": "value", "embedded": "{\"foo\": \"bar\"}"}

我们希望将embedded字段(字符串形式的嵌套JSON)直接解析为JsonString[FooBar]类型,而非先解析为String再手动处理。

优雅的JsonString Decoder实现

import io.circe._
import io.circe.generic.semiauto._
import io.circe.parser.parse

// 定义目标类型
case class JsonString[A](value: A)
case class FooBar(foo: String)
case class Data(key: String, embedded: JsonString[FooBar])

// 实现JsonString的Decoder
implicit def jsonStringDecoder[A](implicit decoder: Decoder[A]): Decoder[JsonString[A]] =
  Decoder.decodeString.flatMap { str =>
    // 解析字符串为Json,将ParsingFailure转为DecodingFailure保留错误上下文
    val parseResult = parse(str).leftMap(pf => DecodingFailure(pf, Nil))
    // 解码Json为目标类型,再包装为JsonString
    val decodeResult = parseResult.flatMap(json => decoder.decodeJson(json)).map(JsonString(_))
    // 将Either结果转为Decoder实例
    Decoder.fromEither(decodeResult)
  }

// 自动派生其他类型的Decoder
implicit val fooBarDecoder: Decoder[FooBar] = deriveDecoder[FooBar]
implicit val dataDecoder: Decoder[Data] = deriveDecoder[Data]

为什么这比原始实现更好?

  1. 保留错误上下文:原始实现将所有错误转为字符串,丢失了错误的具体类型(ParsingFailure/DecodingFailure)和上下文信息。优化后的实现将ParsingFailure转换为DecodingFailure,保留了原始错误的Throwable信息,调试时能更精准定位问题。
  2. 贴合Circe设计风格:使用Circe原生的flatMap和fromEither组合子,避免手动处理字符串错误转换,代码更简洁且符合库的设计逻辑。
  3. 错误定位更明确:当解析失败时,错误会关联到embedded字段的解析路径,而非模糊的字符串错误提示。

可选:带上下文的错误信息格式化

如果希望错误信息更易读,可以在转换时添加明确的上下文前缀:

implicit def jsonStringDecoder[A](implicit decoder: Decoder[A]): Decoder[JsonString[A]] =
  Decoder.decodeString.emap { str =>
    parse(str)
      .flatMap(json => decoder.decodeJson(json))
      .map(JsonString(_))
      .leftMap {
        case pf: ParsingFailure => s"Embedded JSON parse failed: ${pf.getMessage}"
        case df: DecodingFailure => s"Embedded JSON decode failed: ${df.getMessage}"
      }
  }

这种方式虽然将错误转为字符串,但添加了明确的场景描述,便于快速识别是嵌套JSON的解析还是解码环节出错。

测试验证

用目标JSON测试解析:

import io.circe.parser.decode

val json = """{"key": "value", "embedded": "{\"foo\": \"bar\"}"}"""
val result = decode[Data](json)
// result: Either[DecodingFailure, Data] = Right(Data("value", JsonString(FooBar("bar"))))

若embedded字段为无效JSON:

val invalidJson = """{"key": "value", "embedded": "invalid-json"}"""
val errorResult = decode[Data](invalidJson)
// 错误信息会包含具体的解析失败原因,如"Embedded JSON parse failed: expected colon at line 1 column 8"

内容的提问来源于stack exchange,提问作者cheezsteak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 05:25:58