Reputation: 651
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
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
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