如何为含可变名称字段的KeyValueRow类编写Circe解码器
为带动态字段的case class编写Circe解码器
嘿,这个场景我之前也碰到过——咱们的case class KeyValueRow(count: Int, key: String)结构固定,但对应的JSON里除了固定的count字段,另一个字段的键是动态变化的(比如示例里的url、city)。要给这个类写Circe解码器,其实利用Circe对JSON AST的灵活操作就能搞定,下面一步步来实现:
核心思路
我们需要从JSON里提取两个关键信息:
- 固定的
count整数值 - 除
count外唯一的那个字段的键名(也就是KeyValueRow里的key字段值)
Circe的Decoder允许我们直接操作JSON对象,所以我们可以先解析出count,再筛选出剩下的动态字段。
具体实现步骤
1. 先导入必要的Circe包
首先得把Circe的核心类和语法糖导入进来:
import io.circe._ import io.circe.generic.semiauto._
2. 编写自定义Decoder
我们手动实现Decoder[KeyValueRow],逻辑很清晰:
implicit val keyValueRowDecoder: Decoder[KeyValueRow] = Decoder.instance { c => for { // 先解析固定的count字段 count <- c.get[Int]("count") // 把当前JSON转成JsonObject,过滤掉count字段 remainingFields <- c.as[JsonObject].map(_.filterKeys(_ != "count")) // 确保剩下的字段只有一个,然后提取它的键名 (dynamicKey, _) <- remainingFields.headOption match { case Some((k, _)) => Right((k, ())) case None => Left(DecodingFailure("JSON缺少动态字段(除count外无其他字段)", c.history)) } } yield KeyValueRow(count, dynamicKey) }
3. 可选:获取动态字段的值
如果你的业务需要同时拿到动态字段的对应值,可以调整代码逻辑,比如:
// 假设我们把case class改成:case class KeyValueRow(count: Int, key: String, value: String) implicit val keyValueRowDecoder: Decoder[KeyValueRow] = Decoder.instance { c => for { count <- c.get[Int]("count") remainingFields <- c.as[JsonObject].map(_.filterKeys(_ != "count")) (dynamicKey, dynamicValue) <- remainingFields.headOption match { case Some((k, v)) => v.as[String].map(vStr => (k, vStr)) case None => Left(DecodingFailure("JSON缺少动态字段", c.history)) } } yield KeyValueRow(count, dynamicKey, dynamicValue) }
4. 测试验证
用你给出的示例JSON来测试一下:
// 测试url场景 val jsonUrl = """{"count":974989,"url":"http://google.com"}""" io.circe.parser.decode[KeyValueRow](jsonUrl) // 输出:Right(KeyValueRow(974989, "url")) // 测试city场景 val jsonCity = """{"count":1234,"city":"Rome"}""" io.circe.parser.decode[KeyValueRow](jsonCity) // 输出:Right(KeyValueRow(1234, "city"))
如果JSON里除了count存在多个动态字段,解码器会返回Left,你也可以根据业务需求调整这部分逻辑(比如抛出更明确的错误,或者默认取第一个字段)。
内容的提问来源于stack exchange,提问作者Pavel Ajtkulov
相关产品推荐
相关产品推荐

