JSON-to-YAML in JavaScript is a one-liner with any reasonable library. The interesting bits are output style (block vs flow), key ordering, and round-trip support. js-yaml covers the basics; the eemeli/yaml package covers everything.
Method 1: js-yaml (the standard option)
js-yaml is the most-downloaded YAML library on npm. Lightweight (~150KB), works in browser and Node, supports YAML 1.1.
npm install js-yaml
import yaml from "js-yaml";
import fs from "node:fs";
function jsonToYaml(inPath, outPath) {
const data = JSON.parse(fs.readFileSync(inPath, "utf8"));
const yamlText = yaml.dump(data, {
sortKeys: false, // preserve JSON insertion order
lineWidth: -1, // don't wrap long lines
noRefs: true, // disable anchors/aliases
});
fs.writeFileSync(outPath, yamlText);
}
jsonToYaml("config.json", "config.yaml");
For browser use:
import yaml from "js-yaml";
document.querySelector("input[type=file]").addEventListener("change", async (e) => {
const text = await e.target.files[0].text();
const yamlText = yaml.dump(JSON.parse(text), { sortKeys: false, lineWidth: -1 });
const blob = new Blob([yamlText], { type: "text/yaml" });
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement("a"), { href: url, download: "out.yaml" });
a.click();
});
js-yaml is YAML 1.1 by default — that means 'yes', 'no', 'on', 'off' get coerced to booleans. To force YAML 1.2 behavior, pass schema: yaml.JSON_SCHEMA.
Method 2: yaml (eemeli, full YAML 1.2 + round-trip)
The yaml package by Eemeli Aro is the most spec-compliant YAML library on npm. Slightly larger than js-yaml (~250KB) but supports YAML 1.2 fully and preserves comments on round-trips.
npm install yaml
import { stringify } from "yaml";
import fs from "node:fs";
function jsonToYaml(inPath, outPath) {
const data = JSON.parse(fs.readFileSync(inPath, "utf8"));
const yamlText = stringify(data, {
indent: 2,
lineWidth: 0, // 0 = no wrapping
minContentWidth: 0,
blockQuote: "literal", // multi-line strings use | instead of escape codes
});
fs.writeFileSync(outPath, yamlText);
}
jsonToYaml("config.json", "config.yaml");
For round-trip work (parse YAML, modify, write back without losing comments):
import { parseDocument } from "yaml";
import fs from "node:fs";
const doc = parseDocument(fs.readFileSync("original.yaml", "utf8"));
doc.set("version", "1.2.0");
fs.writeFileSync("modified.yaml", String(doc)); // comments preserved
Use this package when YAML output will be edited by humans (k8s configs, Helm charts, GitHub Actions). Use js-yaml when YAML is purely machine-generated.
Method 3: ChangeThisFile API (no library, edge-friendly)
If you're on Cloudflare Workers, Vercel Edge, or want to keep your bundle small, the API does the conversion in one fetch call. Free tier gives 100 conversions/month.
const API_KEY = "ctf_sk_your_key_here";
async function jsonToYaml(jsonText, filename = "input.json") {
const form = new FormData();
form.append("file", new Blob([jsonText], { type: "application/json" }), filename);
form.append("source", "json");
form.append("target", "yaml");
const response = await fetch("https://changethisfile.com/v1/convert", {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}` },
body: form,
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
return await response.text();
}
const yaml = await jsonToYaml(JSON.stringify({ name: "app", version: "1.0" }));
console.log(yaml);
The API also accepts JSON5 (with comments) and JSONC inputs — useful for converting tsconfig.json or VS Code config files which neither js-yaml nor yaml-package handle natively.
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| js-yaml | Default for most JS projects, smallest bundle | YAML 1.1 only by default, loses comments on round-trips |
| yaml (eemeli) | YAML 1.2, round-trip work, k8s/Helm configs | Slightly larger bundle |
| ChangeThisFile API | Edge runtimes, JSON5/JSONC inputs, multi-language teams | Network call, per-call cost |
CLI alternative: yq or json2yaml
For one-off conversions, yq is the fastest CLI option (Go-based, no Node startup cost).
brew install yq
yq -P -oy config.json > config.yaml
# Or via npm:
npm install -g json2yaml
json2yaml config.json > config.yaml
The npm json2yaml package is just a CLI wrapper around js-yaml. yq uses Go's YAML library — slightly different defaults but functionally equivalent output.
Production tips
- Set lineWidth: -1 (js-yaml) or 0 (yaml package). Both libraries wrap long strings by default, which makes diffs ugly when configs change. Disable wrapping for stable diffs.
- Preserve key order. sortKeys: false in js-yaml, default in yaml package. Sorted output makes config files harder to scan because related fields scatter alphabetically.
- Use the JSON schema for round-trip safety. If you do JSON→YAML→JSON cycles, set js-yaml's schema: yaml.JSON_SCHEMA so the dump uses the same type assumptions as JSON.
- Quote ambiguous strings. '01' as YAML 1.1 becomes int 1. The eemeli yaml package does this correctly by default; js-yaml needs schema: yaml.CORE_SCHEMA.
For most projects, js-yaml is enough. For comment-preserving round-trips, the eemeli yaml package. For edge runtimes or unusual JSON dialects, the API. Free tier is 100 conversions/month, no card.