Customizing transformers

Let’s add a field to our Butterfly case class.

case class Catterpillar(size: Int, name: String)
case class Butterfly(size: Int, name: String, wingsColor: String)

Now, when trying to perform the same transformation as in Getting started with transformers, we get compile-time error. This is naturally expected, as we don’t have any data source for new wingsColor field.

val stevie = Catterpillar(5, "Steve")
val steve = stevie.transformInto[Butterfly]
// error: Chimney can't derive transformation from Catterpillar to Butterfly
// Butterfly
//   wingsColor: String - no accessor named wingsColor in source type Catterpillar
// Consult for usage examples.
//        val steve = stevie.transformInto[Butterfly]

Providing missing values

In this scenario, we can use Chimney’s syntax to provide a missing value. Notice that transformInto[T] is a shortcut for into[T].transform, where the latter form allow us to provide additional transformation rules.

With withFieldConst operation, we can provide exact value for specific field.

val steve = stevie.into[Butterfly]
  .withFieldConst(_.wingsColor, "white")
// Butterfly(5, "Steve", "white")

With withFieldComputed operation we can construct field value dynamically, by providing a function.

val steve = stevie.into[Butterfly]
  .withFieldComputed(_.wingsColor, c => if(c.size > 4) "yellow" else "gray")
// Butterfly(5, "Steve", "yellow")

Fields renaming

Sometimes a field only change its name. In such case you can use withFieldRenamed operation to instruct the library about performed renaming.

case class SpyGB(name: String, surname: String)
case class SpyRU(imya: String, familia: String)

val jamesGB = SpyGB("James", "Bond")

val jamesRU = jamesGB.into[SpyRU]
    .withFieldRenamed(, _.imya)
    .withFieldRenamed(_.surname, _.familia)
// SpyRU("James", "Bond")

Using method accessors

By default, Chimney will only consider val and lazy val defined within the source type, because methods may perform side effects (e.g. mutation some state in the source object).

You can ask Chimney to consider methods with .enableMethodAccessors. Note that only methods that are public and have no parameter list are considered.

case class Foo(a: Int) {
  def m: String = "m"
case class FooV2(a: Int, m: String)

// FooV2(1, "m")

Transforming coproducts

With Chimney you can not only transform case classes, but sealed trait hierarchies (also known as coproducts) as well. Consider two following hierarchy definitions.

sealed trait Color
object Color {
  case object Red extends Color
  case object Green extends Color
  case object Blue extends Color

sealed trait Channel
object Channel {
  case object Alpha extends Channel
  case object Blue extends Channel
  case object Green extends Channel
  case object Red extends Channel

Because of object names correspondence, we can transform Color to a Channel in a simple way.

val colRed: Color = Color.Red
val chanRed = colRed.transformInto[Channel]
// chanRed: Channel = Red

How about other way round?

// error: Chimney can't derive transformation from Channel to Color
// Color
//   can't transform coproduct instance Channel.Alpha to Color
// Consult for usage examples.
//        chanRed.transformInto[Color]
//                             ^

This time we tried to transform a Channel to a Color. Notice that in this case we don’t have defined case object in target hierarchy with corresponding name for case object Alpha. Wanting to keep the transformation total, we need to somehow provide a value from a target domain. We can use withCoproductInstance to do that. Let’s convert any Channel.Alpha to Color.Blue.

val red = chanRed.into[Color]
  .withCoproductInstance { (_: Channel.Alpha.type) => Color.Blue }
// red: Color = Red

val alpha: Channel = Channel.Alpha
val blue = alpha.into[Color]
  .withCoproductInstance { (_: Channel.Alpha.type) => Color.Blue }
// blue: Color = Blue

After providing a default, Chimney can prove the transformation is total and use provided function, when it’s needed.