Skip to content

Scala library for boilerplate-free, type-safe data transformations

License

Notifications You must be signed in to change notification settings

ghostdogpr/chimney

Repository files navigation

Chimney Chimney logo

chimney Scala version support

CI build codecov.io License Join the chat at https://gitter.im/scalalandio/chimney

Build docs Scaladoc 2.11 Scaladoc 2.12 Scaladoc 2.13

Battle tested Scala library for boilerplate-free data transformations.

In the daily life of a strongly-typed language's programmer sometimes it happens we need to transform an object of one type to another object which contains a number of the same or similar fields in their definitions.

case class MakeCoffee(id: Int, kind: String, addict: String)
case class CoffeeMade(id: Int, kind: String, forAddict: String, at: ZonedDateTime)

Usual approach is to just rewrite fields one by one

val command = MakeCoffee(id = Random.nextInt,
                         kind = "Espresso",
                         addict = "Piotr")
val event = CoffeeMade(id = command.id,
                       kind = command.kind,
                       forAddict = command.addict,
                       at = ZonedDateTime.now)

While the example stays lean, in real-life code we usually end up with tons of such boilerplate, especially when:

  • we keep separate models of different layers in the system
  • we apply practices like DDD (Domain-Driven-Design) where suggested approach is to separate models of different bounded contexts
  • we use code-generation tools like Protocol Buffers that generate primitive types like Int or String, while you'd prefer to use value objects in your domain-level code to improve type-safety and readability
  • we maintain typed, versioned schemas and want to migrate between multiple schema versions

Chimney provides a compact DSL with which you can define transformation rules and transform your objects with as little boilerplate as possible.

import io.scalaland.chimney.dsl._

val event = command.into[CoffeeMade]
  .withFieldComputed(_.at, _ => ZonedDateTime.now)
  .withFieldRenamed(_.addict, _.forAddict)
  .transform
  // CoffeeMade(24, "Espresso", "Piotr", "2020-02-03T20:26:59.659647+07:00[Europe/Warsaw]")

For computations that may potentially fail, Chimney provides partial transformers.

import io.scalaland.chimney.dsl._
import io.scalaland.chimney.partial._

case class UserForm(name: String, ageInput: String, email: Option[String])
case class User(name: String, age: Int, email: String)

UserForm("John", "21", Some("john@example.com"))
  .intoPartial[User]
  .withFieldComputedPartial(_.age, form => Result.fromCatching(form.ageInput.toInt))
  .transform
  .asOption  // Some(User("name", 21, "john@example.com"))

val result = UserForm("Ted", "eighteen", None)
  .intoPartial[User]
  .withFieldComputedPartial(_.age, form => Result.fromCatching(form.ageInput.toInt))
  .transform
  
result.asOption // None
result.asErrorMessageStrings 
// Iterable("age" -> "For input string: \"eighteen\"", "email" -> "empty value")

The library uses Scala macros underneath, to give you:

  • type-safety at compile-time
  • efficient generated code, almost equivalent to hand-written version
  • excellent compilation error messages
  • minimal overhead on compilation time

Getting started

To include Chimney to your SBT project, add the following line to your build.sbt:

// if you use Scala on JVM-only
libraryDependencies += "io.scalaland" %% "chimney" % chimneyVersion
// if you cross-compile to Scala.js and/or Scala Native
libraryDependencies += "io.scalaland" %%% "chimney" % chimneyVersion

where the latest versions available on Maven for each platform are

Chimney JVM versions
Chimney Scala.js 1.x versions
Chimney Scala.js 0.6 versions
Chimney Scala Native 0.4 versions
Chimney Scala Native 0.3 versions

Library is currently supported for Scala 2.12.x and 2.13.x on JVM, SJS 1.x, SN 0.4. Other versions should be considered EOL.

Due to some compiler bugs, it's recommended to use at least Scala 2.12.1.

Trying out with Scala CLI/Ammonite

If you are using Scala CLI you can try out Chimney by adding it with using clause:

//> using scala "2.13.10"
//> using dep "io.scalaland::chimney:0.7.1"
import io.scalaland.chimney.dsl._

case class Foo(x: String, y: Int, z: Boolean = true)
case class Bar(x: String, y: Int)

object Main extends App {
  println(Foo("abc", 10).transformInto[Bar])
  println(Bar("abc", 10).into[Foo].enableDefaultValues.transform)
}

or run the Ammonite REPL:

scala-cli --power repl --ammonite --scala "2.13.10" --dependency "io.scalaland::chimney:0.7.1"
Loading...
Welcome to the Ammonite Repl 2.5.6-1-f8bff243 (Scala 2.13.10 Java 17.0.1)
@ case class Foo(x: String, y: Int, z: Boolean = true)
defined class Foo

@ case class Bar(x: String, y: Int)
defined class Bar

@ import io.scalaland.chimney.dsl._
import io.scalaland.chimney.dsl._

@ Foo("abc", 10).transformInto[Bar]
res3: Bar = Bar(x = "abc", y = 10)

@ Bar("abc", 10).into[Foo].enableDefaultValues.transform
res4: Foo = Foo(x = "abc", y = 10, z = true)

If you don't have Scala CLI installed you can use this quick script that downloads coursier and uses it to fetch Ammonite REPL with the latest version of Chimney. It drops you immediately into a REPL session.

curl -s https://raw.githubusercontent.com/scalalandio/chimney/master/try-chimney.sh | bash

Documentation

Chimney documentation is available at https://scalalandio.github.io/chimney

Building documentation locally

In order to build documentation locally, you need to install Sphinx documentation generator first.

Then in project's root directory run command:

sbt makeSite

HTML Documentation should be generated at target/sphinx/html/index.html.

Alternatively use Docker:

docker run --rm -v "$PWD/docs:/docs" sphinxdoc/sphinx:3.2.1 bash -c "pip install sphinx-rtd-theme && make html"

Thanks

Thanks to JProfiler (Java profiler) for helping us develop the library and allowing us to use it during development.

Thanks to SwissBorg for sponsoring the development time for this project.

About

Scala library for boilerplate-free, type-safe data transformations

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Scala 100.0%