Skip to content

Custom adapters

Use a custom adapter when a config value should become a domain type instead of one of Kayan’s built-in Kotlin shapes.

Built-in schema entries are data. Custom adapters are build code. During source generation, Kayan loads the adapter, passes it the raw resolved value, and embeds the returned Kotlin expression in generated source.

Declare the raw config shape with custom(...) and point to an adapter class or object that is visible on the buildscript classpath:

kayan {
schema {
custom(
jsonKey = "regional_support_links",
propertyName = "SUPPORT_MATRIX",
rawKind = io.kayan.ConfigValueKind.STRING_LIST_MAP,
adapter = "sample.buildlogic.SupportMatrixAdapter",
required = true,
)
}
}

rawKind is the shape Kayan validates before your adapter runs. The generated property type comes from the adapter’s kotlinType.

Implement BuildTimeConfigAdapter<T> when possible:

package sample.buildlogic
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.TypeName
import io.kayan.ConfigValueKind
import io.kayan.gradle.BuildTimeConfigAdapter
object EnvironmentAdapter : BuildTimeConfigAdapter<EnvironmentSpec> {
override val rawKind: ConfigValueKind = ConfigValueKind.STRING
override val kotlinType: TypeName =
ClassName("sample", "Environment")
override fun parse(rawValue: Any): EnvironmentSpec {
val name = rawValue as? String
?: error("environment must be a string.")
require(name in setOf("dev", "staging", "prod")) {
"Unsupported environment '$name'."
}
return EnvironmentSpec(name)
}
override fun renderKotlin(value: EnvironmentSpec): String =
"sample.Environment(name = ${quote(value.name)})"
private fun quote(value: String): String = buildString {
append('"')
value.forEach { character ->
when (character) {
'\\' -> append("\\\\")
'"' -> append("\\\"")
'\n' -> append("\\n")
'\r' -> append("\\r")
'\t' -> append("\\t")
else -> append(character)
}
}
append('"')
}
}
data class EnvironmentSpec(val name: String)

The rendered string must be a complete Kotlin expression that can compile in the generated source file.

Kayan also accepts reflective adapters with the same public members and methods. The interface is safer because the compiler checks the contract.

Adapters usually live in included build logic or buildSrc. For example, the sample app keeps its adapter in sample/build-logic and adds that build logic to the sample buildscript classpath before applying Kayan.

The adapter type itself does not need to be part of the application runtime. It only needs to be visible to Gradle while Kayan generates source.

buildValue() does not apply custom adapters. It returns raw values such as String, Boolean, List<String>, or Map<String, List<String>>.

Custom adapters also do not receive explicit config null values. If a custom entry is nullable and resolves to null, generated source uses null directly instead of calling the adapter.

Adapters are trusted build code. They can run arbitrary logic and render arbitrary Kotlin expressions, so review them like Gradle plugins or build logic. Keep renderKotlin() deterministic and escape any string data that becomes part of a generated Kotlin literal.

For the broader trust model, see Security.