richid
richid

Reputation: 651

Scala: Generic solution to implicitly convert pairs of case classes

I'm looking for a generic way to convert pairs of case classes using implicits. To make this concrete, I'm writing a new configuration library that must maintain backward compatibility with an existing configuration library. In order to make this transition as painless as possible I'm hoping to use implicits to convert the new case classes to their older counterparts on-demand.

In the example below the the 1 classes are in the old library and the 2 classes are from the new library. I'm using apply on the companion objects to extract the actual data from the Config instance (using Typesafe's config library).

package older {
  case class A1(a: String)
  case class B1(b: Int)
}

package newer {
  case class A2(a: String)
  case class B2(b: Int)

  object A2 {
    def apply(cfg: Config): A2 = A2(a = cfg.getString("a"))
    def toOlder(a2: A2) = older.A1(a = a2.a)
  }

  object B2 {
    def apply(cfg: Config): B2 = B2(b = cfg.getInt("b"))
    def toOlder(b2: B2) = older.B1(b = b2.b)
  }
}

Ideally, I wouldn't have to write an implicit function to convert each of the case classes (A1 -> A2, B1 -> B2, etc) but could use a single generic implicit to handle all of them. The goal is to be able to use an instance of A2 for the a1 method below by simply importing the implicit:

trait Foo {
  def a1: older.A1
}

I've been banging my head against the wall for a couple hours now and can't come up with the solution.

Thanks in advance.

Upvotes: 0

Views: 296

Answers (2)

richid
richid

Reputation: 651

Naturally, after I walked away from the problem for a couple hours I came up with the solution. Embarrassingly, it's pretty straightforward too.

package older {
    case class A1(a: String)
    case class B1(b: Int)
}

package newer {

    sealed trait Convertable[A <: AnyRef, B <: AnyRef] {
        implicit val formats = DefaultFormats

        implicit def convert(new: A)(implicit mB: Manifest[B]): B = {
            read[B](write(new))
        }
    }

    case class A2(a: String)
    case class B2(b: Int)

    object A2 extends Convertable[A2, older.A1] {
        def apply(cfg: Config): A2 = A2(a = cfg.getString("a"))
    }

    object B2 extends Convertabe[B2, older.B1] {
        def apply(cfg: Config): B2 = B2(b = cfg.getInt("b"))
    }
}

Also note that I used Lift-JSON's ability to transform case classes to JSON and back to perform the actual instantiation. Seemed easier than messing with reflection.

Thanks for help!

Upvotes: 0

Paweł Jurczenko
Paweł Jurczenko

Reputation: 4471

Does it solve your problem, or did I misunderstand something?

implicit final def aToOlder(self: A2): A1 = A1(a = self.a)
implicit final def bToOlder(self: B2): B1 = B1(b = self.b)

Usage:

val old: A1 = A1("John")
val oldFromNew: A1 = A2("Doe") // This will be implicitly converted from the `A2` instance to the `A1` instance

Upvotes: 1

Related Questions