来自Typesafe配置的案例类实例化

时间:2016-07-27 13:58:13

标签: scala typesafe-config hocon

假设我有一个scala case类,可以序列化为json(使用json4s或其他一些库):

case class Weather(zip : String, temp : Double, isRaining : Boolean)

如果我使用HOCON配置文件:

allWeather {

   BeverlyHills {
    zip : 90210
    temp : 75.0
    isRaining : false
  }

  Cambridge {
    zip : 10013
    temp : 32.0
    isRainging : true
  }

}

有没有办法使用typesafe config自动实例化Weather对象?

我正在寻找表格

val config : Config = ConfigFactory.parseFile(new java.io.File("weather.conf"))

val bevHills : Weather = config.getObject("allWeather.BeverlyHills").as[Weather]

该解决方案可以利用"allWeather.BeverlyHills"引用的值是json“blob”的事实。

我显然可以编写自己的解析器:

def configToWeather(config : Config) = 
  Weather(config.getString("zip"), 
          config.getDouble("temp"), 
          config.getBoolean("isRaining"))

val bevHills = configToWeather(config.getConfig("allWeather.BeverlyHills"))

但这似乎不够优雅,因为对天气定义的任何更改都需要更改为configToWeather

提前感谢您的审核和回复。

4 个答案:

答案 0 :(得分:11)

typesafe配置库有来自config的API to instantiate对象,它使用java bean约定。但据我所知,案例类不遵循这些规则。

several scala libraries包装typesafe配置并提供您正在寻找的scala特定功能。

例如,使用pureconfig阅读配置可能看起来像

val weather:Try[Weather] = loadConfig[Weather]

其中Weather是config

中值的案例类

答案 1 :(得分:7)

扩展Nazarii的回答,以下内容对我有用:

import scala.beans.BeanProperty

//The @BeanProperty and var are both necessary
case class Weather(@BeanProperty var zip : String,
                   @BeanProperty var temp : Double,
                   @BeanProperty var isRaining : Boolean) {

  //needed by configfactory to conform to java bean standard
  def this() = this("", 0.0, false)
}

import com.typesafe.config.ConfigFactory

val config = ConfigFactory.parseFile(new java.io.File("allWeather.conf"))

import com.typesafe.config.ConfigBeanFactory

val bevHills = 
  ConfigBeanFactory.create(config.getConfig("allWeather.BeverlyHills"), classOf[Weather])

答案 2 :(得分:0)

另一个选择是将circe.config与以下代码一起使用。参见https://github.com/circe/circe-config

import io.circe.generic.auto._
import io.circe.config.syntax._

def configToWeather(conf: Config): Weather = {
  conf.as[Weather]("allWeather.BeverlyHills") match {
    case Right(c) => c
    case _ => throw new Exception("invalid configuration")
  }
}

答案 3 :(得分:0)

一个没有外部库的简单解决方案,灵感来自 playframework Configuration.scala

trait ConfigLoader[A] { self =>
  def load(config: Config, path: String = ""): A
  def map[B](f: A => B): ConfigLoader[B] = (config, path) => f(self.load(config, path))
}
object ConfigLoader {
  def apply[A](f: Config => String => A): ConfigLoader[A] = f(_)(_)
  implicit val stringLoader: ConfigLoader[String] = ConfigLoader(_.getString)
  implicit val booleanLoader: ConfigLoader[Boolean] = ConfigLoader(_.getBoolean)
  implicit val doubleLoader: ConfigLoader[Double] = ConfigLoader(_.getDouble)
}
object Implicits {
  implicit class ConfigOps(private val config: Config) extends AnyVal {
    def apply[A](path: String)(implicit loader: ConfigLoader[A]): A = loader.load(config, path)
  }
  implicit def configLoader[A](f: Config => A): ConfigLoader[A] = ConfigLoader(_.getConfig).map(f)
}

用法:

import Implicits._

case class Weather(zip: String, temp: Double, isRaining: Boolean)
object Weather {
  implicit val loader: ConfigLoader[Weather] = (c: Config) => Weather(
    c("zip"), c("temp"), c("isRaining")
  )
}

val config: Config = ???
val bevHills: Weather = config("allWeather.BeverlyHills")

Run the code in Scastie