如何用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]
为什么这比原始实现更好?
- 保留错误上下文:原始实现将所有错误转为字符串,丢失了错误的具体类型(
ParsingFailure/DecodingFailure)和上下文信息。优化后的实现将ParsingFailure转换为DecodingFailure,保留了原始错误的Throwable信息,调试时能更精准定位问题。 - 贴合Circe设计风格:使用Circe原生的
flatMap和fromEither组合子,避免手动处理字符串错误转换,代码更简洁且符合库的设计逻辑。 - 错误定位更明确:当解析失败时,错误会关联到
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
相关产品推荐
相关产品推荐

