Skip to content

FAQ

Why not contribute these improvements to Circe, Jsoniter Scala, etc.?

Different foundations require different codebases. Kindlings' derivation is built on Hearth, a macro toolkit that provides high-level, cross-platform abstractions over Scala 2 and Scala 3 metaprogramming. Existing libraries use their own macro infrastructure (or Shapeless/Mirrors). Replacing the internals of a library with a completely different macro foundation is not a patch — it's a rewrite of the derivation layer.

Independence allows faster iteration. Kindlings can support new type categories (named tuples, opaque types, Java enums), experiment with better error messages, and optimize compilation speed without being blocked by the release cycle or design philosophy of upstream libraries.

Maintaining cross-version compatibility is a design constraint. Most libraries have separate Scala 2 and Scala 3 implementations with different capabilities. Kindlings shares a single derivation logic across both versions.

What does "sanely-automatic" mean?

Sanely-automatic derivation means three things:

  1. Semi-automatic is recursive. When you call KindlingsEncoder.derived[Person], it derives instances for Person and all its nested types (Address, List[Address], etc.) in a single macro expansion — no need to declare instances for each type manually.

  2. Automatic has no overhead over semi-automatic. For a single derivation site, automatic and semi-automatic produce identical generated code — same compilation cost, same runtime performance. The generated code is as fast as what you'd write by hand.

  3. Errors are informative and actionable. When derivation fails, you get a clear message telling you which type is missing an instance and where in the type hierarchy the problem is — not a cryptic diverging implicit expansion.

If the same type is auto-derived at multiple call sites, each site derives independently. This is still cheaper than Shapeless/Mirrors-based automatic derivation, but if you want to guarantee a type is derived exactly once, use semi-automatic (KindlingsEncoder.derived[A]) and assign it to an implicit val / given.

Why not separate automatic and semi-automatic derivation?

Most Scala libraries split derivation into "automatic" (an import makes instances appear wherever needed) and "semi-automatic" (you must write deriveX[A] for every type). Kindlings deliberately does not offer a separate semi-automatic-only mechanism, and it does not make recursion optional. The reasoning:

  • The auto/semi distinction is mostly arbitrary. Every library always supports primitives, collections, Option, newtypes, refined types, etc. unconditionally. "Automatic vs semi-automatic" really only governs case classes and sealed hierarchies. Explaining to a newcomer why a List[String] inside a case class "just works" while the case class itself needs a special import — and why disabling recursion still leaves Option/List/newtypes working — is hard, because the line is an implementation detail leaking into the API.

  • The two-imports model hurts the people we most want to help. Needing to learn that there are two sets of imports, where swapping one silently changes your program's semantics, empowers Scala experts at the cost of newcomers and people focused on the business problem rather than the language. Kindlings optimizes for lowering that barrier.

  • Semi-automatic doesn't actually guarantee coherence anyway. One stray import, a new instance added to a companion, or an instance introduced up an inheritance hierarchy can still change runtime behavior — invisibly, during a refactor. If you care about a wire contract, golden tests are the real guard (and they also catch serde library bugs and migrations between JSON libraries, which no derivation strategy can).

What Kindlings does offer for teams that want tighter control is a global, per-library opt-out of automatic derivation: the Derivation Policy. Set it to opt-in and structural derivation is only allowed in the scopes you designate (or behind an explicit import) — everywhere else you get a clear compile error telling you to define the instance explicitly. This achieves the "instances only appear where I decided" goal without a second API surface and without giving up recursive, single-expansion derivation. See issue #85 for the full discussion.

Can I use both Kindlings and the original library's derivation?

Yes. Kindlings type classes extend their parent library's types (KindlingsEncoder[A] extends Encoder[A], KindlingsDecoder[A] extends Decoder[A], etc.). You can mix manually written instances with derived ones.

If both Kindlings' and the original library's automatic derivation are in scope, you may get ambiguous implicits. In that case, use semi-automatic derivation (KindlingsEncoder.derived[A]) to be explicit.

How do I migrate from circe-generic?

  1. Replace the dependency: circe-generic / circe-generic-extras with kindlings-circe-derivation
  2. Replace imports: io.circe.generic.auto._ or io.circe.generic.semiauto._ with hearth.kindlings.circederivation._
  3. Replace deriveEncoder[A] / deriveDecoder[A] with KindlingsEncoder.derived[A] / KindlingsDecoder.derived[A]
  4. If using @ConfiguredJsonCodec or circe Configuration, switch to Kindlings' Configuration class (same builder API)

How do I migrate from kittens?

  1. Replace the dependency: kittens with kindlings-cats-derivation
  2. Replace imports: cats.derived._ or cats.derived.auto.* with hearth.kindlings.catsderivation._
  3. For Scala 3 derives syntax: replace derives cats.Show with derives cats.Show (Kindlings provides the derived method as an extension, so derives just works)
  4. For semi-automatic: replace cats.derived.semiauto.show[A] with cats.Show.derived[A]

How do I migrate from jsoniter-scala macros?

  1. Replace the dependency: jsoniter-scala-macros with kindlings-jsoniter-derivation
  2. Replace JsonCodecMaker.make[A] with KindlingsJsonValueCodec.derived[A]
  3. Replace CodecMakerConfig with JsoniterConfig (similar builder API)

Which modules are JVM-only?

  • kindlings-avro-derivation — depends on org.apache.avro:avro (JVM-only)
  • kindlings-pureconfig-derivation — depends on com.typesafe:config (JVM-only)

All other modules are cross-compiled for JVM, Scala.js, and Scala Native.

Which module is Scala 3-only?

  • kindlings-iron-integration — Iron is a Scala 3-only library (opaque types)

All other modules support both Scala 2.13 and Scala 3.

Do I need to import anything for Refined / Iron support?

No. Add the integration dependency to your build and derivation handles refined/iron types automatically:

// build.sbt
libraryDependencies += "com.kubuszok" %% "kindlings-refined-integration" % "0.3.1"
// or for Iron (Scala 3 only):
libraryDependencies += "com.kubuszok" %% "kindlings-iron-integration" % "0.3.1"

The macro extension system discovers the integration at compile time. No imports, no configuration.

Where are Cats collection types like NonEmptyList supported?

Add the kindlings-cats-integration dependency. It provides IsCollection and IsMap providers for NonEmptyList, NonEmptyVector, NonEmptyChain, Chain, NonEmptyMap, and NonEmptySet. These are then automatically available to all derivation modules (Circe, Jsoniter, Avro, etc.).

libraryDependencies += "com.kubuszok" %% "kindlings-cats-integration" % "0.3.1"