将 Hocon 配置读取为 Map[String, String],其中键为点符号和值

Posted

技术标签:

【中文标题】将 Hocon 配置读取为 Map[String, String],其中键为点符号和值【英文标题】:Read Hocon config as a Map[String, String] with key in dot notation and value 【发布时间】:2021-01-05 21:26:57 【问题描述】:

我有以下 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
  )

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

附:另外,我想继续通过“pureconfig”阅读器实现。

【问题讨论】:

【参考方案1】:

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) 
    
  

请注意,我的解决方案根本没有提到 ConfigValueConfigReader.Result。它只接受现有的ConfigReader 对象并将它们与maporElse 等组合符组合在一起。一般来说,这是编写ConfigReaders 的最佳方式:不要从头开始使用ConfigReader.fromFunction 之类的方法,使用现有的阅读器并将它们组合起来。

上面的代码一开始似乎有点令人惊讶,因为我在自己的定义中使用了reader。但它之所以有效,是因为 orElse 方法按名称而不是按值获取参数。

【讨论】:

【参考方案2】:

您可以在不使用递归的情况下做同样的事情。使用方法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())
        )
      )
    )


【讨论】:

感谢您的回答,您的解决方案有效,但我的问题可能需要更精确。我想通过“pureconfig”阅读器来实现它,因为我主要在我的代码中使用它。 @MikeMostetskyi 您可以使用entrySet 定义自定义阅读器。查看更新【参考方案3】:

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

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

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

【讨论】:

以上是关于将 Hocon 配置读取为 Map[String, String],其中键为点符号和值的主要内容,如果未能解决你的问题,请参考以下文章

Kafka Connect:如何将String解析为Map

java怎么将json文件读取进来并转成map

Java servlet 将Map传到jsp中,通过<c:forEach >怎么将map中的值读取到?

通过读取文件创建 map<string,vector<string>> 时如何管理内存

markdown HOCON面向群众

将 String RDD 转换为 Int RDD