YAML vs JSON for LLMs: Real Token Counts, Real Gotchas

# yaml# json# ai# llm
YAML vs JSON for LLMs: Real Token Counts, Real GotchasAmaresh Pelleti

YAML vs JSON for LLMs, measured with a real tokenizer: YAML cuts ~30% of tokens vs JSON, and one YAML parsing quirk already breaks GitHub Actions configs.

Originally published on DevToolHub.

YAML vs JSON for LLMs isn't just a style preference — it changes how many tokens you pay for on every request. I ran both formats through a real tokenizer on identical data instead of repeating the "20-ish percent" figures floating around online, and the gap is bigger than you'd guess, with one real parsing bug that can silently corrupt an LLM's output if you're not careful.

YAML vs JSON for LLMs: The Real Numbers, Measured

Most "YAML saves tokens" claims cite a number without showing their work. So I built two test cases — a nested config object and a 10-row uniform table — and ran both formats through OpenAI's cl100k_base tokenizer (the GPT-4-era encoding, a standard reference point; exact counts shift slightly on Claude's or Gemini's own tokenizers, but the ranking between formats doesn't).

Nested config (not a list — a single object with child objects):

Format Tokens Characters
JSON 99 322
YAML 66 223

YAML cut 33.3% of the tokens off the same config, just by dropping braces, commas, and quoted keys.

Uniform table (10 rows, 4 fields each — the shape most AI agent data actually takes: user lists, logs, tool outputs):

Format Tokens Characters
JSON 323 963
YAML 221 590
TOON 97 235

Here the picture changes. YAML still beats JSON by 31.6%, but TOON — a format built specifically for this case — beats JSON by 70% and beats YAML by 56.1%.

The reason is structural, not cosmetic. YAML's - id: 1\n name: Alice syntax repeats every field name on every row. TOON declares the field list once (users[10]{id,name,role,active}:) and streams bare values after it. For a one-off config file that repetition barely matters. For a 500-row tool result going into an agent's context window on every turn, it adds up fast.

YAML vs JSON vs TOON: Which Should You Use?

  • Nested, non-uniform data (app config, a single object with child objects): YAML and TOON are nearly identical here — TOON's spec explicitly borrows YAML's indentation syntax for non-tabular data, so there's no real token advantage to switching formats for this case. Pick YAML; it's the one more tools already read.
  • Uniform arrays of objects (user lists, log lines, query results, tool outputs): TOON is the clear winner. This is specifically the case it was designed to compress.
  • Anything hitting an API, a database, or existing software: JSON. It's universal, strict, and every library expects it. Don't reach for YAML or TOON here — convert at the LLM boundary, not before it.

A practical pattern: keep your actual system in JSON, and only convert to YAML or TOON for the text you hand to the model — then have the model hand JSON back for anything that needs to go back into code.

The YAML Gotcha Nobody Warns You About

YAML's compactness comes from a looser grammar than JSON's, and that looseness has a well-documented failure mode: the Norway problem. In YAML 1.1 (still what many libraries implement, including older PyYAML), bare words like no, yes, on, and off parse as booleans, not strings. A country-code list like:

geoblock_regions:
  - dk
  - fi
  - is
  - no
  - se
Enter fullscreen mode Exit fullscreen mode

silently becomes ["dk", "fi", "is", false, "se"] — Norway (no) turns into False. Version-looking strings have the same problem: 10.23 parses as the float 10.23, not the string "10.23", which breaks version-comparison logic that expected text.

This isn't theoretical for this site's audience specifically. GitHub Actions workflow files use on: as a top-level key (on: push). Per a real bug report against the Go YAML library, a generic YAML 1.1 parser reads that key as the boolean true, not the string "on" — so any tooling that parses workflow YAML with a non-GitHub-aware library can silently drop or mishandle the trigger config. GitHub's own runner handles on: correctly because it's YAML-1.2-aware, but if you're writing your own workflow linter, generator, or an LLM-based tool that reads .github/workflows/*.yml, this is exactly the kind of bug that passes code review and fails in production. See our GitHub Actions YAML guide for the rest of the syntax.

The fix, per that same thread, is one of two things: quote the key ("on": push) or use a YAML 1.2-compliant parser. YAML 1.2.2 — the current spec, revised 2021-10-01 — dropped the bare on/off/yes/no boolean shortcuts specifically because of bugs like this one. If you're feeding YAML into an LLM and expect it to reliably reason about or regenerate that YAML, this gotcha matters doubly: the model has almost certainly also been trained on YAML 1.1-style parsing assumptions.

YAML vs JSON for LLMs: Why Token Count Matters More for Agents

A single chat message where YAML saves 60 tokens over JSON is immaterial. It compounds somewhere specific: AI agents that re-send accumulated context — conversation history, tool outputs, retrieved documents — on every turn. If an agent calls a tool 20 times in a session and each tool result is a JSON array of rows, you're paying the JSON tax 20 times over, not once. That's the same economics covered in our prompt compression guide: the lever that matters is the one you pull on every request, not the one you pull once.

Frequently Asked Questions

Q: Is YAML actually faster for an LLM to parse, or just fewer tokens?
A: This article only measured token count, which is the cost and context-window lever. Parsing speed is a separate question and depends on the runtime, not the model.

Q: Does TOON replace YAML?
A: No — they overlap only on uniform tabular data. For nested, irregular structures (most real config files), TOON's own spec uses YAML-style indentation, so there's no efficiency gain from switching.

Q: Why does "no" become false in YAML?
A: YAML 1.1 treats a set of bare words (y, n, yes, no, on, off, true, false) as boolean literals when unquoted. YAML 1.2.2 removed this behavior from the spec, but many parsers (older PyYAML, Go's go-yaml v2) still implement 1.1 rules for compatibility.

Q: Should I quote every YAML value to avoid this?
A: Quote any value that could be misread as a different type — country codes, version strings, and anything resembling yes/no/on/off. For a config key like GitHub Actions' on:, quote the key itself: "on": push.

Quick Summary:

  • Measured with a real tokenizer: YAML saves ~30-33% of tokens vs JSON on both nested config and tabular data
  • TOON pulls further ahead specifically on uniform tabular data — 70% fewer tokens than JSON, 56% fewer than YAML, in this test
  • For nested, non-uniform data, YAML and TOON are about the same size — pick YAML for compatibility
  • The Norway problem (no → false) is real and has broken actual tooling, including GitHub Actions' on: key in non-GitHub-aware parsers
  • JSON still wins for anything touching an API or database — convert formats only at the LLM boundary

If you're weighing YAML vs JSON for LLMs in your own pipeline, the honest answer is "it depends on the shape of the data," not "YAML always wins" — measure your own payloads with a tokenizer before committing, and watch for unquoted values that look like booleans.