Reputation: 9521
Is it possible to make pureconfig read properties as Map[String, String]
? I have the following
application.conf
:
cfg{
some.property.name: "value"
some.another.property.name: "another value"
}
Here is the application I tried to read the config with:
import pureconfig.generic.auto._
import pureconfig.ConfigSource
import pureconfig.error.ConfigReaderException
object Model extends App {
case class Config(cfg: Map[String, String])
val result = ConfigSource.default
.load[Config]
.left
.map(err => new ConfigReaderException[Config](err))
.toTry
val config = result.get
println(config)
}
The problem is it throws the following excpetion:
Exception in thread "main" pureconfig.error.ConfigReaderException: Cannot convert configuration to a Model$Config. Failures are:
at 'cfg.some':
- (application.conf @ file:/home/somename/prcfg/target/classes/application.conf: 2-3) Expected type STRING. Found OBJECT instead.
at Model$.$anonfun$result$2(Model.scala:11)
at scala.util.Either$LeftProjection.map(Either.scala:614)
at Model$.delayedEndpoint$Model$1(Model.scala:11)
at Model$delayedInit$body.apply(Model.scala:5)
at scala.Function0.apply$mcV$sp(Function0.scala:39)
at scala.Function0.apply$mcV$sp$(Function0.scala:39)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
at scala.App.$anonfun$main$1(App.scala:73)
at scala.App.$anonfun$main$1$adapted(App.scala:73)
at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:553)
at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:551)
at scala.collection.AbstractIterable.foreach(Iterable.scala:920)
at scala.App.main(App.scala:73)
at scala.App.main$(App.scala:71)
at Model$.main(Model.scala:5)
at Model.main(Model.scala)
Is there a way to fix it? I expected that the Map[String, String]
will contain the following mappings:
some.property.name -> "value"
some.another.property.name -> "another value"
Upvotes: 2
Views: 2613
Reputation: 4587
You can read a Map[String, String]
in that way with the following ConfigReader
:
implicit val strMapReader: ConfigReader[Map[String, String]] = {
implicit val r: ConfigReader[String => Map[String, String]] =
ConfigReader[String]
.map(v => (prefix: String) => Map(prefix -> v))
.orElse { strMapReader.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) }
}
}
Note that this is a recursive val
definition, because strMapReader
is used within its own definition. The reason it works is that the orElse
method takes its parameter by name and not by value.
Upvotes: 3
Reputation: 27535
Your issue is not pureconfig. Your issue is that by HOCON spec what you wrote:
cfg {
some.property.name: "value"
some.another.property.name: "another value"
}
is a syntactic sugar for:
cfg {
some {
property {
name = "value"
}
}
another {
property {
name = "another value"
}
}
}
It's TypeSafe Config/Lightbend Config who decides that your cfg
has two properties and both of them are nested configs. Pureconfig only takes these nested configs and maps them into case classes. But it won't be able to map something which has a radically different structure then expected.
If you write:
cfg {
some-property-name: "value"
some-another-property-name: "another value"
}
You'll be able to decode "cfg"
path as Map[String, String]
and top level config as case class Config(cfg: Map[String, String])
. If you wanted to treat .
as part of the key and not nesting... then I'm afraid you have to write a ConfigReader
yourself because that is non-standard usage.
Upvotes: 5