Search code examples
scalatypestypesafepureconfig

assign any val scala pureconfig during configuration read


I know this is going against the very nature of Scala pureconfig ... however ... Is it even feasible to implement with scala pureconfig configuration reading for this case class, so that instead of having strongly typed value( as String) for the constructor parameter "variable" to have Any type or at least String, Integer, Double, Array[Strings], Array[Integer], Array[Double].

  case class Filter(
  field: String,
  operator: String,
  variable: String  // should support Int , Double , List[String], List[Int]
  )

To my poor understanding, neither CoProductHint nor Custom Reader approach will work ...


Solution

  • By default pureconfig doesn't provide a way to read Any. If for a specific class you would like to read Any then you can define a codec for Any in the context of that class:

    case class Filter(field: String, operator: String, variable: Any)
    
    implicit val readFilter = {
      implicit val readAny = new ConfigReader[Any] {
        def from(config: ConfigValue): Either[ConfigReaderFailures, Any] = {
          Right(config.unwrapped())
        }
      }
      ConfigReader[Filter]
    }
    

    and then you can read Filter

    val config = ConfigFactory.parseString(
        """
        {
          field: "foo"
          operator: "bar"
          variable: []
        }
        """)
    println(pureconfig.loadConfig[Filter](config))
    // will print Right(Filter(foo,bar,[]))
    

    unwrapped converts a ConfigValue to Any recursively.

    So the answer is yes, it if possible to tell pureconfig how to read Any.

    The reason why pureconfig doesn't provide the codec for Any by default is because Any is the ancestor of all the classes in Scala and it's impossible to create a codec for anything (e.g. database connections). When you know that you are expecting a restricted set of types, like the ones you listed, you can wrap everything in a coproduct:

    sealed abstract class MySupportedType
    final case class MyInt(value: Int) extends MySupportedType
    final case class MyDouble(value: Double) extends MySupportedType
    final case class MyListOfString(value: List[String]) extends MySupportedType
    final case class MyListOfInt(value: List[Int]) extends MySupportedType
    
    final case class Filter2(field: String, operator: String, variable: MySupportedType)
    

    and then use the default way to extract the coproduct value or a custom codec for MySupportedType

    val config = ConfigFactory.parseString(
        """
        {
          field: "foo"
          operator: "bar"
          variable: {
            type: mylistofint
            value: []
          }
        }
        """)
      println(pureconfig.loadConfig[Filter2](config))
      // will print Right(Filter2(foo,bar,MyListOfInt(List())))
    

    Using a coproduct instead of Any limits the possible values that variable can have and let the compiler help you if something is wrong with what you are doing.