Skip to content

Working with YAML

The kpointer-yamlkt module reads and mutates YamlKt YamlMap and YamlList values by JSON Pointer. Like the JSON module, it is built on kpointer-adapter but is used through convenience extensions on the YamlKt types. Every mutation produces a new immutable value; the original is unchanged.

No wasmJs target

YamlKt does not publish a wasmJs artifact, so kpointer-yamlkt does not target wasmJs. Its other targets (JVM, Android, iOS, Linux x64, JS) match the rest of kPointer.

Reading a value

YamlKt's YamlMap already declares operator fun get(key: Any?), which would silently shadow a KPointer-based get extension. To avoid that trap, this module uses elementAt for pointer reads rather than the [] operator:

val doc: YamlMap = Yaml.decodeYamlMapFromString(
    """
    user:
      name: Alice
      age: 30
    """.trimIndent()
)

val name: YamlElement? = doc.elementAt(KPointer.from("/user/name"))
println(name)   // Alice

// Pass a string directly — KPointer.from is called for you
val same: YamlElement? = doc.elementAt("/user/name")

// Absent paths yield null
println(doc.elementAt("/user/email"))   // null

YamlList works the same way.

Typed accessors

val user: YamlMap?       = doc.mapAt("/user")
val tags: YamlList?      = doc.listAt("/user/tags")
val name: YamlPrimitive? = doc.primitiveAt("/user/name")

They return null for an absent path and throw IllegalArgumentException on a type mismatch. Each accessor also accepts a pre-parsed KPointer.

Checking whether a path exists

contains distinguishes "absent" from "present but YamlNull":

KPointer.from("/user/name") in doc                 // true
doc.contains(KPointer.from("/user/nullField"))     // true, even though the value is YamlNull
doc.contains(KPointer.from("/missing"))            // false

Mutating

val updated: YamlMap = doc.mutate {
    "/user/name"   to "Bob"
    "/user/active" to true
    "/user/age"    to 42
    "/user/ratio"  to 3.14
    "/user/temp"   to null
    remove("/user/deprecated")
}

Supported right-hand-side types: String, Boolean, Int, Long, Float, Double, null, and KpaElement (wrap a YamlElement with .toKpaElement() to insert a nested map or list).

YamlList mutation mirrors the JSON array API:

val original: YamlList = Yaml.decodeYamlListFromString("[a, b, c]")

val replaced: YamlList = original.mutate { "/1" to "B" }    // [a, B, c]
val appended: YamlList = original.mutate { "/-" to "d" }    // [a, b, c, d]
val removed:  YamlList = original.mutate { remove("/1") }   // [a, c]

YAML-specific behavior

Primitive type inference

YamlKt stores every literal — number, boolean, or string — as a single String, with no preserved type tag. This module classifies primitives per the YAML 1.2 core schema:

  • true, True, TRUE, false, False, FALSE → boolean.
  • Anything that parses via String.toLongOrNull() or String.toDoubleOrNull() → number.
  • Everything else → string.

Consequences worth knowing:

  • YAML 1.1 boolean spellings (yes / no / on / off) are strings here, not booleans.
  • Exotic numeric forms (hex, octal, .inf, underscore-separated digits) are strings too. If you need those, reach through the adapter's backing property to YamlKt's own API.

Compound map keys

YAML permits non-scalar keys — a sequence or map used as a map key. For example, in YAML:

? [a, b]
: compoundValue
literal1: v1

The key [a, b] is a sequence, not a string. JSON Pointer segments are always strings, so there is no pointer expression that can address a compound-keyed entry. kPointer exposes only the literal-keyed entries of a YamlMap through pointer operations:

// A map with one compound key ([a, b]) and two literal keys (literal1, literal2)
val doc: YamlMap = /* the YamlMap above, built by YamlKt */

// Only literal keys are visible:
doc.elementAt("/literal1")   // YamlLiteral("v1")
doc.elementAt("/literal2")   // YamlLiteral("v2")

// The segment "a" does not match any literal key — compound keys are invisible:
doc.elementAt("/a")          // null

The underlying YamlMap — compound keys intact — is still reachable via .asKpaStruct().backing if you need to inspect or copy those entries directly.

Mutation drops compound-keyed entries

YamlMap.mutate { … } rebuilds the map from its literal-keyed snapshot, so compound-keyed entries are silently dropped from the result:

val mutated = doc.mutate { "/literal1" to "newValue" }

// mutated has only "literal1" and "literal2" — the [a, b] entry is gone

If you must preserve compound-keyed entries across a mutation, manipulate the YamlMap directly rather than through the pointer DSL.

Handing adapters to other libraries

When integrating with generic code that consumes the kPointer document model directly:

val struct: KpaStruct = ymap.asKpaStruct()
val list:   KpaList   = ylist.asKpaList()

// Back to a YamlKt type
val element: YamlElement = someKpaElement.toYamlElement()