简体   繁体   English

类型安全配置:如何迭代配置项

[英]Typesafe config: How to iterate over configuration items

In my Play application I've a configuration like this:在我的 Play 应用程序中,我有一个这样的配置:

social {
    twitter {
        url="https://twitter.com"
        logo="images/twitter.png"
    }
    facebook {
        url="https://www.facebook.com"
        logo="images/facebook.png"
    }
}

Ho do I iterate over all the social entries to get url and logo for each entry?我如何遍历所有social条目以获取每个条目的urllogo

<table border="0" cellspacing="0" cellpadding="2"><tr>
    @configuration.getConfig("social").map { config =>
        @for(item <- config.entrySet) {
           <td><a href="item.getString("url")">
           <img src="@routes.Assets.at("item.getString("logo")").absoluteURL()" width="24" height="24"/></a></td>
        }
    }
</table>

Of course, item.getString in the snippet here above does not work... it just shows what I'm trying to achieve.当然,上面代码片段中的item.getString不起作用......它只是显示了我想要实现的目标。

The final objective would be to be able to add any further social url without having to modify the page template.最终目标是能够在无需修改页面模板的情况下添加任何进一步的社交 url。

If you change the config to:如果将配置更改为:

"social" : [
     {
        name="twitter",
        url="https://twitter.com",
        logo="images/twitter.png"
    },
    {
        name="facebook",
        url="https://www.facebook.com",
        logo="images/facebook.png"
    }
]

You could do it like this:你可以这样做:

@(message: String)(implicit request: RequestHeader)
@import play.api.Play.current

<table border="0" cellspacing="0" cellpadding="2"><tr>
    @current.configuration.getConfigList("social").get.map { config =>
            <td><a href="@config.getString("url")">
            <img src="@routes.Assets.at(config.getString("logo").get).absoluteURL()" width="24" height="24"/></a></td>
        }
</table>

For posterity, here's another way to iterate over a nested config like you had.对于后代,这是另一种迭代嵌套配置的方法。 I prefer that format to the array one and I'd rather make my config cleaner than the code.我更喜欢这种格式而不是数组,我宁愿让我的配置比代码更干净。

import collection.JavaConversions._
val socialConfig = ConfigFactory.load.getConfig("social")
socialConfig.root.map { case (name: String, configObject: ConfigObject) => 
    val config = configObject.toConfig
    println(config.getString("url"))
    println(config.getString("logo"))
}

I'm sure the OP could convert this into a Twirl template.我确信 OP 可以将其转换为 Twirl 模板。 That's about as clean as I can get it.这是我能得到的最干净的。

In case you you're using java,this might be a solution :如果您使用的是 java,这可能是一个解决方案:

ConfigList socials = ConfigFactory().load.getList("social")

for (ConfigValue cv : socials) {
   Config c = ((ConfigObject)cv).toConfig();
   System.out.println(c.getString("url"));
   System.out.println(c.getString("logo"));
}

socialConfig.root.map does not work. socialConfig.root.map 不起作用。

Here is my solution -这是我的解决方案 -

private val firstSegmentRE = """^(\w+)[\.*].*$""".r

// convert "aaa.bbb.ccc" to "aaa"
private def parseFirstSegment(fullPath: String) : Option[String] = {
  if (fullPath.contains("."))
    fullPath match {
      case firstSegmentRE(segment) => Some(segment)
      case _ => None
    }
  else
    Some(fullPath)
}

// for all keys in white list get a map of key -> config
protected def subConfigMap(config: Config, whiteList: List[String], 
configName: String) : ErrorOr[Map[String, Config]] = {
  // This will traverse the whole config and flatten down to the leaves..
  val leafKeys : List[String] =
    config.entrySet()
      .asScala
      .map(e => e.getKey)
      .toList
  // Remove all after the first dot
  val nextLevelKeys : List[String] =
    leafKeys.map(parseFirstSegment)
      .collect {
        case Some(firstSegment) => firstSegment
      }
      .distinct
  val keysToSearch = nextLevelKeys.filter(whiteList.contains)
  // we have a list of valid first level children
  // parse out subconfigs and convert to map
  keysToSearch.traverseErrorOr( key =>
    extractSubConfig(config, key, configName).map((key, _))
  )
  .map(_.toMap)
}

Where extractSubConfig is a method which produces an ERROR / Config (a scalaz disjunction) and traverseErrorOr is a method to traverse a list and either process all the elements or fail and return failed disjunction if failed at any point.其中,extractSubConfig 是一种产生 ERROR / Config(scalaz 析取)的方法,而 traverseErrorOr 是一种遍历列表的方法,要么处理所有元素,要么失败并在任何时候失败并返回失败的析取。 This method can be done without scalaz, just posting an answer to help people if they want.这种方法可以在没有 scalaz 的情况下完成,只需发布​​一个答案来帮助人们(如果他们愿意)。

Since collection.JavaConversions has since been deprecated in favor of collection.JavaConverters (and this question is the top result on a search for how to iterate through a Typesafe Config in Scala), I wanted to suggest a more modern version of Cole's great answer:由于 collection.JavaConversions 已被弃用,取而代之的是 collection.JavaConverters(这个问题是搜索如何在 Scala 中迭代 Typesafe 配置的最佳结果),我想建议一个更现代的 Cole 优秀答案版本:

import collection.JavaConverters._

val socialConfig = ConfigFactory.load.getConfig("social")

for ( (name: String, configObject: ConfigObject) <- socialConfig.root.asScala) {
    println(name) // prints "twitter" or "facebook"

    val config = configObject.toConfig
    println(config.getString("url"))
    println(config.getString("logo"))
}

To be clear, socialConfig.root.asScala yields a standard Scala Map[String, ConfigObject] and you can iterate over it however you'd like.需要明确的是,socialConfig.root.asScala 产生一个标准的 Scala Map[String, ConfigObject],你可以随意迭代它。

import collection.JavaConversions._
val socialConfig = ConfigFactory.load.getConfig("social")

val socialConfigMap = socialConfig
  .root()
  .entrySet()
  .asScala
  .map(socialEntry => {
    println(socialEntry.getKey)
    val socialEntryConfig = socialEntry.getValue.asInstanceOf[ConfigObject].toConfig

    println(socialEntryConfig.getString("url"))
    println(socialEntryConfig.getString("logo"))
  })

For the config mentioned originally in question:对于最初提到的配置:

social {
  twitter {
    url="https://twitter.com"
    logo="images/twitter.png"
  }
  facebook {
    url="https://www.facebook.com"
    logo="images/facebook.png"
  }
}

A solution in Java is: Java中的解决方案是:

val socialConfig = ConfigFactory.load.getConfig("social");
ConfigList socials = socialConfig.getList("social");

Map<String, Object> map = socials.root().unwrapped();
for (Map.Entry<String, Object> cv : map.entrySet()) {
  Map<String, String> c = (Map<String, String>)cv.getValue();
  System.out.println(cv.getKey());
  System.out.println(c.getOrDefault("url", ""));
  System.out.println(c.getOrDefault("logo", ""));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM