@stoplight/yaml-ast-parser
A TypeScript-friendly YAML parser that returns a concrete syntax-aware tree instead of only turning YAML into plain JavaScript values. Every node carries character offsets, kind information, parent links and collected parse errors; the document also retains comments. It began as a js-yaml fork and adds error recovery plus a dedicated node kind for RAML-style !include references, which makes it useful inside editors, linters and API-description tooling.
Keep it for existing Stoplight or RAML tooling that depends on this exact AST, especially when recoverable errors and source ranges matter. For a new YAML editor or validator, yaml has a more current model and a healthier documentation story.
Use it if
- You are building a YAML editor, linter or language tool and need exact source ranges for nodes and comments
- You need a best-effort tree plus diagnostics after malformed YAML instead of a parser that stops at the first error
- You process RAML or another format that uses !include and want include references represented in the tree
- You maintain Stoplight-era tooling that already consumes its Kind enum and YAMLNode interfaces
- You only need a JavaScript object from valid YAML: the yaml or js-yaml packages have clearer modern documentation and avoid exposing a custom AST model
- You want an actively evolving parser: version 0.0.50 was published in March 2024 and the repository's last push was in May 2024, despite millions of weekly downloads
- You require a stable public contract: the package is still on 0.0.x, several node fields are typed as any, and parent is declared as always present even though a root has no meaningful parent
- You need a YAML 1.2-focused document editing API that preserves and rewrites formatting; the README describes inspection, not round-trip AST editing
- You expect complete standalone docs: the README covers the node kinds and a single load call, then points readers to source and tests for the rest of the exported API
Setup reality
Installation is just npm install @stoplight/yaml-ast-parser. Version 0.0.50 declares no runtime dependencies and ships JavaScript plus .d.ts files, so there is no native build, peer dependency or config file. The friction is in the old API shape. The package is CommonJS-first, its README uses require, and its declarations were emitted by TypeScript 3.8, so ESM projects may need a namespace import or a default-interop setting that matches their bundler. load() does not return ordinary objects. You must branch on the numeric Kind enum and cast to YAMLScalar, YAMLMapping, YamlMap or YAMLSequence before reading type-specific fields. Parse failures are normally accumulated in node.errors so callers must inspect diagnostics explicitly; a returned tree does not prove the input was valid. Ranges are zero-based character offsets into the original JavaScript string, not byte offsets or line-column pairs. Comments live on the document as separate ranges and are not attached semantically to a mapping. The package recognizes RAML !include but does not read the referenced file for you. Finally, safeLoad and safeLoadAll are inherited names from the js-yaml lineage; do not assume they sanitize application-level data or make untrusted YAML safe to execute in some broader sense.
Patterns
Parse YAML into an ASTparse-document
import * as YAML from '@stoplight/yaml-ast-parser';
const source = 'name: demo\nenabled: true\n';
const root = YAML.load(source);
if (root.kind !== YAML.Kind.MAP) {
throw new Error('Expected a mapping document');
}
console.log(root.startPosition, root.endPosition, root.errors);load returns YAMLNode, not a plain JavaScript object. Check root.errors even when a tree was returned because the parser recovers from many syntax errors.
Find a value in a YAML mapread-mapping
import { Kind, YAMLMapping, YAMLScalar, YamlMap } from '@stoplight/yaml-ast-parser';
const map = root as YamlMap;
const pair = map.mappings.find((item: YAMLMapping) => item.key.value === 'name');
if (pair?.value.kind === Kind.SCALAR) {
console.log((pair.value as YAMLScalar).value);
}A map contains YAMLMapping pairs; the key and value are nodes. The declarations leave some convenience fields as any, so explicit kind checks preserve type safety.
Walk every AST nodewalk-tree
import { Kind, YAMLNode, YamlMap, YAMLSequence } from '@stoplight/yaml-ast-parser';
function* walk(node: YAMLNode): Generator<YAMLNode> {
yield node;
if (node.kind === Kind.MAP) {
for (const pair of (node as YamlMap).mappings) yield* walk(pair);
} else if (node.kind === Kind.MAPPING) {
yield* walk(node.key);
yield* walk(node.value);
} else if (node.kind === Kind.SEQ) {
for (const item of (node as YAMLSequence).items) yield* walk(item);
}
}
for (const node of walk(root)) console.log(Kind[node.kind]);Nodes do not expose an accept or children method. The repository tests implement the same switch-on-Kind visitor pattern.
Recover the original source for a nodeslice-source-range
const pair = (root as YAML.YamlMap).mappings[0];
const originalPairText = source.slice(pair.startPosition, pair.endPosition);
const originalValueText = source.slice(
pair.value.startPosition,
pair.value.endPosition
);Positions index the original JavaScript string. They are UTF-16 code-unit offsets, so do not treat them as UTF-8 byte positions when applying edits to a file buffer.
Report parser errors and warningscollect-diagnostics
const document = YAML.load(source, { filename: 'config.yaml' });
const diagnostics = document.errors.map((error) => ({
message: error.reason,
warning: error.isWarning,
line: error.mark?.line ?? 0,
column: error.mark?.column ?? 0,
position: error.mark?.position ?? 0
}));Line and column values in Mark are zero-based. Diagnostics are collected on the document rather than reliably thrown, which is central to the parser's editor-oriented recovery behavior.
Extract comments with their rangesread-comments
const document = YAML.safeLoad('name: demo # shown in UI\n');
for (const comment of document.comments ?? []) {
console.log({
text: comment.value.trim(),
range: [comment.startPosition, comment.endPosition],
});
}Comments are stored as a flat document-level list. The parser does not decide which mapping or sequence item a comment belongs to.
Parse a multi-document YAML streamparse-multiple-documents
const documents: YAML.YAMLNode[] = [];
YAML.loadAll('name: first\n---\nname: second\n', (document) => {
documents.push(document);
});
console.log(documents.length); // 2loadAll reports documents through a callback and returns void. Inspect errors on every collected document separately.
Classify and convert a scalarinfer-scalar-type
const scalar = pair.value as YAML.YAMLScalar;
const type = YAML.determineScalarType(scalar);
let value: unknown = scalar.value;
if (type === YAML.ScalarType.bool) value = YAML.parseYamlBoolean(scalar.value);
if (type === YAML.ScalarType.int) value = YAML.parseYamlBigInteger(scalar.value);
if (type === YAML.ScalarType.float) value = YAML.parseYamlFloat(scalar.value);YAMLScalar.value is declared as a string. Type inference and conversion are separate steps, and parseYamlBigInteger may return either number or bigint.
Choose how duplicate keys are treatedhandle-duplicate-keys
const strictDoc = YAML.load('name: first\nname: second\n');
console.log(strictDoc.errors.map((error) => error.reason));
const permissiveDoc = YAML.load('name: first\nname: second\n', {
ignoreDuplicateKeys: true,
});Ignoring duplicate-key diagnostics does not make duplicate keys unambiguous for downstream consumers. Prefer rejecting them in configuration and API-description files.
Find RAML include referencesfind-include-references
const document = YAML.load('schema: !include schema.json\n');
const includes = [...walk(document)].filter(
(node) => node.kind === YAML.Kind.INCLUDE_REF
);
for (const include of includes) {
console.log(source.slice(include.startPosition, include.endPosition));
}INCLUDE_REF records the syntax only. The package does not resolve paths, read files, enforce a project root or prevent directory traversal for you.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| yaml | npm | Choose it for a maintained YAML 1.2 parser with Document and CST APIs plus practical round-trip editing |
| yaml-eslint-parser | npm | Choose it when the target consumer is ESLint and you need an ESTree-like YAML AST with visitor keys |
| yaml-ast-parser | npm | Choose the original MuleSoft package only when an existing dependency requires that exact package name |