Plugging in own transformations

In case the transformation is relatively complex or if for some reason you just want to bypass Chimney derivation mechanism, you can always fall back to a simple function that you can plug into the Chimney transformation.

Transformer type class

The library defines a type class Transformer that just wraps transformation function.

trait Transformer[From, To] {
  def transform(src: From): To
}

You can plug your own transformer in by providing implicit instance in a local context.

import io.scalaland.chimney.dsl._
import io.scalaland.chimney.Transformer

object v1 {
  case class User(id: Int, name: String, street: String, postalCode: String)
}
object v2 {
  case class Address(street: String, postalCode: String)
  case class User(id: Int, name: String, addresses: List[Address])
}

implicit val userV1toV2: Transformer[v1.User, v2.User] =
  (user: v1.User) => v2.User(
    id = user.id,
    name = user.name,
    addresses = List(v2.Address(user.street, user.postalCode))
  )

val v1Users = List(
  v1.User(1, "Steve", "Love street", "27000"),
  v1.User(2, "Anna", "Broadway", "00321")
)

val v2Users = v1Users.transformInto[List[v2.User]]
// List(
//   v2.User(1, "Steve", List(Address("Love street", "27000"))),
//   v2.User(2, "Anna", List(Address("Broadway", "00321")))
// )

As we can see, Chimney correctly picked and used implicit Transformer[v1.User, v2.User] defined locally in transformation between list of users.

But is it really a necessity to define custom transformer completely manually?

Transformer definition DSL

One can think that if we only need to provide function implementation of type v1.User => v2.User, why not use Chimney’s DSL in order to generate the transformation?

implicit val userV2toV2: Transformer[v1.User, v2.User] =
  (user: v1.User) => user
    .into[v2.User]
    .withFieldComputed(_.addresses, u => List(v2.Address(u.street, u.postalCode)))
    .transform

Warning

While it looks reasonably, it will not work as expected :(

Chimney’s macro, before trying to derive any transformer, tries to find instance of required transformer in implicit scope. Unfortunately, it will pick userV2toV2, because types match, this value is marked as implicit and is available in macro expansion scope. Depending on few details, it will either end up as compilation error, or will lead to StackOverflowError at runtime.

Note

Since version 0.4.0 there is a simple solution to this problem.

We need to use special syntax Transformer.define[From, To] which introduces us to new transformer builder between types From and To.

implicit val userV2toV2: Transformer[v1.User, v2.User] =
  Transformer.define[v1.User, v2.User]
    .withFieldComputed(_.addresses, u => List(v2.Address(u.street, u.postalCode)))
    .buildTransformer

In transformer builder we can use all operations available to usual transformer DSL. The only difference is that we don’t call .transform at the end (since we don’t transform value in place), but buildTransformer (because we generate transformer here). Such generated transformer is semantically equivalent to hand-written transformer from previous section.

Chimney solves self reference implicit problem by not looking for implicit instance for Transformer[From, To] when using transformer builder Transformer.define[From, To].

Recursive data types support

Chimney can generate transformers between recursive data structures. Consider following example.

case class Foo(x: Option[Foo])
case class Bar(x: Option[Bar])

We would like to define transformer instance which would be able to convert a value Foo(Some(Foo(None))) to Bar(Some(Bar(None))). In order to avoid aforementioned issues with self-referencing, you must define your recursive transformer instance as implicit def or implicit lazy val.

implicit def fooToBarTransformer: Transformer[Foo, Bar] =
  Transformer.derive[Foo, Bar] // or Transformer.define[Foo, Bar].buildTransformer

Foo(Some(Foo(None))).transformInto[Bar]
// Bar(Some(Bar(None)))