Cats Collections Integration
Automatic support for Cats data types in all Kindlings derivation modules. Add the dependency and NonEmptyList, NonEmptyMap, Chain, Validated, Const, and other Cats types are handled transparently in Circe, Jsoniter, Avro, YAML, and every other module — no imports, no configuration.
Installation
sbt
Cross-platform (JVM / Scala.js / Scala Native):
Note
You also need cats-core as a dependency:
How it works
The integration registers IsCollection, IsMap, IsEither, and IsValueType providers for Cats data types. These providers are discovered at compile time via SPI (Service Provider Interface) — the macro extension system picks them up automatically when the jar is on the classpath.
Each provider teaches all derivation modules how to encode and decode the corresponding Cats type:
- Collections (
NonEmptyList,NonEmptyVector,NonEmptyChain,Chain): encoded as JSON arrays, decoded with non-emptiness validation where applicable. - Maps (
NonEmptyMap): encoded as JSON objects (likeMap), decoded with non-emptiness validation. - Sets (
NonEmptySet): encoded as JSON arrays, decoded with non-emptiness and ordering validation. - Either-like (
Validated): encoded/decoded likeEither, mappingInvalidto left andValidto right. - Value types (
Const): unwrapped to the underlying value.
Supported types
Collection types
| Type | Encoded as | Decoding validation |
|---|---|---|
NonEmptyList[A] |
Array | Must be non-empty |
NonEmptyVector[A] |
Array | Must be non-empty |
NonEmptyChain[A] |
Array | Must be non-empty |
Chain[A] |
Array | None |
Map types
| Type | Encoded as | Decoding validation |
|---|---|---|
NonEmptyMap[K, V] |
Object / array of pairs | Must be non-empty; requires Order[K] |
Set types
| Type | Encoded as | Decoding validation |
|---|---|---|
NonEmptySet[A] |
Array | Must be non-empty; requires Order[A] |
Either-like types
| Type | Encoded as | Decoding validation |
|---|---|---|
Validated[E, A] |
Like Either[E, A] |
None (both Invalid and Valid are valid) |
ValidatedNel[E, A] |
Like Either[NonEmptyList[E], A] |
Handled automatically (it is Validated[NonEmptyList[E], A]) |
Value types
| Type | Behavior |
|---|---|
Const[A, B] |
Unwrapped to A (phantom B is ignored) |
Examples
Non-empty collections with Circe
//> using scala 2.13.18
//> using dep com.kubuszok::kindlings-circe-derivation:0.2.0
//> using dep com.kubuszok::kindlings-cats-integration:0.2.0
//> using dep org.typelevel::cats-core:2.13.0
//> using dep io.circe::circe-parser:0.14.15
import hearth.kindlings.circederivation._
import io.circe._
import cats.data.NonEmptyList
case class Team(
name: String,
members: NonEmptyList[String]
)
val team = Team("Backend", NonEmptyList.of("Alice", "Bob", "Carol"))
println(KindlingsEncoder.encode(team).noSpaces)
// expected output:
// {"name":"Backend","members":["Alice","Bob","Carol"]}
// Decoding an empty array fails — NonEmptyList requires at least one element
val bad = io.circe.parser.parse("""{"name":"Empty","members":[]}""")
.flatMap(KindlingsDecoder.decode[Team](_))
println(bad.isLeft)
// expected output:
// true
val good = io.circe.parser.parse("""{"name":"Solo","members":["Dave"]}""")
.flatMap(KindlingsDecoder.decode[Team](_))
println(good)
// expected output:
// Right(Team(Solo,NonEmptyList(Dave)))
NonEmptyMap with Jsoniter
//> using scala 3.8.3
//> using dep com.kubuszok::kindlings-jsoniter-derivation:0.2.0
//> using dep com.kubuszok::kindlings-cats-integration:0.2.0
//> using dep org.typelevel::cats-core:2.13.0
import hearth.kindlings.jsoniterderivation._
import com.github.plokhotnyuk.jsoniter_scala.core._
import cats.data.NonEmptyMap
import cats.implicits._
case class Config(
settings: NonEmptyMap[String, String]
)
val codec = KindlingsJsonValueCodec.derived[Config]
val config = Config(NonEmptyMap.of("host" -> "localhost", "port" -> "8080"))
println(writeToString(config)(codec))
// expected output:
// {"settings":{"host":"localhost","port":"8080"}}
Validated fields
//> using scala 2.13.18
//> using dep com.kubuszok::kindlings-circe-derivation:0.2.0
//> using dep com.kubuszok::kindlings-cats-integration:0.2.0
//> using dep org.typelevel::cats-core:2.13.0
//> using dep io.circe::circe-parser:0.14.15
import hearth.kindlings.circederivation._
import io.circe._
import cats.data.Validated
case class FormResult(
username: Validated[String, String]
)
val valid = FormResult(Validated.valid("alice"))
println(KindlingsEncoder.encode(valid).noSpaces)
// expected output:
// {"username":{"Valid":{"a":"alice"}}}
val invalid = FormResult(Validated.invalid("username is required"))
println(KindlingsEncoder.encode(invalid).noSpaces)
// expected output:
// {"username":{"Invalid":{"e":"username is required"}}}
Combining with other integrations
The Cats integration composes naturally with other integration modules. For example, you can have NonEmptyList[Int Refined Positive] fields — the Cats integration handles NonEmptyList and the Refined integration handles Refined:
libraryDependencies ++= Seq(
"com.kubuszok" %%% "kindlings-circe-derivation" % "0.2.0",
"com.kubuszok" %%% "kindlings-cats-integration" % "0.2.0",
"com.kubuszok" %%% "kindlings-refined-integration" % "0.2.0"
)
Each integration module independently registers its providers, and the derivation engine composes them automatically.