【问题标题】:Case Class Instantiation From Typesafe Config类型安全配置中的案例类实例化
【发布时间】:2016-12-01 14:09:06
【问题描述】:

假设我有一个能够序列化为 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
  }

}

有没有办法使用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

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

【问题讨论】:

    标签: scala typesafe-config hocon


    【解决方案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")
      )
    }
    

    【讨论】:

    • 非常感谢您提供的答案。但是,在问题中,我明确表示倾向于避免这种类型的解决方案。
    【解决方案2】:

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

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

    【讨论】:

    • 请注意,所有三个无参数构造函数 @BeanProperyvar 都需要才能完成这项工作。如果您忘记了var,您将默默地得到一个空值(在构造函数中分配)而不是配置的值。
    • 如果成员变量之一是Seq[T],这显然是行不通的
    • 跟进上面的评论:你应该只使用 Java 集合
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      另一种选择是使用 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")
        }
      }
      

      【讨论】:

        【解决方案5】:

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

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

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

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

        其中Weather 是配置中值的案例类

        【讨论】:

        • 纯配置 +1。熟悉 Spring Boot 的优秀 configuration management,pureconfig 是我在 Scala 中能找到的最接近的东西。
        猜你喜欢
        • 2015-02-28
        • 2013-08-21
        • 2020-07-29
        • 1970-01-01
        • 1970-01-01
        • 2020-07-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多