在Kotlin阅读和处理HOCON
我想从HOCON(Typesafe Config)文件中读取以下配置到Kotlin。
tablename: { columns: [ { item: { type: integer, key: true, null: false } } { desc: { type: varchar, length: 64 } } { quantity: { type: integer, null: false } } { price: { type: decimal, precision: 14, scale: 3 } } ] }
事实上,我想提取关键列(S)。 到目前为止,我尝试了以下方法。
val metadata = ConfigFactory.parseFile(metafile) val keys = metadata.getObjectList("${tablename.toLowerCase()}.columns") .filter { it.unwrapped().values.first().get("key") == true }
但是,它会失败,并出现以下错误。
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: @kotlin.internal.InlineOnly public operator inline fun kotlin.collections.Map.get(key: kotlin.String): ??? defined in kotlin.collections
很显然,Kotlin无法理解Map中“value”字段的数据types。 我如何声明或让Kotlin知道?
也不是说在这个地图中有不同的types和可选的键。
PS:我知道Kontrig和Klutter有Kotlin可用的包装。 我希望如果这很容易写,我可以避免另一个图书馆。
更新1:
我已经尝试了以下。
it.unwrapped().values.first().get("key")
得到下面的编译器错误。
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: @kotlin.internal.InlineOnly public operator inline fun kotlin.collections.Map.get(key: kotlin.String): kotlin.Boolean? defined in kotlin.collections
和这个
it.unwrapped().values.first().get("key")
与输出
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: @kotlin.internal.InlineOnly public operator inline fun kotlin.collections.Map.get(key: kotlin.String): kotlin.Boolean? defined in kotlin.collections
更新2:
看看它在别处处理的方式,我想我可能需要使用reflection。 尝试一下,我有限的曝光。 到目前为止没有运气。
考虑一下你的代码,解构如下:
val keys = metadata.getObjectList("tablename.columns") .filter { val item:ConfigObject = it val unwrapped:Map = item.unwrapped() val values:Collection = unwrapped.values val firstValue:Any? = values.first() firstValue.get("key") == true // does not compile }
从上面的问题应该是显而易见的。 您需要帮助编译器使用firstValue
持有Map
的信息,如下所示:
val firstValueMap = firstValue as Map firstValueMap["key"] == true
即使你没有使用Klutter,我也创建了一个更新来使ConfigObject
和Config
一致。 从Klutter版本1.17.1
开始(今天推送到Maven中心),您可以根据您的问题做以下unit testing所代表的内容。
find关键列的函数:
fun findKeyColumns(cfg: Config, tableName: String): Map { return cfg.nested(tableName).value("columns").asObjectList() .map { it.keys.single() to it.value(it.keys.single()).asObject() } .filter { it.second.value("key").asBoolean(false) } .toMap() }
这里是完整的unit testing:
// from http://stackoverflow.com/questions/37092808/reading-and-processing-hocon-in-kotlin @Test fun testFromSo37092808() { // === mocked configuration file val cfg = loadConfig(StringAsConfig(""" products: { columns: [ { item: { type: integer, key: true, null: false } } { desc: { type: varchar, length: 64 } } { quantity: { type: integer, null: false } } { price: { type: decimal, precision: 14, scale: 3 } } ] } """)) // === function to find which columns are key columns fun findKeyColumns(cfg: Config, tableName: String): Map { return cfg.nested(tableName).value("columns").asObjectList() .map { it.keys.single() to it.value(it.keys.single()).asObject() } .filter { it.second.value("key").asBoolean(false) } .toMap() } // === sample usage val productKeys = findKeyColumns(cfg, "products") // we only have 1 in the test data, so grab the name and the values val onlyColumnName = productKeys.entries.first().key val onlyColumnObj = productKeys.entries.first().value assertEquals ("item", onlyColumnName) assertEquals (true, onlyColumnObj.value("key").asBoolean()) assertEquals ("integer", onlyColumnObj.value("type").asString()) assertEquals (false, onlyColumnObj.value("null").asBoolean()) }
因为列名不在其设置范围内,所以可以像上面那样返回一个Map
,或者将列名Pair
的列表返回到设置映射。
配置文件的设计也可以改变,使配置的处理更加简单(即配置对象中的表名,而不是左侧的键),同样的列名称,添加到对象和而不是作为左侧键)。