Scala 将Hocon配置读取为带有点符号和值的Map[String,String]
我有以下HOCON配置:Scala 将Hocon配置读取为带有点符号和值的Map[String,String],scala,functional-programming,hocon,pureconfig,Scala,Functional Programming,Hocon,Pureconfig,我有以下HOCON配置: a { b.c.d = "val1" d.f.g = "val2" } HOCON将路径“b.c.d”和“d.f.g”表示为对象。因此,我希望有一个读卡器,它将这些配置读取为Map[String,String],例如: Map("b.c.d" -> "val1", "d.f.g" -> "val2") 我创建了一个阅读器,并尝试递
a {
b.c.d = "val1"
d.f.g = "val2"
}
HOCON将路径“b.c.d”和“d.f.g”表示为对象。因此,我希望有一个读卡器,它将这些配置读取为Map[String,String],例如:
Map("b.c.d" -> "val1", "d.f.g" -> "val2")
我创建了一个阅读器,并尝试递归地执行它:
import scala.collection.mutable.{Map => MutableMap}
private implicit val mapReader: ConfigReader[Map[String, String]] = ConfigReader.fromCursor(cur => {
def concat(prefix: String, key: String): String = if (prefix.nonEmpty) s"$prefix.$key" else key
def toMap(): Map[String, String] = {
val acc = MutableMap[String, String]()
def go(
cur: ConfigCursor,
prefix: String = EMPTY,
acc: MutableMap[String, String]
): Result[Map[String, Object]] = {
cur.fluent.mapObject { obj =>
obj.value.valueType() match {
case ConfigValueType.OBJECT => go(obj, concat(prefix, obj.pathElems.head), acc)
case ConfigValueType.STRING =>
acc += (concat(prefix, obj.pathElems.head) -> obj.asString.right.getOrElse(EMPTY))
}
obj.asRight
}
}
go(cur, acc = acc)
acc.toMap
}
toMap().asRight
})
它给出了正确的结果,但是这里有没有避免可变映射的方法?
另外,我想保留“pureconfig”阅读器的实现。不使用递归也可以实现。使用方法
entrySet
如下
导入scala.jdk.CollectionConverters_
瓦尔霍肯=
"""
|a{
|b.c.d=“val1”
|d.f.g=val2
|}“.stripMargin”
val config=ConfigFactory.load(ConfigFactory.parseString(hocon))
val innerConfig=config.getConfig(“a”)
val map=innerConfig
.entrySet()
阿斯卡拉先生
.map{entry=>
entry.getKey->entry.getValue.render()
}
汤玛普先生
println(地图)
产生
Map(b.c.d -> "val1", d.f.g -> "val2")
根据已知的知识,可以定义一个pureconfig.ConfigReader
,它读取Map[String,String]
,如下所示
implicit val reader:ConfigReader[Map[String,String]]=ConfigReader.fromFunction{
案例co:ConfigObject=>
对(
co.toConfig
.entrySet()
阿斯卡拉先生
.map{entry=>
entry.getKey->entry.getValue.render()
}
汤玛普先生
)
案例值=>
//处理错误案例
左(
ConfigReaderFails(
一次性失败(
新的RuntimeException(“无法映射到字符串->字符串的映射”),
选项(value.origin())
)
)
)
}
伊万·斯坦尼斯拉夫丘克给出的解决方案并不理想。如果解析后的config对象包含字符串或对象以外的值,则不会得到错误消息(如您所料),而是一些非常奇怪的输出。例如,如果您解析这样的类型安全配置文档
"a":[1]
结果值如下所示:
Map(a -> [
# String: 1
1
])
即使输入只包含对象和字符串,它也不能正常工作,因为它错误地在所有字符串值周围添加双引号
所以我自己尝试了一下,提出了一个递归解决方案,它可以报告列表或null之类的错误,并且不添加不应该存在的引号
implicit val reader: ConfigReader[Map[String, String]] = {
implicit val r: ConfigReader[String => Map[String, String]] =
ConfigReader[String]
.map(v => (prefix: String) => Map(prefix -> v))
.orElse { reader.map { v =>
(prefix: String) => v.map { case (k, v2) => s"$prefix.$k" -> v2 }
}}
ConfigReader[Map[String, String => Map[String, String]]].map {
_.flatMap { case (prefix, v) => v(prefix) }
}
}
请注意,我的解决方案根本没有提到ConfigValue
或ConfigReader.Result
。它只接受现有的ConfigReader
对象,并将它们与组合符(如map
和orElse
)组合。一般来说,这是编写ConfigReader
s的最佳方法:不要从头开始使用ConfigReader.fromFunction
,使用现有的读取器并将它们组合起来
一开始似乎有点奇怪,上面的代码居然能工作,因为我在自己的定义中使用了
reader
。但是它是有效的,因为orElse
方法按名称而不是按值获取参数。谢谢你的回答,你的解决方案是有效的,但我可能需要更精确地回答我的问题。我想通过“pureconfig”读取器实现它,因为我主要在代码中使用它。@MikeMostetskyi您可以使用entrySet
定义自定义读取器。请参阅更新