# yaml

> Read and write YAML, the format most config files are written in.

This is the Markdown version of https://osl-lang.org/packages/yaml. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
import "std:yaml"
```

`yaml` reads and writes YAML. Use it for configuration that people edit by hand: it has comments, no quoting noise and readable nesting. Parsing gives you ordinary OSL values (objects, arrays, strings, numbers), the same ones [json](/packages/json.md) gives you, so the code that uses the data doesn't care which format it came from.

```osl
import "std:yaml"
```

## Parse a document

`yaml.parse` returns a [result](/learn/errors.md). Mappings become objects, sequences become arrays, and scalars become strings, numbers, booleans or null.

```osl
import "std:yaml"

string text = `
name: shop
port: 8080
debug: false
owner: ~
tags:
  - web
  - prod
db:
  host: localhost
  pool: 4
`

auto cfg = yaml.parse(text).unwrap()
log cfg.name cfg.port cfg.debug cfg.owner
log cfg.tags[2]
log cfg.db.host cfg.db.pool
```

Output:

```text
shop 8080 false null
prod
localhost 4
```

Comments start with `#` and are dropped. An empty document parses to `null`.

Try it: add a `replicas: 3` line to the text and log it.

## Errors

Bad indentation, a duplicate key or an unclosed bracket is a `ParseError`. The message says which line.

```osl
import "std:yaml"

string[] docs = ["a: 1\nb: [1, 2", "a: 1\na: 2", "ok: true"]
for d in docs (
  switch yaml.parse(d) (
    case ok(v)
      log "ok"
    case err(e)
      log e.kind
  )
)
log yaml.isValid("x: 1") yaml.isValid("x: [")
```

Output:

```text
ParseError
ParseError
ok
true false
```

Try it: fix the first document's missing bracket and watch `ParseError` disappear.

## Some YAML surprises

YAML guesses types from how a value looks. `yes` is not special here, but `true`, `null`, numbers and `~` are. Quote anything that must stay text. Timestamps are left as the strings you wrote.

```osl
import "std:yaml"

auto v = yaml.parse(`
version: 1.10
code: "007"
when: 2026-11-15
empty:
`).unwrap()
log v.version v.code v.when v.empty
```

Output:

```text
1.1 007 2026-11-15 null
```

Note `version: 1.10` became the number `1.1`. If a value is a version or an identifier, quote it in the file.

## Anchors and multi-line text

Anchors (`&name`) and aliases (`*name`) let you reuse a block. Block scalars (`|` keeps newlines, `>` folds them) hold long text.

```osl
import "std:yaml"

auto cfg = yaml.parse(`
defaults: &defaults
  retries: 3
  timeout: 30
prod:
  <<: *defaults
  timeout: 60
banner: |
  Hello
  world
`).unwrap()
log cfg.prod.retries cfg.prod.timeout
log cfg.banner.len
```

Output:

```text
3 60
12
```

## Write YAML

`yaml.stringify` turns a value into YAML, with keys in sorted order and a trailing newline. The `indent` option sets the spaces per level (2 to 16, default 4).

```osl
import "std:yaml"

auto data = {name: "shop", ports: [80, 443], db: {host: "localhost", pool: 4}}
log yaml.stringify(data, {indent: 2})
```

Output:

```text
db:
  host: localhost
  pool: 4
name: shop
ports:
  - 80
  - 443
```

Strings that would be read back as something else are quoted for you, so `parse(stringify(x))` gives `x` back.

```osl
import "std:yaml"

auto back = yaml.parse(yaml.stringify({a: "true", b: "12", c: "plain"})).unwrap()
log back.a back.b back.c
log back.a == "true"
```

Output:

```text
true 12 plain
true
```

Try it: change `indent` to 4 and compare.

## Files

`yaml.parseFile` reads a file. A missing file is `NotFoundError`, a bad one is `ParseError`.

```osl
import "std:yaml"
import "std:fs"

fs.writeFile("app.yaml", yaml.stringify({name: "demo", workers: 2})).unwrap()
auto app = yaml.parseFile("app.yaml").unwrap()
log app.name app.workers
log yaml.parseFile("nope.yaml").unwrapErr().kind
```

