0

我有以下 HOCON 配置:

a {
 b.c.d = "val1"
 d.f.g = "val2" 
}

HOCON 将路径“bcd”和“dfg”表示为对象。所以,我想要一个阅读器,它将这些配置读取为 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
  })

它给了我正确的结果,但有没有办法在这里避免 MutableMap?

PS 另外,我想通过“pureconfig”阅读器继续实施。

4

3 回答 3

0

您可以在不使用递归的情况下执行相同的操作。使用方法entrySet如下

import scala.jdk.CollectionConverters._

val hocon =
  """
  |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()
  .asScala
  .map { entry =>
    entry.getKey -> entry.getValue.render()
  }
  .toMap

println(map)

生产

Map(b.c.d -> "val1", d.f.g -> "val2")

有了给定的知识,可以定义pureconfig.ConfigReader如下所示Map[String, String]

implicit val reader: ConfigReader[Map[String, String]] = ConfigReader.fromFunction {
  case co: ConfigObject =>
    Right(
      co.toConfig
        .entrySet()
        .asScala
        .map { entry =>
          entry.getKey -> entry.getValue.render()
        }
        .toMap
    )
  case value =>
    //Handle error case
    Left(
      ConfigReaderFailures(
        ThrowableFailure(
          new RuntimeException("cannot be mapped to map of string -> string"),
          Option(value.origin())
        )
      )
    )
}

于 2020-09-19T07:36:00.580 回答
0

Ivan Stanislavciuc 给出的解决方案并不理想。如果解析的配置对象包含字符串或对象以外的值,您不会收到错误消息(如您​​所料),而是一些非常奇怪的输出。例如,如果您解析这样的类型安全配置文档

"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。一般来说,这是编写ConfigReaders 的最佳方式:不要从头开始使用类似的方法ConfigReader.fromFunction,使用现有的阅读器并将它们组合起来。

乍一看,上面的代码完全有效,这似乎有点令人惊讶,因为我是reader在它自己的定义中使用的。但它之所以有效,是因为该orElse方法通过名称而不是值来获取其参数。

于 2020-09-21T01:17:06.783 回答
0

我不想编写自定义阅读器来获取键值对的映射。相反,我将内部数据类型从映射更改为对列表(我正在使用 kotlin),然后如果需要,我可以在稍后的内部阶段轻松地将其更改为映射。然后我的 HOCON 就可以变成这样了。

  additionalProperties = [
    {first = "sasl.mechanism", second = "PLAIN"},
    {first = "security.protocol", second = "SASL_SSL"},
  ]
  additionalProducerProperties = [
    {first = "acks", second = "all"},
  ]

对人类来说不是最好的......但我更喜欢它必须构建自定义解析组件。

于 2021-09-18T07:46:55.493 回答