将 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)
请注意,我的解决方案根本没有提到 ConfigValue
或 ConfigReader.Result
。它只接受现有的ConfigReader
对象并将它们与map
和orElse
等组合符组合在一起。一般来说,这是编写ConfigReader
s 的最佳方式:不要从头开始使用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],其中键为点符号和值的主要内容,如果未能解决你的问题,请参考以下文章
Java servlet 将Map传到jsp中,通过<c:forEach >怎么将map中的值读取到?