Pureconfig читает конфигурацию как карту свойств

Можно ли сделать так, чтобы pureconfig читал свойства как Map[String, String]? У меня есть следующие

application.conf:

cfg{
  some.property.name: "value"
  some.another.property.name: "another value"
}

Вот приложение, с помощью которого я пытался прочитать конфигурацию:

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)
}

Проблема в том, что выкидывает следующее исключение:

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)

Есть способ исправить? Я ожидал, что Map[String, String] будет содержать следующие сопоставления:

some.property.name -> "value"
some.another.property.name -> "another value"

person Some Name    schedule 16.10.2020    source источник


Ответы (2)


Ваша проблема не в pureconfig. Ваша проблема в том, что по спецификации HOCON вы написали:

cfg {
  some.property.name: "value"
  some.another.property.name: "another value"
}

синтаксический сахар для:

cfg {
  some {
    property {
      name = "value"
    }
  }
  
  another {
    property {
      name = "another value"
    }
  }
}

Это TypeSafe Config / Lightbend Config решает, что ваш cfg имеет два свойства, и оба они являются вложенными конфигурациями. Pureconfig принимает только эти вложенные конфигурации и сопоставляет их с классами case. Но он не сможет отобразить что-то, что имеет радикально другую структуру, чем ожидалось.

Если вы напишете:

cfg {
  some-property-name: "value"
  some-another-property-name: "another value"
}

Вы сможете декодировать "cfg" путь как Map[String, String] и конфигурацию верхнего уровня как case class Config(cfg: Map[String, String]). Если вы хотите рассматривать . как часть ключа, а не как вложенность ... тогда, боюсь, вам придется писать ConfigReader самостоятельно, потому что это нестандартное использование.

person Mateusz Kubuszok    schedule 16.10.2020
comment
Предыстория проблем состоит в том, чтобы позволить пользователям добавлять параметры конфигурации в формате файла свойств. - person Some Name; 17.10.2020

Вы можете прочитать Map[String, String] таким образом со следующим 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) }
  }
}

Обратите внимание, что это рекурсивное определение val, потому что strMapReader используется в своем собственном определении. Причина, по которой он работает, заключается в том, что метод orElse принимает свой параметр по имени, а не по значению.

person Matthias Berndt    schedule 17.10.2020