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

Circe技术问询:数组元素转Case Class及相关优化问题

处理Circe JSON层级解析的两个问题

问题背景

现有如下JSON结构及初始Circe代码:

import io.circe._
import io.circe.parser._

val jStr = """
{
    "a1": ["c1", "c2"],
    "a2": [{"d1": "abc", "d2": "efg"}, {"d1": "hij", "d2": "klm"}]
}
"""

val j = parse(jStr).getOrElse(Json.Null)
val ja = j.hcursor.downField("a2").as[Json].getOrElse("").toString

// 初始Song类及解码器
case class Song(id: String, title: String)
implicit val songDecoder: Decoder[Song] = (c: HCursor ) => for {
    id <- c.downField("d1").as[String]
    title <- c.downField("d2").as[String]
} yield Song(id,title)

// 不合理的转String解码方式
io.circe.parser.decode[List[Song]](ja).getOrElse("")

问题1:将a1数组的c1值添加到每个Song实例中

需修改Song类为:case class Song(id: String, title: String, artist: String),把原JSON中a1数组的第一个元素(c1)作为每个Song的artist字段值。

解决方案

先从根JSON中提取a1的第一个元素,再结合该值解码a2列表并构造Song实例:

// 修改后的Song类
case class Song(id: String, title: String, artist: String)

val rootJson = parse(jStr).getOrElse(Json.Null)
val rootCursor = rootJson.hcursor

// 提取a1数组的第一个元素,默认值可根据业务调整
val targetArtist = rootCursor.downField("a1")
  .as[List[String]]
  .map(_.headOption.getOrElse("unknown_artist"))
  .getOrElse("unknown_artist")

// 直接从cursor解码a2,注入artist值
val songs = rootCursor.downField("a2")
  .as[List[Song]] {
    Decoder.decodeList { c: HCursor =>
      for {
        id <- c.downField("d1").as[String]
        title <- c.downField("d2").as[String]
      } yield Song(id, title, targetArtist)
    }
  }
  .getOrElse(Nil)

或者用更优雅的参数化解码器方式:

// 定义带artist参数的解码器
def songDecoder(artist: String): Decoder[Song] = 
  Decoder.forProduct3("d1", "d2", _ => artist)(Song)

val songsAlt = for {
  a1List <- rootCursor.downField("a1").as[List[String]]
  artist = a1List.headOption.getOrElse("unknown_artist")
  songList <- rootCursor.downField("a2").as[List[Song]](Decoder.decodeList(songDecoder(artist)))
} yield songList

问题2:移除转String的不合理解码方式

之前将a2的Json转为String再解码的方式完全多余,直接通过HCursor操作即可完成解码,无需中间字符串转换。

解决方案

直接使用根HCursor定位到a2字段,调用as[List[...]]完成解码:

// 以初始Song类为例,直接解码
val directSongs = rootCursor.downField("a2")
  .as[List[Song]]
  .getOrElse(Nil)

结合问题1的场景,同样不需要转String,直接在cursor层面完成所有解码逻辑,既高效又避免了字符串转换带来的潜在问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:15:43