如何在Scala Circe中根据条件解码兼容List[String]与List[File]格式的JSON至指定Case Class
解决两种格式
files字段的Circe解码问题 我来帮你搞定这个问题——你需要处理files字段的两种输入格式:List[String]和List[File],最终都要解码为List[File]类型,其中字符串要转换为type硬编码为audio的File实例。下面是两种可行的实现方案,先从最简洁优雅的方式说起:
方案1:让File解码器兼容两种输入类型
核心思路是给File编写一个能同时处理字符串和完整对象的解码器,这样列表的解码逻辑会自动复用这个能力,无需额外修改JSON结构。
完整代码示例
import io.circe._ import io.circe.generic.semiauto._ // 你的Case Class定义 case class File(`type`: String, value: String) case class Config(files: List[File], channel: String = "BC") case class Inventory(config: Config) object File { // 自定义File解码器:先尝试解码为完整对象,失败则尝试解码为字符串并转换 implicit val decoder: Decoder[File] = Decoder.instance { cursor => // 优先解码为标准的File JSON对象 deriveDecoder[File].decode(cursor) // 如果解码失败,尝试将字符串转换为type=audio的File .orElse(cursor.as[String].map(str => File("audio", str))) } } object Config { import File.decoder // 利用deriveDecoder自动生成Config解码器,会复用上面的File解码器处理files字段 implicit val decoder: Decoder[Config] = deriveDecoder[Config] } object Inventory { import Config.decoder implicit val decoder: Decoder[Inventory] = deriveDecoder[Inventory] }
测试验证
- 当输入是
List[String]格式时:val inputStr = """{ "config": { "files": ["welcome"], "channel": "media" } }""" val result = io.circe.parser.decode[Inventory](inputStr) // 结果:Right(Inventory(Config(List(File("audio", "welcome")), "media"))) - 当输入是
List[File]格式时:val inputObj = """{ "config": { "files": [{"type": "video", "value": "intro.mp4"}], "channel": "media" } }""" val result = io.circe.parser.decode[Inventory](inputObj) // 结果:Right(Inventory(Config(List(File("video", "intro.mp4")), "media")))
方案2:解码前修改JSON结构(手动转换)
如果你更倾向于在解码前先统一JSON结构,可以通过prepare方法修改游标对应的JSON,把字符串列表转换成File对象列表:
代码示例
import io.circe._ import io.circe.generic.semiauto._ case class File(`type`: String, value: String) case class Config(files: List[File], channel: String = "BC") case class Inventory(config: Config) object File { implicit val decoder: Decoder[File] = deriveDecoder[File] } object Config { import File.decoder implicit val decoder: Decoder[Config] = deriveDecoder[Config].prepare { cursor => // 定位到files字段并读取原始JSON cursor.downField("files").as[Json] match { case Right(Json.arr(elements@_*)) => // 将字符串元素转换为File对象的JSON,非字符串元素保持不变 val convertedElements = elements.map { case Json.Str(str) => Json.obj( "type" -> Json.Str("audio"), "value" -> Json.Str(str) ) case other => other } // 替换原files字段并返回修改后的游标 cursor.set(Json.obj( "files" -> Json.arr(convertedElements: _*), "channel" -> cursor.get[Json]("channel").getOrElse(Json.Str("BC")) )) case _ => cursor // 非数组格式直接返回原游标 } } } object Inventory { import Config.decoder implicit val decoder: Decoder[Inventory] = deriveDecoder[Inventory] }
为什么你之前的尝试失败了?
你之前在File解码器的prepare方法中尝试修改数组结构,但File解码器是用来处理单个元素的,而files是列表类型,逻辑层级不匹配。正确的做法应该是在单个元素(File)的解码层面兼容两种输入,或者在列表/Config的解码层面统一处理结构转换。
内容的提问来源于stack exchange,提问作者Arpit Bhardwaj




