JSON.stringify(YAML.parse(text), null, 2) converts YAML text to JSON with the yaml package (npm install yaml), which reads YAML 1.2 unless you ask for 1.1.
import { readFileSync } from 'node:fs';
import YAML from 'yaml';
const text = readFileSync('config.yaml', 'utf8');
console.log(JSON.stringify(YAML.parse(text), null, 2));
For this config.yaml:
name: deploy
on: push
region: NO
mode: 0755
released: 2026-10-15
version: 1.10
yaml 2.9.1 on Node 22 prints:
{
"name": "deploy",
"on": "push",
"region": "NO",
"mode": 755,
"released": "2026-10-15",
"version": 1.1
}
The converter below is built on the same package and prints the same JSON.
YAML 1.2 against 1.1
Under YAML 1.2’s core schema only true and false are booleans, so on and NO stay strings, 0755 is decimal (octal is 0o755) and a date is plain text. YAML 1.1, which PyYAML and many older tools follow, reads the same file differently:
JSON.stringify(YAML.parse(text, { version: '1.1' }))
// {"name":"deploy","true":"push","region":false,"mode":493,"released":"2026-10-15T00:00:00.000Z","version":1.1}
1.10 is the number 1.1 under both, so quote version strings.
js-yaml: check the major version
js-yaml 4 is import yaml from 'js-yaml' and yaml.load(text). It reads booleans and octals the 1.2 way but keeps YAML 1.1’s timestamps, so the date becomes a Date, and JSON.stringify writes "released":"2026-10-15T00:00:00.000Z", a time and a time zone the file never had. Old answers call yaml.safeLoad, which throws in js-yaml 4: Function yaml.safeLoad is removed in js-yaml 4. Use yaml.load instead, which is now safe by default.
js-yaml 5, from June 2026, is imported by name, import { load } from 'js-yaml'; the old default import fails with SyntaxError: The requested module 'js-yaml' does not provide an export named 'default'. Its default core schema prints exactly the JSON above. load(text, { schema: YAML11_SCHEMA }) gives the 1.1 reading.
Merge keys
Merge keys (<<: *defaults, all over Docker Compose files) are a YAML 1.1 type, and YAML.parse leaves them alone by default. For
x-defaults: &defaults
restart: unless-stopped
services:
worker:
restart: "no"
<<: *defaults
the worker comes out as {"restart":"no","<<":{"restart":"unless-stopped"}}. YAML.parse(text, { merge: true }) applies the merge: {"restart":"no"}, since a key written in the mapping wins. js-yaml 4 merges by default; js-yaml 5 needs schema: CORE_SCHEMA.withTags(mergeTag). The tool merges.
Several documents
YAML.parse throws on a file with --- between documents: Source contains multiple documents; please use YAML.parseAllDocuments(). This gives an array, which is what the tool makes of such a file:
YAML.parseAllDocuments(text).map((doc) => doc.toJS())
// [{"kind":"ConfigMap","metadata":{"name":"app-config"}},{"kind":"Secret","metadata":{"name":"app-secret"}}]
In js-yaml, that’s loadAll(text).