YAML to JSON Converter

Output
Examples:

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 scalarJSON valueNotes
4242Integer. Leading zeros are allowed by js-yaml’s JSON schema and dropped in the output.
0xFF255Hexadecimal integers are accepted and emitted as decimal.
0o1715Octal (YAML 1.2 style) is accepted.
3.143.14Float.
1e31000Exponential notation becomes a JSON number.
.infnull or Infinityjs-yaml parses .inf/-.inf/.nan; JSON.stringify turns non-finite numbers into null.
truetrueBoolean. Only true/false (and True/False/TRUE/FALSE) are booleans under the JSON schema.
falsefalseBoolean.
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.
nullnullNull.
~nullTilde is the YAML shorthand for null.
"" (empty quoted)""Empty string.
(empty value)nullA 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

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

Frequently asked questions

How are multi-document YAML files handled?
YAML files separated by --- produce a JSON array where each element is one document. A single-document YAML file produces a single JSON object (not wrapped in an array).
Does it support YAML anchors and aliases?
Yes. Anchors (&) and aliases (*) are resolved by the parser. The output JSON contains the fully expanded values, not the anchor/alias syntax, since JSON does not support references.
What about tabs in YAML?
YAML does not allow tabs for indentation. If your YAML contains tabs, the parser will report an error. Convert tabs to spaces in your editor before pasting.
How are YAML null values handled?
YAML nulls (tilde ~, the word null, or empty values) become JSON null. Empty YAML mappings become empty objects, and empty sequences become empty arrays.
Does it handle YAML 1.1 vs 1.2 differences?
The parser uses js-yaml with the json schema, which follows YAML 1.2 conventions. Booleans are true/false (not yes/no), and numbers follow JSON conventions. If you have YAML 1.1 style booleans (yes/no/on/off), they are treated as strings.

Related converters