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:
-
Semi-automatic is recursive. When you call
KindlingsEncoder.derived[Person], it derives instances forPersonand all its nested types (Address,List[Address], etc.) in a single macro expansion — no need to declare instances for each type manually. -
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.
-
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 aList[String]inside a case class "just works" while the case class itself needs a special import — and why disabling recursion still leavesOption/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?
- Replace the dependency:
circe-generic/circe-generic-extraswithkindlings-circe-derivation - Replace imports:
io.circe.generic.auto._orio.circe.generic.semiauto._withhearth.kindlings.circederivation._ - Replace
deriveEncoder[A]/deriveDecoder[A]withKindlingsEncoder.derived[A]/KindlingsDecoder.derived[A] - If using
@ConfiguredJsonCodecor circeConfiguration, switch to Kindlings'Configurationclass (same builder API)
How do I migrate from kittens?
- Replace the dependency:
kittenswithkindlings-cats-derivation - Replace imports:
cats.derived._orcats.derived.auto.*withhearth.kindlings.catsderivation._ - For Scala 3
derivessyntax: replacederives cats.Showwithderives cats.Show(Kindlings provides thederivedmethod as an extension, soderivesjust works) - For semi-automatic: replace
cats.derived.semiauto.show[A]withcats.Show.derived[A]
How do I migrate from jsoniter-scala macros?
- Replace the dependency:
jsoniter-scala-macroswithkindlings-jsoniter-derivation - Replace
JsonCodecMaker.make[A]withKindlingsJsonValueCodec.derived[A] - Replace
CodecMakerConfigwithJsoniterConfig(similar builder API)
Which modules are JVM-only?
kindlings-avro-derivation— depends onorg.apache.avro:avro(JVM-only)kindlings-pureconfig-derivation— depends oncom.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.).