YAML to JSON Schema Converter

A schema built from one sample file is a set of guesses. This page makes each guess and then tells you which one it was: why a key became required, why 30 is an integer and "30" is a string, why an unquoted date turned into a timestamp before the generator ever saw the text. Pick the draft, set the required rule, copy or download the result.

YAML to JSON Schema workbench

Required keys
YAML sample
JSON Schema

Every guess, and the reason for it

    A schema from one sample is a list of guesses. Read them.

    JSON Schema describes every document a field will ever hold. A YAML file shows you one. Going from the second to the first means deciding, for each key, whether the value in front of you is the only shape allowed or one of several. No generator knows the answer. What a good one does is make a defensible default and say out loud where the default was applied, so you spend your review time on the twelve lines where the guess was wrong instead of rereading the two hundred where the guess was fine. That is the reason the panel under the output exists. Each row is one decision the generator took on your behalf. Amber rows are the ones a validator will punish you for later.

    The required trap

    Every key in a sample is present, by definition. A generator with no other information has three honest choices: mark all of them required, mark none, or mark the ones with a non-null value. The first option is the strictest and is the default here, because a schema that is too strict fails loudly on the first document that omits a key, and a schema that is too loose fails silently forever. Switch to Keys with a value when your sample uses ~ or an empty value to mean "optional, not set today". Switch to None when the file is a partial override, such as a Helm values file, where nearly every key is optional by design.

    YAML sample
    service: api.toolexe.com
    replicas: 3
    canary: ~
    region: eu-west-1
    required, under each rule
    Every key present ["service","replicas","canary","region"]Keys with a value ["service","replicas","region"]None (key omitted)

    When the sample is a list of objects, the generator has more to go on. It reads every item, not the first one, and a key becomes required only when every item carries it. A key that shows up in two of three items is left out of required and gets an info row naming the count, so you know the omission was evidence, not a default.

    30, "30", 3.0 and 0755

    YAML resolves scalar types before any converter runs. This page reads YAML through js-yaml 4, which follows the YAML 1.2 core schema, and the schema you get reflects what the parser handed over, not what you typed.

    Dates that were parsed before you asked

    An unquoted 2024-03-08 or 2024-03-08T09:30:00Z matches the YAML timestamp tag, and js-yaml turns it into a JavaScript Date object. The generator never sees the original text. It writes "type": "string" and picks "format": "date" when the parsed value sits at midnight UTC, "format": "date-time" otherwise. That midnight rule is a heuristic. A value written as 2024-03-08T00:00:00Z comes out as date even though the source carried a time. If the distinction matters, quote the value in the YAML so it stays a string, and format detection reads the actual characters.

    With formats enabled, quoted strings are checked against a short list: email, uri, date, date-time, time, ipv4, ipv6 and uuid. Detection needs a full match, so ops@toolexe.com becomes email but "contact ops@toolexe.com first" does not. Hostnames without a scheme, such as api.toolexe.com, are left as plain strings on purpose. The hostname format is loose enough to match ordinary words, and a false positive there costs more than a missed one.

    Lists: every item, then a merge

    The shortcut most generators take is to type a list from its first item. This page reads all of them and merges. Two objects merge property by property. An integer and a float merge to number. Two strings with different formats merge to a string with no format. When the items share nothing, such as a list of [8080, "auto"], the output is "items": {"anyOf": [...]}, and an amber row asks whether the mix is intentional. An empty list produces "items": {}, because there is nothing to infer from, and that row is amber too.

    three items, one key missing
    owners:- name: Maya Lindqvist
    email: maya@toolexe.com
    pager: true
    - name: Tomasz Wierzbicki
    email: tomasz@toolexe.com
    - name: Ifeoma Adeyemi
    email: ifeoma@toolexe.com
    pager: false
    items, merged
    "items": {"type": "object","properties": {"name": { "type": "string" },"email": { "type": "string", "format": "email" },"pager": { "type": "boolean" }},"required": ["name", "email"]}

    Anchors, aliases and merge keys leave no trace

    YAML lets you define a block once with &base and reuse it with *base or <<: *base. The parser resolves these before returning data, so a document with one anchor and five aliases produces the same schema as one where the block was pasted five times. There is no $ref in the output pointing at a shared definition. If you want reuse in the schema, move the repeated object into $defs by hand after generating. The generator cannot tell a copied block from a referenced one once the parser is done.

    What the draft selector changes

    Three values are offered: draft-07, 2019-09 and 2020-12. The generator emits only keywords whose meaning is identical across all three, so the visible difference is the $schema URL and nothing else. That is deliberate. The keywords that changed between drafts, definitions becoming $defs and array items splitting into prefixItems, are not ones this page writes. Pick the draft your validator expects. Ajv 8 defaults to draft-07 and needs a separate import for 2020-12. Most OpenAPI 3.1 tooling expects 2020-12. Older Java and .NET validators often stop at draft-07.

    A worked example

    The sample loaded by the button is a deploy config for api.toolexe.com. Load it and read the panel. You should see an info row for version: 2.0 explaining why the type is number rather than integer, an amber row for the unquoted released date, another for mode: 0755 arriving as 755, an amber row for the empty sidecars list, an info row for the email format on owners[].email, and a required list on owners items that omits pager. Each row names a path you can search for in the output.

    Where this generator stops

    Everything below needs knowledge a single sample cannot carry. Add these by hand after generating.

    If the source of truth is already JSON, the JSON to JSON Schema page does the same inference without the YAML scalar resolution described above, which removes the date and octal cases entirely. If the file will not parse at all, run it through the YAML validator first, which reports the line and column instead of a bare error message.

    The output is a first draft, not a contract. Generate it, read the amber rows, tighten what you know, and then validate a second real file against it before anything depends on the result.

    YAML to JSON Schema questions

    Why is every key marked required?

    A single sample shows every key as present, so the default treats all of them as required. That is the stricter reading, and a strict schema fails on the first document that omits a key rather than accepting bad input quietly. Switch the Required keys rule to Keys with a value if your sample marks optional fields with ~ or an empty value, or to None for a partial override file where most keys are optional.

    My date came out as format date-time even though I wrote a plain date.

    An unquoted date in YAML matches the timestamp tag and is converted to a Date object by the parser before the generator runs. The page writes format date when the parsed value sits at midnight UTC and date-time otherwise. If your date is in a timezone that shifts it off midnight, or the field holds text, quote the value in the YAML. Quoted strings keep their exact characters and format detection reads those.

    Why does 3.0 become an integer?

    JavaScript stores 3.0 and 3 as the same number, so by the time the generator sees the value the fraction is gone. The page scans the raw YAML for values written with a .0 tail and emits number for those keys, with an info row. If your sample is loaded from somewhere the raw text is not available, or the key is nested inside a list, add number by hand.

    Does the generator use every item in a list or only the first?

    Every item. Objects are merged property by property, and a key becomes required only when every item carries it. Integer and number merge to number. Items with nothing in common produce an anyOf under items and an amber row asking whether the mixed list is intentional.

    What does the draft selector change in the output?

    Only the $schema URL. The generator emits keywords whose meaning did not change between draft-07, 2019-09 and 2020-12, and it never writes definitions, $defs or prefixItems. Pick the draft your validator expects. Ajv 8 defaults to draft-07, OpenAPI 3.1 tooling expects 2020-12.

    Can the generator produce enum, minimum or pattern keywords?

    No. One sample shows one value, not the set of allowed values or a boundary. Those keywords need knowledge of the field that the file does not carry. Generate the skeleton here, then add enum, ranges and patterns by hand on the keys where they matter.

    What happens to anchors and merge keys?

    The parser expands them before returning data, so an aliased block appears in the schema as a fully repeated object with no $ref. If you want a shared definition, cut the repeated object into $defs after generating and point each use at it with $ref.