Reputation: 351
I have a case class with 25 fields and need to convert it into another with 22, of which 19 of these are shared and 3 are simply renamed.
I have found a few examples of how to do this using shapeless
(e.g. an answer here and some code examples from Miles Sabin here and here) but the last of those looks a bit out of date, and I can't figure out from the Github example how I can use shapeless to rename multiple fields, or do more manipulation on a field before adding it to the new object. Can anyone help me out?
Simplified code example;
import shapeless.LabelledGeneric
case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)
val aGen = LabelledGeneric[A]
val bGen = LabelledGeneric[B]
val freddie = new A(1,"Freddie","somestring")
val record = aGen.to(freddie)
val atmp = freddie.fieldA
record.Remove("fielda")
val freddieB = bGen.from(record +
(Symbol("fieldARenamed") ->> atmp) +
(Symbol("fieldCTransformed") ->> freddie.fieldC.toUpperCase)
) //Errors everywhere, even if I replace + with :: etc.
I have a feeling Align
is going to come into the picture somewhere here, but understanding how to do this in the leanest possible fashion - e.g. without creating additional traits like Field
, as in that third link above - would be interesting.
In The Shapeless Guide, there is also some usage of a single quote, (e.g. 'fieldC
) notation, which I haven't been able to find much information on, so if that plays a role some explanation would also be really helpful. Fairly new to this depth of Scala sorcery, so apologies if the question seems obtuse or covers too many disparate topics.
EDIT: For the avoidance of doubt, I am not looking for answers which suggest that I just manually create a new case class by referencing fields from the first, as in;
val freddieB = B(fieldARenamed = freddie.fieldA, fieldB = freddie.fieldB, fieldC = freddie.fieldC, fieldCTransformed =freddie.fieldC.toUpperCase)
See below comment for various reasons why this is inappropriate.
Upvotes: 2
Views: 872
Reputation: 51271
Just FYI, here's one way to get your question code to work.
import shapeless._
import shapeless.labelled.FieldType
import shapeless.ops.hlist.{Align,Intersection}
import shapeless.syntax.singleton._
case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)
val fromGen = LabelledGeneric[A]
val toGen = LabelledGeneric[B]
val freddie = A(1, "Freddie", "somestring")
val putARename = Symbol("fieldARenamed") ->> freddie.fieldA
val putCTrans = Symbol("fieldCTransformed") ->> freddie.fieldC.toUpperCase
trait Field { type K; type V; type F = FieldType[K, V] }
object Field {
def apply[K0,V0](sample: FieldType[K0,V0]) =
new Field { type K = K0; type V = V0 }
}
val pFieldA = Field(putARename)
val pFieldCT = Field(putCTrans)
val inter = Intersection[pFieldA.F :: pFieldCT.F :: fromGen.Repr, toGen.Repr]
val align = Align[inter.Out, toGen.Repr]
toGen.from(align(inter(putARename :: putCTrans :: fromGen.to(freddie))))
//res0: B = B(1,Freddie,somestring,SOMESTRING)
Upvotes: 1
Reputation: 27356
The simplest solution is to construct an instance of the new case class
using the values from the old one, applying functions to the values as necessary. The code will be very efficient, the purpose of the code will be very clear, it will take less time to write than any other solution, it will be more robust and maintainable than a solution that depends on third-party libraries, and it avoids a hidden dependency between the two classes.
Upvotes: 2
Reputation: 26921
One other option is to use automapper; in particular, Dynamic Mappings feature.
For your particular example it would look like the following:
import io.bfil.automapper._
case class A(fieldA:Int, fieldB:String, fieldC:String)
case class B(fieldARenamed:Int, fieldB:String, fieldC:String, fieldCTransformed:String)
val freddie = new A(1,"Freddie","somestring")
val freddieB = automap(freddie).dynamicallyTo[B](
fieldARenamed = freddie.fieldA,
fieldCTransformed = freddie.fieldC.toUpperCase
)
and I guess you can make it a function
def atob(a: A): B = {
automap(a).dynamicallyTo[B](
fieldARenamed = a.fieldA,
fieldCTransformed = a.fieldC.toUpperCase
)
}
From efficiency point of view, this lib uses macros, so the generated code is practically as good as one could've written by hand
Upvotes: 2