Skip to content

elide:yaml

The elide:yaml module: parse, stream and write YAML as plain JavaScript data.

Read offlineelide help api-yaml

On this page

elide:yaml parses, streams and writes YAML 1.2. Parsed documents are plain data: mappings are objects, sequences are arrays and scalars are primitives. There is nothing to install.

js
import { parse, stringify } from "elide:yaml";

const config = parse(`
port: 8080
hosts: [a, b]
tls:
  cert: ./cert.pem
`);
config.port; // 8080
config.hosts; // ["a", "b"]
stringify(config);
// "port: 8080\nhosts:\n  - a\n  - b\ntls:\n  cert: ./cert.pem\n"
utf-8 · js · Lines: 12

Import it as elide:yaml. A bare "yaml" specifier resolves to the npm package of that name.

Read a file

yaml
# team.yaml
team: platform
users:
  - id: 1
    name: ada
  - id: 2
    name: lin
utf-8 · yaml · Lines: 7
js
// team.mjs
import { parseFile } from "elide:yaml";

const { team, users } = parseFile("team.yaml");
for (const user of users) console.log(team, user.id, user.name);
utf-8 · js · Lines: 5

elide run team.mjs prints:

text
platform 1 ada
platform 2 lin
utf-8 · text · Lines: 2

Parsing

  • parse(input, options?) reads a string, Uint8Array, ArrayBuffer or DataView (UTF-8, or a BOM-marked encoding) holding one document and returns its value. Empty input is null; input holding several documents throws.
  • parseAll(input, options?) returns every document of a stream as an array.
  • parseDocument(input, options?) and parseAllDocuments(input, options?) return documents, which carry their directives.
  • parseFile(path, options?) reads one document from a file path or file: URL.
  • stream(source, options?) reads documents one at a time from a path, file: URL, bytes or a ReadableStream of bytes. See Streaming.

Values follow the YAML 1.2 core schema: true/false are booleans, null/~/empty are null, decimal, 0x and 0o integers and floats (including .inf and .nan) are numbers, and everything else is a string. Integers outside ±(253−1) are bigints. Keys are always strings, so 1: one gives { "1": "one" }.

js
parse("a: 1\nb: 1.5\nc: yes\nd: 2024-01-01\n");
// { a: 1, b: 1.5, c: "yes", d: "2024-01-01" }
parse("big: 18446744073709551616"); // { big: 18446744073709551616n }
utf-8 · js · Lines: 3

Anchors, aliases and merge keys

Aliases refer to the value built for their anchor, so *ref is the same object as &ref. An alias to a node that contains it is rejected with ERR_YAML_SYNTAX. Merge keys (<<) are folded into their mapping with merge: true, or by default when reading YAML 1.1; otherwise << is an ordinary key.

js
const v = parse("base: &b\n  x: 1\nref: *b\n");
v.ref === v.base; // true

parse("base: &b\n  a: 1\n  b: 2\nchild:\n  <<: *b\n  b: 3\n", { merge: true }).child;
// { a: 1, b: 3 }
utf-8 · js · Lines: 5

Tags

!!binary reads as a Uint8Array and !!timestamp as a Date. Other tags leave the value as the schema parses it; with unknownTags: "error" they throw instead. customTags maps a tag to a function that receives the parsed value and the tag and returns what to use:

js
parse("db: !secret DATABASE_URL", {
  customTags: { "!secret": (name) => process.env[name] },
});
utf-8 · js · Lines: 3

Tag URIs are given in full for the standard tags ("tag:yaml.org,2002:timestamp") and as written for local tags ("!secret").

Options

OptionDefaultMeaning
version"1.2""1.1" selects the YAML 1.1 schema and merge keys
schema"core""json" (JSON forms only), "failsafe" (strings only) or "yaml-1.1" (yes/no, 010, 1_000)
mergefalseFold << merge keys
uniqueKeystrueReject a mapping that repeats a key; false keeps the last value
aliasestrueAllow anchors and aliases
unknownTags"keep""error" rejects tags the schema does not know
customTagsnoneTag handlers, by tag
limitssee belowResource bounds

