Scala 将Hocon配置读取为带有点符号和值的Map[String,String]

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") 我创建了一个阅读器,并尝试递

我有以下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")
我创建了一个阅读器,并尝试递归地执行它:

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
定义自定义读取器。请参阅更新