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

基于Circe实现动态JSON解码:提取指定路径键值对

解决Circe中提取动态路径键名与值映射到ADT的问题

我明白你遇到的痛点了——Circe的Decoder.prepare确实能帮你导航到指定路径的值,但它没法直接获取当前值对应的键名,而这正是你需要映射到StringTuple/NumericTuple这些ADT类的关键信息。下面是具体的解决方案:

核心思路

要同时拿到键名和对应值,我们不能直接跳到最终值的游标位置,而是需要:

  1. 把点分隔的路径拆分为「父路径」和「目标键名」两部分
  2. 导航到父路径的游标节点
  3. 在父节点下定位到目标键,同时获取键名和值
  4. 根据值的类型映射到对应的Tuple case class

完整实现代码

首先补充路径拆分的工具函数,然后自定义Decoder来实现逻辑:

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

// 你的ADT定义
sealed trait Data
final case class StringTuple(key: String, value: String) extends Data
final case class NumericTuple(key: String, value: Double) extends Data
final case class DateTuple(key: String, value: String) extends Data // 假设用String存储日期字符串

// 路径拆分工具函数:把"a.b.c"拆成(Some("a.b"), "c"),根路径则是(None, "a")
def splitPath(path: String): (Option[String], String) = {
  val parts = path.split("\\.")
  if (parts.length == 1) (None, parts.head)
  else (Some(parts.init.mkString(".")), parts.last)
}

// 自定义Decoder:根据配置的路径提取键和值,映射到对应的Data子类
def dataDecoder(confPath: String): Decoder[Data] = Decoder.instance { cursor =>
  val (parentPathOpt, targetKey) = splitPath(confPath)
  
  // 导航到父节点游标
  val parentCursor = parentPathOpt match {
    case Some(parentPath) => cursor.downField(parentPath)
    case None => cursor
  }
  
  // 定位目标键,获取键名和值
  parentCursor.get[Json](targetKey).flatMap { json =>
    json.as[String].map(StringTuple(targetKey, _))
      .orElse(json.as[Double].map(NumericTuple(targetKey, _)))
      .orElse(json.as[String].map(DateTuple(targetKey, _))) // 这里可以根据实际日期格式调整解码逻辑
  }
}

// 使用示例
case class Conf(path: String)

def extractValue(confElement: Conf, json: String): Either[Error, Data] = {
  parse(json).flatMap(_.as(dataDecoder(confElement.path)))
}

// 测试你的示例JSON
val sampleJson = """{ "myDocument": { "static_key": "value", "dynamic_key": "value", "static_key2": "value2", "dynamic_key2": { "dynamic_key3": "value3" } } }"""
val conf = Conf("myDocument.dynamic_key2.dynamic_key3")
val result = extractValue(conf, sampleJson)
// result: Right(StringTuple(dynamic_key3,value3))

关键细节解释

  • splitPath函数:把完整路径拆成父路径和目标键,这样我们能先导航到父节点,保留键名的上下文
  • 自定义Decoder.instance:直接操作游标,先到父节点,再获取目标键的Json值,然后尝试按不同类型解码,映射到对应的Tuple类
  • 类型匹配逻辑:通过orElse依次尝试字符串、数值、日期的解码,你可以根据实际需求调整解码顺序或添加更精确的日期解码(比如用io.circe.java8.time的日期解码器)

为什么之前的方法不行?

你之前用Decoder.prepare(_.downField(confElement.path)),本质是直接跳到了value3的游标位置,此时游标已经指向值,没有办法再回溯获取它的键名dynamic_key3。而先导航到父节点dynamic_key2,再获取目标键的方式,才能同时拿到键和值的信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:36:58