Skip to content

YAML ​

wvlet.uni.text.yaml reads YAML 1.2 into the same JSONValue tree that JSON processing uses, so Weaver can decode it into case classes. It works on JVM, Scala.js, and Scala Native.

Decoding into Case Classes ​

scala
import wvlet.uni.text.yaml.YAML
import wvlet.uni.weaver.Weaver

case class ServerConfig(name: String, port: Int = 8080, tags: Seq[String] = Nil) derives Weaver

val config = YAML.fromYaml[ServerConfig]("""
name: api
tags: [public, v2]
""")
// ServerConfig(api, 8080, List(public, v2))

Missing fields take their case-class defaults.

Parsing to JSONValue ​

scala
import wvlet.uni.json.JSON.JSONValue

val v: JSONValue = YAML.parse("""
server:
  host: localhost
  ports: [80, 443]
""")
v.toJSON // {"server":{"host":"localhost","ports":[80,443]}}

YAML.parse reads a single document, and returns JSONNull for an empty one. For a ----separated stream, use YAML.parseAll:

scala
val docs: Seq[JSONValue] = YAML.parseAll("a: 1\n---\nb: 2")

Frontmatter ​

Frontmatter.parse splits a YAML header delimited by --- lines from the rest of a text, as used in Markdown files and agent skills:

scala
import wvlet.uni.text.yaml.Frontmatter

case class SkillHeader(name: String, description: String = "") derives Weaver

val f = Frontmatter.parse("""---
name: review
description: Review a pull request
---
# Instructions
""")
f.as[SkillHeader] // SkillHeader(review, Review a pull request)
f.body            // "# Instructions\n"

When the text has no frontmatter, data is None and body is the whole text.

Supported YAML ​

  • Block mappings and sequences, including a sequence at its key's column
  • Flow collections: [a, b], {a: 1}, and single-pair entries like [a: 1]
  • Plain, single-quoted, and double-quoted scalars (all YAML escapes), with multi-line folding
  • Literal (|) and folded (>) block scalars, with chomping (-, +) and indentation indicators
  • Comments, multiple documents (---, ...), and %YAML directives
  • Anchors (&name), aliases (*name), and merge keys (<<: *base)
  • Tags !!str, !!int, !!float, !!bool, and !!null. Other tags are ignored, and the value resolves as if untagged

Plain scalars resolve with the YAML 1.2 core schema:

YAMLValue
~, null, emptyJSONNull
true, FalseJSONBoolean
42, 0x1F, 0o17JSONLong
1.5, 1e3, .inf, .nanJSONDouble
yes, on, anything elseJSONString

YAML 1.1 booleans like yes and on stay strings, as YAML 1.2 specifies.

Not supported:

  • Explicit keys (? key) and complex (non-scalar) mapping keys
  • %TAG handles. Tags are ignored, apart from the ones listed above

Duplicate keys are an error. Mapping keys keep their source text, so 1: a has the key "1".

Safety ​

Aliases share the anchored value instead of copying it. To stop "billion laughs" inputs, where nested aliases expand exponentially, parsing fails once aliases expand to more than YAML.defaultMaxAliasNodes (1,000,000) nodes. To change the limit, pass maxAliasNodes:

scala
YAML.parseAll(untrustedYaml, maxAliasNodes = 10_000)

Writing YAML ​

To write YAML, use YAMLFormatter.toYaml from wvlet.uni.json. It converts a JSON string to YAML:

scala
import wvlet.uni.json.YAMLFormatter

YAMLFormatter.toYaml("""{"name":"api","port":8080}""")
// name: api
// port: 8080

Released under the Apache 2.0 License.