1

I'm trying to get nested case classes to deserialize with pureconfig, but the code fails to compile. I have something as follows:

import com.typesafe.config.{Config, ConfigFactory}
import pureconfig.error.ConfigReaderFailures
import pureconfig.generic.ProductHint
import pureconfig.generic.semiauto._
import pureconfig.{CamelCase, ConfigFieldMapping}

class ClassAReader {
  implicit def classAHint = ProductHint[ClassA](ConfigFieldMapping(CamelCase, CamelCase))
  implicit def classBHint = ProductHint[ClassB](ConfigFieldMapping(CamelCase, CamelCase))

  implicit val classAReader = deriveReader[ClassA]
  implicit val classBReader = deriveReader[ClassB]

  def read(config: Config): ClassA = pureconfig.loadConfig[ClassA](config.getConfig("foo")).fold((a: ConfigReaderFailures) => throw new Exception(), conf => conf)

  val config = ConfigFactory.parseString("""{ a: { one: 1, two: "foo" }, b: { one: 2, two: "bar" }, 42 }""")
  val reader = new ClassAReader

  reader.read(config)
}

case class ClassA(a: ClassB, b: ClassB, other: Int)

case class ClassB(one: Int, two: String)

But I'm getting compilation issues. Anyone know what I'm doing wrong?

user1178488
  • 51
  • 2
  • 7

1 Answers1

3

You should always annotate implicit definitions, especially in situations like this, where the derivation of the reader for ClassA depends on the one for ClassB, etc. The following just works:

import com.typesafe.config.{Config, ConfigFactory}
import pureconfig.ConfigReader
import pureconfig.error.ConfigReaderFailures
import pureconfig.generic.ProductHint
import pureconfig.generic.semiauto._
import pureconfig.{CamelCase, ConfigFieldMapping}

class ClassAReader {
  implicit def classAHint: ProductHint[ClassA] =
    ProductHint[ClassA](ConfigFieldMapping(CamelCase, CamelCase))
  implicit def classBHint: ProductHint[ClassB] =
    ProductHint[ClassB](ConfigFieldMapping(CamelCase, CamelCase))

  implicit val classAReader: ConfigReader[ClassA] = deriveReader[ClassA]
  implicit val classBReader: ConfigReader[ClassB] = deriveReader[ClassB]

  def read(config: Config): Either[ConfigReaderFailures, ClassA] =
    pureconfig.loadConfig[ClassA](config)
}

case class ClassA(a: ClassB, b: ClassB, other: Int)
case class ClassB(one: Int, two: String)

And then:

scala> val config = ConfigFactory.parseString(
     |   """{ a: { one: 1, two: "foo" }, b: { one: 2, two: "bar" }, other: 42 }"""
     | )
config: com.typesafe.config.Config = Config(SimpleConfigObject({"a":{"one":1,"two":"foo"},"b":{"one":2,"two":"bar"},"other":42}))

scala> val reader = new ClassAReader
reader: ClassAReader = ClassAReader@589da48f

scala> reader.read(config)
res1: Either[pureconfig.error.ConfigReaderFailures,ClassA] = Right(ClassA(ClassB(1,foo),ClassB(2,bar),42))

(Note that I also had to move the read call out of the class—otherwise the constructor is calling itself and you'll get a Stack Overflow. I also fixed the example so it can actually be decoded.)

Travis Brown
  • 138,631
  • 12
  • 375
  • 680