Output:

```text
demo 2
NotFoundError
```

## Converting between formats

Because every format gives the same values, converting is a parse and a stringify:

```osl
import "std:yaml"
import "std:json"

auto doc = yaml.parse("a: 1\nlist:\n  - x\n  - y\n").unwrap()
log json.stringify(doc)
```

Output:

```text
{"a":1,"list":["x","y"]}
```

**Quiz.** In YAML, `version: 1.10` is read as...

- [ ] The string `1.10`.
- [x] The number `1.1`.
- [ ] An error.

Answer: YAML guesses types from how a value looks. Quote it (`"1.10"`) to keep it as text.

### Exercise: Add up the ports

Parse the YAML and print the name, then the sum of the ports.

Starting code:

```osl
import "std:yaml"

string text = "name: demo\nports:\n  - 80\n  - 443\n"
log text.len
```

It should print:

```text
demo 523
```

Hint 1: `yaml.parse(text).unwrap()` gives an object.

Hint 2: `cfg.ports[1]` is the first port.

A solution:

```osl
import "std:yaml"

string text = "name: demo\nports:\n  - 80\n  - 443\n"
auto cfg = yaml.parse(text).unwrap()
log cfg.name cfg.ports[1] + cfg.ports[2]
```

## API reference

## API reference

Every member of `std:yaml`, generated from the source of OSL.

### Functions

#### `yaml.delete(object data, any key) object`

**Deprecated.** Deprecated: use data.delete(key) instead.

delete removes key from data in place and returns data.

#### `yaml.fromJSON(any jsonData) any`

**Deprecated.** Deprecated: use json.parse(text) instead.

fromJSON decodes any JSON value (object, array or scalar); invalid JSON
gives null.

#### `yaml.get(any data, any key) any`

**Deprecated.** Deprecated: use data[key] instead.

get reads data[key], or null.

#### `yaml.has(object data, any key) boolean`

**Deprecated.** Deprecated: use data.contains(key) instead.

has reports whether data has key.

#### `yaml.isValid(string text) boolean`

isValid reports whether yaml.parse would accept text.

#### `yaml.keys(object data) array`

**Deprecated.** Deprecated: use data.getKeys().sort() instead.

keys lists data's keys in sorted order.

#### `yaml.merge(object data1, object data2) object`

**Deprecated.** Deprecated: use {...data1, ...data2} for a shallow merge, or merge nested objects with a loop, instead.

merge deep-merges data2 into a copy of data1: nested objects merge, and
any other value in data2 replaces data1's.

#### `yaml.mergeCloned(object data1, object data2) object`

**Deprecated.** Deprecated: use {...data1, ...data2} for a shallow merge, or merge nested objects with a loop, instead.

mergeCloned is yaml.merge under another name.

#### `yaml.parse(string text) result<any, error>`

parse reads a YAML document from text into OSL values: mappings become
objects (keys as strings), sequences arrays, and scalars strings,
numbers, booleans or null; timestamps stay strings as written. An empty document is null. Fails with
ParseError, naming the line, for malformed YAML, duplicate keys and
recursive aliases.

#### `yaml.parseFile(string path) result<any, error>`

parseFile reads and parses the YAML file at path like yaml.parse. Fails
with NotFoundError, PermissionError or IOError when the file can't be
read and ParseError when it isn't valid YAML.

#### `yaml.set(object data, any key, any value) object`

**Deprecated.** Deprecated: use data[key] = value instead.

set sets data[key] = value in place and returns data.

#### `yaml.stringify(any value, object? options) string`

stringify encodes value as YAML text, with object keys in sorted order
and a trailing newline. Options: indent (spaces per nesting level, 2 to
16, default 4). Throws TypeError for values YAML can't represent, such
as functions, and ValueError for a value that contains itself.

#### `yaml.toJSON(any yamlData) string`

**Deprecated.** Deprecated: use json.stringify(yaml.parse(text)) instead.

toJSON converts YAML text to JSON text, or a JSON {"error": message}
object when the YAML is malformed.

#### `yaml.values(object data) array`

**Deprecated.** Deprecated: use data.getValues() instead.

values lists data's values.
