11

假设我有一个能够序列化为 json 的 scala 案例类(使用 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
  }

}

有没有办法使用类型安全配置来自动实例化一个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"))

但这似乎不优雅,因为对 Weather 定义的任何更改也需要更改为configToWeather.

提前感谢您的评论和回复。

4

5 回答 5

13

类型安全配置库具有API 来从使用 java bean 约定的配置中实例化对象。但据我了解,案例类不遵循这些规则。

几个 scala 库包装类型安全配置并提供您正在寻找的 scala 特定功能。

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

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

Weather配置中值的案例类在哪里

于 2016-07-27T16:51:11.443 回答
9

扩展 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])

跟进:根据下面的评论,可能只有 Java 集合而不是 Scala 集合是案例类参数的可行选项(例如Seq[T],将不起作用)。

于 2016-08-01T15:11:31.293 回答
1

一个没有外部库的简单解决方案,灵感来自 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")

在 Scastie 中运行代码

于 2021-08-08T06:57:57.330 回答
-1

使用配置加载器

implicit val configLoader: ConfigLoader[Weather] = (rootConfig: Config, path: String) => {

  val config = rootConfig.getConfig(path)

  Weather(
    config.getString("zip"),
    config.getDouble("temp"),
    config.getBoolean("isRaining")
  )
}
于 2021-10-11T20:28:57.477 回答
-1

另一种选择是使用 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")
  }
}
于 2018-12-03T01:08:32.860 回答