Typesafe config: How to iterate over configuration items

14.7k views Asked by At

In my Play application I've a configuration like this:

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?

<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.

The final objective would be to be able to add any further social url without having to modify the page template.

7

There are 7 answers

2
Paweł Kozikowski On BEST ANSWER

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>
1
Prashanth Acharya On

socialConfig.root.map does not work.

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. This method can be done without scalaz, just posting an answer to help people if they want.

0
Sync On

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:

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.

0
Rubber Duck On
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"))
  })
0
Rahul Singhai On

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:

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", ""));
}
0
Haiyuan Zhang On

In case you're using Java, for a config like:

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

this will work:

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"));
}
0
Cole Stanfield On

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. That's about as clean as I can get it.