
YAML 1.2 serializer/deserializer integrating with a serialization framework, handling scalars, maps, sequences, anchors/aliases, polymorphism styles, and Docker Compose–style extension fields.
kotaml is a supported fork of kaml by Charles Korn.
This library adds YAML support to kotlinx.serialization.
Currently, only Kotlin/JVM is fully supported.
Kotlin/JS and Kotlin/Wasm support are considered highly experimental. It is not yet fully functional, and may be removed or modified at any time.
YAML version 1.2 is supported.
@Serializable
data class Team(
val leader: String,
val members: List<String>
)
val input = """
leader: Amy
members:
- Bob
- Cindy
- Dan
""".trimIndent()
val result = Yaml.default.decodeFromString(Team.serializer(), input)
println(result)@Serializable
data class Team(
val leader: String,
val members: List<String>
)
val input = Team("Amy", listOf("Bob", "Cindy", "Dan"))
val result = Yaml.default.encodeToString(Team.serializer(), input)
println(result)It is possible to parse a string or an InputStream directly into a YamlNode, for example
the following code prints Cindy.
val input = """
leader: Amy
members:
- Bob
- Cindy
- Dan
""".trimIndent()
val result = Yaml.default.parseToYamlNode(input)
println(
result
.yamlMap.get<YamlList>("members")!![1]
.yamlScalar
.content
)When a parsed YamlNode is serialized, plain numeric and boolean scalars keep their
source text, including 007, 0x7, 1E+003 and True. This preserves numeric
precision and distinct mapping keys. String quoting and document formatting follow
the writer configuration.
LegacyV0_110 is intended only for a quick upgrade to 0.111.0 and will be removed
in a future release. Use its diagnostics to migrate existing YAML, then remove the
option and return to the default strict reader.
val compatibility = YamlReadCompatibility.LegacyV0_110(
onUse = { issue ->
println("${issue.reason} at ${issue.path.toHumanReadableString()} " +
"(${issue.location.line}:${issue.location.column}): " +
"replace ${issue.originalValue} with ${issue.replacement}")
}
)
val yaml = Yaml(configuration = YamlConfiguration(readCompatibility = compatibility))The mode accepts legacy signed radix integers (-0x11, -0o11), Unicode integer
digits, floating point suffixes (1f, 1d), Infinity / -Infinity / NaN,
hexadecimal floats (0x1p3), and radix integers as floats (0x2, 0o2, 0b10).
These forms are supported on every platform, including forms previously accepted
only by JVM or JS. Integer range checks still apply.
Unquoted Null and NULL remain strings in values and keys, including nullable
string fields. Their diagnostics occur during parsing; numeric diagnostics occur
when decoding a numeric type. Ordinary string values such as Infinity do not
trigger numeric diagnostics. The callback is optional, runs synchronously, and
may receive events before a later document error. Callback exceptions propagate.
Breaking in 0.111.0: strict reading rejects empty plain mapping keys, just as it
rejects null and ~ keys. To use an empty string key, quote it:
? ""
: 1LegacyV0_110 preserves empty plain keys as empty strings and reports LegacyNullKey
with "" as the replacement. Empty values still resolve to null in both modes.
Use this configured Yaml instance for both parseToYamlNode and typed decoding;
an already resolved YamlNull cannot recover its original spelling. Direct scalar
conversions require an explicit argument, for example node.yamlScalar.toInt(compatibility).
The parameterless scalar conversions remain strict.
Encoding continues to use the new format. A typical migration reads a typed object
with compatibility enabled, writes it back, and verifies that the strict reader
can load the result. Parsing an untyped YamlNode does not infer numeric types for
legacy numeric strings.
Add the following to your Gradle build script:
plugins {
kotlin("jvm").version("2.4.20")
kotlin("plugin.serialization").version("2.4.20")
}
dependencies {
implementation("io.heapy.kotaml:kotaml:0.111.0")
}Check the releases page for the latest release information, and the Maven Central page for examples of how to reference the library in other build systems.
Supports most major YAML features:
Supports parsing YAML to Kotlin objects (deserializing) and writing Kotlin objects as YAML (serializing)
Supports kotlinx.serialization's polymorphism for sealed and unsealed types
Two styles are available (set YamlConfiguration.polymorphismStyle when creating an instance of Yaml):
using YAML tags to specify the type:
servers:
- !<frontend>
hostname: a.mycompany.com
- !<backend>
database: db-1using a type property to specify the type:
servers:
- type: frontend
hostname: a.mycompany.com
- type: backend
database: db-1The fragments above could be generated with:
@Serializable
sealed class Server {
@SerialName("frontend")
@Serializable
data class Frontend(val hostname: String) : Server()
@SerialName("backend")
@Serializable
data class Backend(val database: String) : Server()
}
@Serializable
data class Config(val servers: List<Server>)
val config = Config(listOf(
Frontend("a.mycompany.com"),
Backend("db-1")
))
val result = Yaml.default.encodeToString(Config.serializer(), config)
println(result)Supports Docker Compose-style extension fields
x-common-labels: &common-labels
labels:
owned-by: myteam@mycompany.com
cost-centre: myteam
servers:
server-a:
<<: *common-labels
kind: frontend
server-b:
<<: *common-labels
kind: backend
# server-b and server-c are equivalent
server-c:
labels:
owned-by: myteam@mycompany.com
cost-centre: myteam
kind: backendSpecify the extension prefix by setting YamlConfiguration.extensionDefinitionPrefix when creating an instance of Yaml (eg. "x-" for the example above).
Extensions can only be defined at the top level of a document, and only if the top level element is a map or object. Any key starting with the extension prefix must have an anchor defined (&...) and will not be included in the deserialised value.
Pull requests and bug reports are always welcome!
kotaml uses Gradle for builds and testing:
./gradlew assemble
./gradlew check
./gradlew --continuous check
kotaml is a supported fork of kaml by Charles Korn.
This library adds YAML support to kotlinx.serialization.
Currently, only Kotlin/JVM is fully supported.
Kotlin/JS and Kotlin/Wasm support are considered highly experimental. It is not yet fully functional, and may be removed or modified at any time.
YAML version 1.2 is supported.
@Serializable
data class Team(
val leader: String,
val members: List<String>
)
val input = """
leader: Amy
members:
- Bob
- Cindy
- Dan
""".trimIndent()
val result = Yaml.default.decodeFromString(Team.serializer(), input)
println(result)@Serializable
data class Team(
val leader: String,
val members: List<String>
)
val input = Team("Amy", listOf("Bob", "Cindy", "Dan"))
val result = Yaml.default.encodeToString(Team.serializer(), input)
println(result)It is possible to parse a string or an InputStream directly into a YamlNode, for example
the following code prints Cindy.
val input = """
leader: Amy
members:
- Bob
- Cindy
- Dan
""".trimIndent()
val result = Yaml.default.parseToYamlNode(input)
println(
result
.yamlMap.get<YamlList>("members")!![1]
.yamlScalar
.content
)When a parsed YamlNode is serialized, plain numeric and boolean scalars keep their
source text, including 007, 0x7, 1E+003 and True. This preserves numeric
precision and distinct mapping keys. String quoting and document formatting follow
the writer configuration.
LegacyV0_110 is intended only for a quick upgrade to 0.111.0 and will be removed
in a future release. Use its diagnostics to migrate existing YAML, then remove the
option and return to the default strict reader.
val compatibility = YamlReadCompatibility.LegacyV0_110(
onUse = { issue ->
println("${issue.reason} at ${issue.path.toHumanReadableString()} " +
"(${issue.location.line}:${issue.location.column}): " +
"replace ${issue.originalValue} with ${issue.replacement}")
}
)
val yaml = Yaml(configuration = YamlConfiguration(readCompatibility = compatibility))The mode accepts legacy signed radix integers (-0x11, -0o11), Unicode integer
digits, floating point suffixes (1f, 1d), Infinity / -Infinity / NaN,
hexadecimal floats (0x1p3), and radix integers as floats (0x2, 0o2, 0b10).
These forms are supported on every platform, including forms previously accepted
only by JVM or JS. Integer range checks still apply.
Unquoted Null and NULL remain strings in values and keys, including nullable
string fields. Their diagnostics occur during parsing; numeric diagnostics occur
when decoding a numeric type. Ordinary string values such as Infinity do not
trigger numeric diagnostics. The callback is optional, runs synchronously, and
may receive events before a later document error. Callback exceptions propagate.
Breaking in 0.111.0: strict reading rejects empty plain mapping keys, just as it
rejects null and ~ keys. To use an empty string key, quote it:
? ""
: 1LegacyV0_110 preserves empty plain keys as empty strings and reports LegacyNullKey
with "" as the replacement. Empty values still resolve to null in both modes.
Use this configured Yaml instance for both parseToYamlNode and typed decoding;
an already resolved YamlNull cannot recover its original spelling. Direct scalar
conversions require an explicit argument, for example node.yamlScalar.toInt(compatibility).
The parameterless scalar conversions remain strict.
Encoding continues to use the new format. A typical migration reads a typed object
with compatibility enabled, writes it back, and verifies that the strict reader
can load the result. Parsing an untyped YamlNode does not infer numeric types for
legacy numeric strings.
Add the following to your Gradle build script:
plugins {
kotlin("jvm").version("2.4.20")
kotlin("plugin.serialization").version("2.4.20")
}
dependencies {
implementation("io.heapy.kotaml:kotaml:0.111.0")
}Check the releases page for the latest release information, and the Maven Central page for examples of how to reference the library in other build systems.
Supports most major YAML features:
Supports parsing YAML to Kotlin objects (deserializing) and writing Kotlin objects as YAML (serializing)
Supports kotlinx.serialization's polymorphism for sealed and unsealed types
Two styles are available (set YamlConfiguration.polymorphismStyle when creating an instance of Yaml):
using YAML tags to specify the type:
servers:
- !<frontend>
hostname: a.mycompany.com
- !<backend>
database: db-1using a type property to specify the type:
servers:
- type: frontend
hostname: a.mycompany.com
- type: backend
database: db-1The fragments above could be generated with:
@Serializable
sealed class Server {
@SerialName("frontend")
@Serializable
data class Frontend(val hostname: String) : Server()
@SerialName("backend")
@Serializable
data class Backend(val database: String) : Server()
}
@Serializable
data class Config(val servers: List<Server>)
val config = Config(listOf(
Frontend("a.mycompany.com"),
Backend("db-1")
))
val result = Yaml.default.encodeToString(Config.serializer(), config)
println(result)Supports Docker Compose-style extension fields
x-common-labels: &common-labels
labels:
owned-by: myteam@mycompany.com
cost-centre: myteam
servers:
server-a:
<<: *common-labels
kind: frontend
server-b:
<<: *common-labels
kind: backend
# server-b and server-c are equivalent
server-c:
labels:
owned-by: myteam@mycompany.com
cost-centre: myteam
kind: backendSpecify the extension prefix by setting YamlConfiguration.extensionDefinitionPrefix when creating an instance of Yaml (eg. "x-" for the example above).
Extensions can only be defined at the top level of a document, and only if the top level element is a map or object. Any key starting with the extension prefix must have an anchor defined (&...) and will not be included in the deserialised value.
Pull requests and bug reports are always welcome!
kotaml uses Gradle for builds and testing:
./gradlew assemble
./gradlew check
./gradlew --continuous check