Deserializer

Reflection-based deserializer that converts a Map (from parsed YAML) into Kotlin data class instances.

Algorithm

For each target data class the deserializer:

  1. Resolves metadata -- reflects on the primary constructor and caches parameter info in a ConcurrentHashMap for fast repeated access.

  2. Detects unknown keys -- any YAML key that does not match a constructor parameter (except the reserved "configVersion") is reported as ConfigError.UnknownKey, with a Levenshtein-based "did you mean ...?" suggestion when a close match exists.

  3. Iterates constructor parameters and for each one:

    • @Transient -- skips the parameter entirely; Kotlin uses its default value.

    • Key lookup -- tries the parameter name, then any legacy names from @MigrateFrom.

    • Missing value -- if the parameter is optional (has a Kotlin default) it is skipped; if nullable it is set to null; otherwise a ConfigError.MissingRequired is recorded.

    • Deserialization -- delegates to deserializeValue which handles primitives, enums, List, Set, Map, SecretString, custom serializers from the SerializerRegistry, and recursive nested data classes.

    • @Range / @Pattern validation -- if the constraint is violated, an error is recorded and the field falls back to its Kotlin default (when available).

  4. Constructs the instance via KFunction.callBy, which honours Kotlin default values for any parameter not explicitly provided.

Supported types

CategoryTypes
PrimitivesString, Int, Long, Double, Float, Boolean
SpecialSecretString, any enum class
CollectionsList<T>, Set<T>, Map<K, V> (recursively typed)
CustomAny type registered in SerializerRegistry
NestedAny Kotlin data class (deserialized recursively)

Annotation interactions

  • @Transient: Field is skipped; always uses Kotlin default value.

  • @MigrateFrom: Falls back to legacy key names when the current key is missing.

  • @Range: Validates numeric values are within bounds; falls back to default on violation.

  • @Pattern: Validates string values against a regex; falls back to default on violation.

Caching

Constructor metadata (ClassMetadata) is cached in a ConcurrentHashMap keyed by KClass, so reflection costs are paid only once per type even across multiple deserialization calls.

Example:

val registry = SerializerRegistry()
BuiltinSerializers.registerAll(registry)
val deserializer = Deserializer(registry)
val errors = ConfigErrorCollector()

val config = deserializer.deserialize(MyConfig::class, yamlMap, errors)
if (errors.hasErrors()) {
println(ConfigErrorFormatter().format(errors.all()))
}

Since

1.0

Parameters

registry

The SerializerRegistry used to resolve custom type serializers.

See also

Constructors

Link copied to clipboard
constructor(registry: SerializerRegistry)

Types

Link copied to clipboard
data class ClassMetadata(val constructor: KFunction<*>, val parameters: List<KParameter>, val paramNames: Set<String>)

Cached reflection metadata for a data class, capturing its primary constructor and the set of valid parameter names.

Functions

Link copied to clipboard
fun <T : Any> deserialize(klass: KClass<T>, map: Map<String, Any?>, errors: ConfigErrorCollector, path: String = ""): T?

Deserializes a YAML-sourced map into an instance of klass.