Chimney Documentation

repository·master·Indexed 23 days ago

https://github.com/scalalandio/chimney

A battle-tested Scala library for efficient and type-safe data transformations, primarily used for mapping between different case classes or data models. It supports Scala 2.13 and 3.8.4+ across JVM, Scala.js, and Scala Native. The library provides tools for total and partial transformations, including a derivation engine that can be extended via ChimneyMacroExtension and SpecialCaseHandler, as well as support for custom optional types and collection types through OptionalValue and PartiallyBuildIterable traits.

Tokens
86.4K
Snippets
172
Records
275
Agent score
79%

What's inside Chimney

  1. Overview of Chimney

    master

    Chimney is a battle-tested Scala library designed for data transformations. It facilitates mapping between different data structures (e.g., converting one case class to another).

    Supported Platforms:

    • Scala versions: 2.13, 3.8.4+
    • Runtimes: JVM, Scala.js, Scala Native

    **Runtime Requirements (JVM):

    • For Scala 2.13 artifacts: JDK 11+
    • For Scala 3 artifacts: JDK 17+

    Chimney is powered by Hearth.

  2. Overview of Chimney features and capabilities

    master

    Chimney is a powerful tool for mapping between types. Key features include:

    • Automatic Mapping: Between any class and any class with a public constructor (including tuples), and between sealed types, enums, and collections.
    • Nested Field Access: Ability to provide/compute values for nested fields using paths like _.nested.field, including support for Option (_.matchingSome), Either (_.matchingLeft/_.matchingRight), Iterable (_.everyItem), and Map (_.everyMapKey/_.everyMapIndex).
    • Specialized Support: Automatic wrapping/unwrapping of AnyVals, opt-in support for reading from def methods or inherited values, and support for Java Bean getters/setters.
    • Partial Transformations: PartialTransformer for transformations that may fail, providing fail-fast or full conversion modes.
    • Integrations: Support for Java collections, Cats, Protocol Buffers, and custom optional/collection types.
  3. Available Third-party Integrations

    master

    Several libraries provide pre-built support for Chimney:

    • Enumz: Provides integration for working with enumeration types (sealed traits, Scala 3 enums, Java enums, scala.Enumeration) uniformly.
    • Neotype: Scala 3 only; provides support for working with opaque types.
    • Refined4s: Scala 3 only; provides support for working with opaque types.
    • Utils: Provides an integration between ZIO Prelude and Chimney.
  4. Reuse chimney-macro-commons utilities

    master

    The chimney-macro-commons module provides non-Chimney-specific macro utilities that can be used to build other macro libraries. It is a standalone artifact that does not depend on Chimney runtime types.

    Key capabilities include:

    • Extracting values and nullary defs from any class.
    • Extracting public constructors and setters.
    • Converting between singleton Type[A] and Expr[A].
    • Providing platform-agnostic utilities for common types and expressions.

    Note: Since Chimney 2.0.0, Chimney's derivation engine is built on top of Hearth, but chimney-macro-commons remains maintained as a standalone library.

  5. Transform into a case class or POJO

    master

    Chimney can transform any type into a target class (like a case class or POJO) by matching field names in the source with constructor arguments in the target.

    • Source: Every val can be used as a data source.
    • Target: Any class with a public primary constructor (or exactly one public constructor) can be the target.
    • Matching: By default, Chimney matches fields by name. It handles cases where the source has extra fields or where fields are in a different order.
    • Recursion: Transformations are applied recursively. If a field in the target is a different type, Chimney will attempt to transform it if a valid transformation exists for that pair.
  6. Transform between sealed traits, enums, and Java enums

    master

    Chimney supports automatic transformation between Algebraic Data Types (ADTs) like sealed traits, Scala 3 enums, and Java enums.

    Core Logic:

    • Subtypes are matched by their names.
    • Every subtype in the source must have a corresponding subtype in the target with a matching name.
    • You can have more subtypes in the target than in the source, but you cannot have a missing match in the source.

    Supported Combinations:

    • sealed trait $\leftrightarrow$ sealed trait
    • sealed trait $\leftrightarrow$ Scala 3 enum
    • Scala 3 enum $\leftrightarrow$ Scala 3 enum
    • Java enum $\leftrightarrow$ sealed trait
    • Java enum $\leftrightarrow$ Scala 3 enum
    import io.scalaland.chimney.dsl._
    
    sealed trait Foo
    object Foo {
      case class Baz(a: String, b: Int) extends Foo
      case object Buzz extends Foo
    }
    sealed trait Bar
    object Bar {
      case class Baz(b: Int) extends Bar
      case object Fizz extends Bar
      case object Buzz extends Bar
    }
    
    // Automatic transformation by name matching
    (Foo.Baz("value", 10): Foo).transformInto[Bar]
    // expected output: Baz(b = 10)
    
    (Foo.Buzz: Foo).transformInto[Bar]
    // expected output: Buzz
  7. How the DSL stores configuration and flags

    master

    The Chimney DSL tracks two distinct types of configuration at the type level:

    • TransformerCfg: Stores information regarding specific field and coproduct overrides. These are typically accompanied by a runtime value (like a constant or a function).
    • TransformerFlags: Stores global options that are not tied to a specific field or subtype (e.g., enabling or disabling certain transformation behaviors). These can be shared across derivations in the same scope using TransformerConfiguration.

    In Scala 2, these are implemented using whitebox macros to turn field selectors (like _.fieldName) into string singleton types. In Scala 3, this is achieved using transparent inline macros.

  8. Implement bidirectional transformations with Iso and Codec

    master

    When you need to derive transformations in both directions (e.g., from Type A to Type B and back), you can use Iso or Codec via semiautomatic derivation. This is more convenient than deriving separate Transformer instances.

    • Iso[A, B]: Use this for bidirectional conversions that always succeed in both directions. It provides .first (Transformer[A, B]) and .second (Transformer[B, A]).
    • Codec[Domain, Dto]: Use this for bidirectional conversions that always succeed in one direction (encoding) but might require validation in the other (decoding). It provides .encode (Transformer[Domain, Dto]) and .decode (PartialTransformer[Dto, Domain]).

    Both Iso and Codec currently only support withFieldRenamed and flags overrides through semiautomatic derivation.

    import io.scalaland.chimney.Iso
    
    case class Foo(a: Int, b: String)
    case class Bar(b: String, a: Int)
    
    object Bar {
      implicit val iso: Iso[Foo, Bar] = Iso.derive
      // Provides:
      // - iso.first: Transformer[Foo, Bar]
      // - iso.second: Transformer[Bar, Foo]
    }
  9. Understand Chimney's derivation modes: Automatic, Semiautomatic, and Inlined

    master

    Chimney offers three distinct ways to handle transformations, which differ from the standard 'automatic vs semiautomatic' dichotomy found in libraries like Circe. Choosing the right mode affects compilation speed, runtime performance, and how much control you have over where transformations are generated.

    1. DSL Mode (Default/Mixed)

    By using import io.scalaland.chimney.dsl._, you get a convenient mix of modes. It allows for recursive case class mapping and easy customization via implicits. It is designed to minimize macro expansions by handling recursion internally within a single macro expansion unless you explicitly provide an override implicit.

    2. Semiautomatic Derivation

    This mode requires you to explicitly call a derivation method. This is useful for caching transformations in companion objects or ensuring a specific instance is used everywhere. It reduces compile time and provides certainty.

    Available methods:

    • Transformer.derive[From, To]
    • PartialTransformer.derive[From, To]
    • Patcher.derive[A, Patch]
    • Transformer.define[From, To].buildTransformer (for customization)
    • PartialTransformer.define[From, To].buildTransformer (for customization)
    • Patcher.define[A, Patch].buildPatcher (for customization)

    3. Inlined Derivation

    By using import io.scalaland.chimney.inlined._, you use extension methods that generate an inlined expression at the call site without instantiating a type class. This is highly optimized for one-time usage as it avoids type class allocation and defers partial.Result wrapping.

    Available methods:

    • from.into[To].transform
    • from.intoPartial[To].transform
    • from.using[To].patch
  10. Use Chimney for lens-like nested updates

    master

    Chimney can be used for deep updates in nested structures, similar to how optics or libraries like Quicklens work. You can use the .into[T] DSL to target specific fields deep within a structure and apply constant values using .withFieldConst(path, value).

    Common path combinators include:

    • .matchingSome: For Option types (targets the value if Some).
    • .everyItem: For collections (targets every element).
    • .everyMapValue: For Map types (targets every value).
    • .everyMapKey: For Map types (targets every key).
    • .matching[Subtype]: For type-based filtering.
    • .matchingLeft / .matchingRight: For left/right side of structures.
    import io.scalaland.chimney.dsl._
    
    foo
      .into[Foo]
      .withFieldConst(_.bar.matchingSome.baz.everyItem.a, 10)
      .withFieldConst(_.bar.matchingSome.baz.everyItem.b, "new")
      .transform
  11. How `transformInto` selects a Transformer

    master

    When calling source.transformInto[Target], Chimney follows a specific priority for selecting the transformation logic:

    1. User-provided Transformer[Source, Target]: If you have explicitly defined a Transformer in the implicit scope, Chimney will use it first.
    2. Automatic Derivation: If no user-provided Transformer is found, Chimney uses a macro to automatically derive the transformation logic.

    If no transformation can be generated, Chimney is designed to produce a readable compiler error rather than a generic implicit not found message.

  12. Use Total Transformers for guaranteed conversions

    master

    A Total Transformer is used when a conversion from a source type (From) to a target type (To) can be applied to every possible value of the source type. In Chimney, these are represented by the Transformer[From, To] type.

    When an implicit Transformer is available in scope, you can use the .transformInto[To] extension method from io.scalaland.chimney.dsl._ to perform the conversion.

    import io.scalaland.chimney.Transformer
    import io.scalaland.chimney.dsl._
    
    class MyType(val a: Int)
    class MyOtherType(val b: String) { override def toString: String = s"MyOtherType($b)" }
    
    // Manual definition of a Total Transformer
    val transformer: Transformer[MyType, MyOtherType] = (src: MyType) => new MyOtherType(src.a.toString)
    
    // Usage via extension method
    val result = new MyType(10).transformInto[MyOtherType]