Search code examples
scalapureconfig

Pureconfig read config as properties map


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"

Solution

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