limits bounds maxBytes (input size, 64 MiB), maxNodes (one million), maxDepth (128), maxAliases (10 000) and maxAliasExpansion (nodes aliases may expand to, one million).

Documents

parseDocument() returns the document’s contents, the %YAML directive as version (or null) and %TAG prefixes by handle as tags, with toJS() and toString(options?):

js
import { parseDocument } from "elide:yaml";

const doc = parseDocument("%YAML 1.2\n---\nname: elide\n");
doc.contents; // { name: "elide" }
doc.version; // "1.2"
doc.toString(); // "name: elide\n"
utf-8 · js · Lines: 6

Streaming

stream() returns an async iterator that yields one document per step:

js
import { stream } from "elide:yaml";

for await (const record of stream("records.yaml")) {
  console.log(record.id);
}
utf-8 · js · Lines: 5

Documents are cut at --- and ... lines and parsed as the loop reaches them, so a file of many documents never needs to be in memory at once. maxDocumentBytes (default 1 MiB) bounds one document. Breaking out of the loop, await reader.close() and await using reader = stream(...) all stop reading and release the source. Bytes passed to stream() are copied first.

Writing

stringify(value, options?) writes block-style YAML ending in a newline. Objects serialize as JSON.stringify would (own enumerable keys, toJSON(), undefined and functions skipped in objects and written as null in arrays), plus Map as a mapping, Set as a sequence, Date as a timestamp and byte views as !!binary. Strings that would read back as something else ("123", "true", "a: b") are quoted, and multi-line strings become block scalars. Repeated objects become anchors and aliases. Cycles throw a TypeError with code ERR_INVALID_ARG_VALUE, regardless of noRefs. stringifyAll(documents, options?) separates documents with --- lines.

js
import { stringify } from "elide:yaml";

stringify({ name: "ada", tags: ["x", "y"], note: "line one\nline two" });
// "name: ada\ntags:\n  - x\n  - y\nnote: |-\n  line one\n  line two\n"

stringify({ a: [1, 2] }, { flowLevel: 0 }); // "{ a: [1, 2] }\n"
utf-8 · js · Lines: 6
OptionDefaultMeaning
indent2Spaces per level, 1 to 8
flowLevel-1Depth from which collections use flow style; 0 for all, -1 for none
sortKeysfalseSort mapping keys
noRefsfalseWrite repeated objects inline
quotingType'"'"'" quotes with single quotes
forceQuotesfalseQuote every string

Errors

Failures throw errors with a code; parse errors also carry 1-based line and column properties and name the position in their message.

CodeErrorCause
ERR_YAML_SYNTAXSyntaxErrorMalformed YAML, a duplicate key, a rejected alias or tag, or several documents where one was expected
ERR_YAML_LIMITErrorA limits bound or maxDocumentBytes was exceeded
ERR_YAML_CLOSEDErrorA closed stream was read
ERR_YAML_IOErrorThe source failed while reading
ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUETypeErrorAn invalid argument, option or value to write
ERR_OUT_OF_RANGERangeErrorA numeric option out of range

File errors (ENOENT, EACCES, EISDIR) match node:fs.

TypeScript

@elide-dev/types declares the module. Name the shape you expect with a type argument:

ts
import { parse, stream } from "elide:yaml";

interface Config {
  port: number;
  hosts: string[];
}

const config = parse<Config>(text);
for await (const record of stream<{ id: string }>("records.yaml")) {
  record.id;
}
utf-8 · ts · Lines: 11

With elide:csv

Parsed documents are plain data, so elide:csv writes a list of mappings directly:

js
// to-csv.mjs
import { parseFile } from "elide:yaml";
import { stringify } from "elide:csv";

const { users } = parseFile("team.yaml");
process.stdout.write(stringify(users, { recordSeparator: "\n" }));
utf-8 · js · Lines: 6

elide run to-csv.mjs prints:

text
id,name
1,ada
2,lin
utf-8 · text · Lines: 3

Was this page helpful?