基于Circe实现动态JSON解码:提取指定路径键值对
解决Circe中提取动态路径键名与值映射到ADT的问题
我明白你遇到的痛点了——Circe的Decoder.prepare确实能帮你导航到指定路径的值,但它没法直接获取当前值对应的键名,而这正是你需要映射到StringTuple/NumericTuple这些ADT类的关键信息。下面是具体的解决方案:
核心思路
要同时拿到键名和对应值,我们不能直接跳到最终值的游标位置,而是需要:
- 把点分隔的路径拆分为「父路径」和「目标键名」两部分
- 导航到父路径的游标节点
- 在父节点下定位到目标键,同时获取键名和值
- 根据值的类型映射到对应的
Tuplecase 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
相关产品推荐
相关产品推荐

