YAML to JSON Converter
Last updated: 2026-09-17
Paste YAML and get formatted JSON output. Multi-document YAML files (separated by ---) produce a JSON array. The converter handles anchors, aliases, Unicode, and all YAML 1.1/1.2 constructs supported by js-yaml. Everything runs locally.
What this tool does
Converts a YAML document (or multi-document YAML) into formatted JSON with 2-space indentation. The parser follows YAML 1.2 conventions via js-yaml’s json schema. Anchors and aliases are resolved into plain values. Multi-document YAML (separated by ---) produces a JSON array with one element per document.
Example
Input:
server:
host: localhost
port: 8080
features:
- auth
- logging
debug: false
Output:
{
"server": {
"host": "localhost",
"port": 8080
},
"features": [
"auth",
"logging"
],
"debug": false
}
Multi-document YAML
Input:
---
name: first
value: 1
---
name: second
value: 2
Output:
[
{ "name": "first", "value": 1 },
{ "name": "second", "value": 2 }
]
A single-document YAML file produces a plain JSON object, not an array.
YAML scalar types and how they map to JSON
The parser resolves unquoted YAML scalars using the JSON schema (YAML 1.2 conventions) before serializing to JSON. The table below shows what each scalar form becomes in the output. Quoted scalars are always strings regardless of their content.
| YAML scalar | JSON value | Notes |
|---|---|---|
42 | 42 | Integer. Leading zeros are allowed by js-yaml’s JSON schema and dropped in the output. |
0xFF | 255 | Hexadecimal integers are accepted and emitted as decimal. |
0o17 | 15 | Octal (YAML 1.2 style) is accepted. |
3.14 | 3.14 | Float. |
1e3 | 1000 | Exponential notation becomes a JSON number. |
.inf | null or Infinity | js-yaml parses .inf/-.inf/.nan; JSON.stringify turns non-finite numbers into null. |
true | true | Boolean. Only true/false (and True/False/TRUE/FALSE) are booleans under the JSON schema. |
false | false | Boolean. |
yes | "yes" | Treated as a string, not boolean. YAML 1.1 would read this as true; this converter follows 1.2. |
no | "no" | String. Same reason as yes. Avoid the Norway problem by quoting country codes. |
null | null | Null. |
~ | null | Tilde is the YAML shorthand for null. |
"" (empty quoted) | "" | Empty string. |
| (empty value) | null | A key with no value, e.g. key: on its own line, resolves to null. |
2024-01-15 | "2024-01-15T00:00:00.000Z" | js-yaml parses dates and timestamps into JavaScript Date objects, which JSON.stringify renders as ISO 8601 strings. |
2024-01-15T10:30:00Z | "2024-01-15T10:30:00.000Z" | Full timestamp, also becomes an ISO string in JSON. |
hello | "hello" | Unquoted plain scalar becomes a string. |
"42" | "42" | Quoted scalars are always strings. |
'0123' | "0123" | Quote values that must stay strings, like zip codes or IDs with leading zeros. |
Numbers larger than JavaScript’s safe integer range (2^53 - 1) lose precision when serialized, because they pass through a JavaScript number. If you need exact large integers, keep them as quoted strings in the YAML.
Converting docker-compose files
A docker-compose YAML file maps cleanly to JSON because it is a single root mapping of service names. This example converts a small compose file with two services, a volume, and a network.
Input (compose.yaml):
services:
web:
image: nginx:1.27
ports:
- "80:80"
- "443:443"
restart: unless-stopped
depends_on:
- api
api:
image: my-api:latest
environment:
DATABASE_URL: postgres://app:secret@db:5432/app
LOG_LEVEL: info
restart: always
volumes:
pgdata:
networks:
default:
driver: bridge
Output:
{
"services": {
"web": {
"image": "nginx:1.27",
"ports": [
"80:80",
"443:443"
],
"restart": "unless-stopped",
"depends_on": [
"api"
]
},
"api": {
"image": "my-api:latest",
"environment": {
"DATABASE_URL": "postgres://app:secret@db:5432/app",
"LOG_LEVEL": "info"
},
"restart": "always"
}
},
"volumes": {
"pgdata": null
},
"networks": {
"default": {
"driver": "bridge"
}
}
}
Note that volumes: pgdata: with no body becomes "pgdata": null in JSON. Compose accepts that as an empty volume definition, and the converter preserves the null rather than inventing an empty object.
Converting OpenAPI specs
OpenAPI and Swagger files are a common conversion target because tooling often expects JSON. The structure is a single root object with openapi, info, paths, and components. This example converts a minimal OpenAPI 3 document describing two endpoints.
Input (openapi.yaml):
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
description: Sample API for converting OpenAPI YAML to JSON.
paths:
/users:
get:
summary: List users
operationId: listUsers
responses:
"200":
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
/users/{id}:
get:
summary: Get a user by id
operationId: getUser
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: A single user
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required:
- id
- name
properties:
id:
type: integer
example: 42
name:
type: string
example: Ada
Output (abbreviated):
{
"openapi": "3.0.3",
"info": {
"title": "Users API",
"version": "1.0.0",
"description": "Sample API for converting OpenAPI YAML to JSON."
},
"paths": {
"/users": {
"get": {
"summary": "List users",
"operationId": "listUsers",
"responses": {
"200": {
"description": "A list of users",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
}
}
},
"/users/{id}": {
"get": {
"summary": "Get a user by id",
"operationId": "getUser",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "A single user",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": {
"type": "integer",
"example": 42
},
"name": {
"type": "string",
"example": "Ada"
}
}
}
}
}
}
The response status code keys ("200") are quoted in the YAML so they stay strings; without quotes, 200 would parse as an integer and the JSON key would become "200" anyway (JSON keys are always strings), but quoting makes the intent explicit and avoids surprises with codes like 404 that some YAML 1.1 parsers would misread. The $ref JSON pointer strings are single-quoted because they contain a colon and slash; double quotes would also work.
YAML anchors and aliases in practice
Anchors (&name) mark a node, and aliases (*name) insert a reference to that same node. Because JSON has no reference concept, the parser expands every alias into a full copy of the anchored value before producing JSON. This is useful for sharing defaults across many entries without repeating them.
Input:
defaults: &defaults
retries: 3
timeout: 30
log_level: warn
services:
auth:
<<: *defaults
endpoint: /auth
timeout: 60
billing:
<<: *defaults
endpoint: /billing
audit:
<<: *defaults
endpoint: /audit
log_level: debug
Output:
{
"defaults": {
"retries": 3,
"timeout": 30,
"log_level": "warn"
},
"services": {
"auth": {
"retries": 3,
"timeout": 60,
"log_level": "warn",
"endpoint": "/auth"
},
"billing": {
"retries": 3,
"timeout": 30,
"log_level": "warn",
"endpoint": "/billing"
},
"audit": {
"retries": 3,
"timeout": 30,
"log_level": "debug",
"endpoint": "/audit"
}
}
}
The <<: *defaults merge key copies the keys from the anchored mapping into the current mapping. Keys defined directly in the current mapping override merged keys, which is why auth.timeout is 60 and audit.log_level is "debug". The anchored defaults mapping itself also appears in the output as a normal object; if you do not want it in the JSON, define the anchor on a mapping that you never emit at the top level, or remove the key after conversion.
Aliases can also point at scalars and sequences, not just mappings. For example, common_env: &env [REGION, STAGE] followed by env: *env produces "env": ["REGION", "STAGE"] in the JSON. Circular aliases (an alias that eventually points back to itself) cause a parser error and are not supported.
Converting Kubernetes manifests
Kubernetes manifests are frequently stored as multi-document YAML, with each --- separator introducing a new resource. Pasting a whole manifest file into the converter yields a JSON array whose elements are the individual resources, in file order. This is handy when a controller or admission webhook expects JSON, or when you want to diff two manifests structurally.
Input (deployment.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
labels:
app: checkout
spec:
replicas: 3
selector:
matchLabels:
app: checkout
template:
metadata:
labels:
app: checkout
spec:
containers:
- name: checkout
image: registry.example.io/checkout:2.4.1
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: checkout
spec:
type: ClusterIP
selector:
app: checkout
ports:
- port: 80
targetPort: 8080
Output (abbreviated to the array shape and the second element):
[
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "checkout",
"labels": { "app": "checkout" }
},
"spec": {
"replicas": 3,
"selector": { "matchLabels": { "app": "checkout" } },
"template": {
"metadata": { "labels": { "app": "checkout" } },
"spec": {
"containers": [
{
"name": "checkout",
"image": "registry.example.io/checkout:2.4.1",
"ports": [ { "containerPort": 8080 } ],
"resources": {
"requests": { "cpu": "100m", "memory": "128Mi" },
"limits": { "cpu": "500m", "memory": "512Mi" }
}
}
]
}
}
}
},
{
"apiVersion": "v1",
"kind": "Service",
"metadata": { "name": "checkout" },
"spec": {
"type": "ClusterIP",
"selector": { "app": "checkout" },
"ports": [ { "port": 80, "targetPort": 8080 } ]
}
}
]
Quantity strings like 100m, 128Mi, and 512Mi stay strings because they are not valid numbers under the JSON schema; Kubernetes parses them separately, so preserving the text is correct. The apiVersion value apps/v1 is a plain scalar and becomes the string "apps/v1" despite the slash, because there is no space after the colon. Labels and selectors flow through unchanged, which makes the JSON suitable for feeding directly into an API request body.
How the converter processes your input
The conversion runs entirely in your browser through a Web Worker, so the YAML never leaves your machine. Internally the worker calls js-yaml’s loadAll with the { json: true } option and a callback that collects each parsed document into an array. The json flag selects js-yaml’s JSON schema, which restricts implicit tag resolution to the types JSON supports plus the YAML timestamp type: integers, floats, booleans, null, strings, dates, and mappings or sequences of those types.
After collection, the worker filters out undefined documents, which appear when a YAML stream contains a bare --- with no body. If exactly one document remains, it is serialized with JSON.stringify(value, null, 2) and returned as a single object. If two or more documents remain, the array is serialized the same way, producing a top-level JSON array. If no documents remain, the result is an error rather than an empty array, because an empty stream usually means the input was whitespace or comments only.
The json: true option has a second effect beyond schema selection: it makes duplicate mapping keys overwrite earlier values instead of throwing, mirroring JSON.parse. Without that flag, repeated keys raise a YAMLException. If you need strict duplicate detection, run a separate validation pass without the flag before converting.
Error reporting surfaces the message from the thrown YAMLException, which includes the line and column of the problem whenever js-yaml can pinpoint it. Common errors include tabs used for indentation, a mapping key missing its colon, an alias pointing at an anchor that was never defined, and a block sequence entry indented fewer spaces than its parent mapping key.
Common mistakes when converting YAML to JSON
- Forgetting that unquoted
yes/no/on/offbecome strings, not booleans. Under the YAML 1.2 JSON schema these are plain strings. If your original file was written for a YAML 1.1 parser that treated them as booleans, the JSON types will differ from what you expect. Quote them or rewrite them astrue/falsefirst. - Leading zeros dropped from string-like numbers. A zip code written as
02134parses as the integer2134and serializes as"2134"in JSON. Wrap values that must keep leading zeros in quotes:"02134". - Tabs used for indentation. YAML rejects tab characters for indentation and the parser will throw. Configure your editor to insert spaces, or run the file through
expandbefore pasting. - Mixing indentation widths. YAML allows any consistent indent as long as it is spaces, but switching from two spaces to four mid-file breaks parsing. Keep one width per nesting level.
- Expecting comments to survive. JSON has no comments, so every
#comment is dropped during conversion. If you need to keep commentary, move it into a dedicated field such as_commentbefore converting. - Assuming duplicate keys are kept. With the
json: trueoption, duplicate keys in a mapping are overwritten (later wins) to matchJSON.parsebehavior rather than throwing. If you need to detect duplicates, validate the YAML separately before converting. - Large integers losing precision. Values above
2^53 - 1pass through a JavaScript number and may round. Treat them as strings if exact magnitude matters. - Plain-colon values breaking. A scalar like
time: 10:30looks like a mapping to the parser because of the colon followed by a space. Quote it ("10:30") or use a different separator. - Trusting date-like strings to stay strings.
2024-01-15parses to a Date and serializes as an ISO 8601 string. That is usually fine, but if a field is meant to be a free-text label that happens to look like a date, quote it. - Ignoring the Norway problem for short uppercase codes. Under YAML 1.1,
NOmeansfalse. This converter uses 1.2 soNOstays a string, but if you round-trip through a 1.1 tool first, the damage is already done. Quote country and language codes.
When to use JSON instead of YAML
JSON is the better choice for machine-to-machine data interchange. It has a single specification (RFC 8259), no ambiguity around booleans or null, and every parser handles it the same way. YAML is better for human-edited configuration (Kubernetes, GitHub Actions, docker-compose, OpenAPI) because of comments and indentation-based structure, but those same features introduce parsing complexity.
Gotchas
The Norway problem. In YAML 1.1, the country code NO is interpreted as boolean false. The same applies to yes, no, on, off. This converter uses YAML 1.2 conventions where only true and false are booleans, but if your YAML was written for a YAML 1.1 parser, string values like no may have been intended as strings.
Tabs for indentation. YAML forbids tabs for indentation. If your file contains tabs, the parser will report an error. Convert tabs to spaces in your editor before pasting.
Merge keys. YAML merge keys (<<: *anchor) are supported and resolved into the output. The merged object appears as a flat object in JSON with all properties combined.
Quoting numbers-as-strings. A YAML value like 0123 may be interpreted as a number. Wrap it in quotes to preserve it as a string.
Do it in code
Node.js (js-yaml):
const yaml = require('js-yaml');
const fs = require('fs');
const doc = yaml.load(fs.readFileSync('config.yaml', 'utf8'), { json: true });
console.log(JSON.stringify(doc, null, 2));
Python (PyYAML):
import yaml, json
with open('config.yaml') as f:
doc = yaml.safe_load(f)
print(json.dumps(doc, indent=2))
CLI (yq):
yq '.' config.yaml -o json
This uses the Go-based yq (mikefarah/yq). The Python-based yq uses a different syntax: yq -j '.' config.yaml.
Round-tripping YAML through JSON
Converting YAML to JSON and then back to YAML should, in principle, reproduce the same underlying data structure. In practice the round trip is lossy in several predictable ways. Comments are lost on the first conversion because JSON has no syntax for them, so when you serialize back to YAML the commentary is gone. Anchors and aliases are expanded into full copies during the YAML-to-JSON step, and the return trip has no way to reconstitute them, so every previously shared value is duplicated inline. Dates and timestamps that js-yaml parsed into JavaScript Date objects become ISO 8601 strings in the JSON; converting back yields a string scalar, not the original date literal. Finally, a multi-document YAML file becomes a single JSON array, so the reverse conversion produces one array-valued document rather than several independent ones. If preserving comments, anchors, original date formatting, or document boundaries matters, keep the YAML source and edit it directly instead of round-tripping through JSON.
Edge cases handled
- Multi-document YAML (separated by
---) produces a JSON array with one element per document. - Anchors and aliases are resolved into their referenced values; JSON output contains no references.
- Merge keys (
<<: *anchor) are expanded into flat objects. - Unicode characters are preserved in the JSON output without escaping.
- Empty YAML mappings become
{}, empty sequences become[]. - YAML null values (
~,null, empty) become JSONnull. - A multi-document YAML stream that collapses to a single remaining document after empty ones are filtered out is serialized as a bare object, not wrapped in a one-element array.
- Duplicate mapping keys are resolved last-value-wins to mirror
JSON.parsesemantics, rather than raising an exception mid-conversion.