Data Formats Guide: Markdown, YAML, JSON & TOML¶
A reference for the four text-based formats you'll run into constantly in documentation, config, and tooling — what each is for, how to write it, and where it tends to bite you.
Markdown¶
What It's For¶
Markdown is a lightweight markup language for writing formatted text using plain, readable syntax. It compiles to HTML and is the default format for READMEs, documentation sites, GitHub/GitLab issues and PRs, static site generators, and note-taking tools (Obsidian, Notion imports, etc.).
There's no single official spec owned by one body — the practical standard most tooling follows today is CommonMark, with GitHub Flavored Markdown (GFM) layered on top for things like tables and task lists.
Core Syntax¶
Headings¶
# H1
## H2
### H3
Emphasis¶
*italic* or _italic_
**bold** or __bold__
***bold italic***
~~strikethrough~~
Lists¶
- Unordered item
- Another item
- Nested item
1. Ordered item
2. Another item
Links and images¶
[link text](https://example.com)

Code¶
Inline `code` uses backticks.
```python
def hello():
print("fenced code block, with optional language for syntax highlighting")
```
Blockquotes¶
> A quoted line.
> Continues here.
Horizontal rule¶
---
GitHub Flavored Markdown extras¶
Tables:
| Name | Role |
|---------|-----------|
| Andrew | Platform |
| Alice | SRE |
Task lists:
- [x] Done
- [ ] Not done
Autolinks and strikethrough work as shown above; GFM also auto-links bare URLs and user/repo#123 references on GitHub itself.
Gotchas¶
- Flavors differ. CommonMark, GFM, and things like MDX or Pandoc's Markdown all extend the base spec differently — a table or footnote that renders on GitHub may not render on a plain CommonMark parser.
- Blank lines matter. A list immediately following a paragraph with no blank line between them can fail to render as a list in some parsers.
- Line breaks require two trailing spaces (or a
<br>) — a single newline in source is usually collapsed into a space in the output. - Escaping: use
\before literal*,_,`, etc. when you don't want them interpreted (\*not italic\*). - Raw HTML is allowed in most Markdown flavors and passes through untouched — useful, but a common source of "why didn't my markdown render" when tags are unclosed.
Tools¶
- CommonMark spec/reference — the closest thing to a canonical spec
- markdownlint — CLI/editor linter for consistent style
- Pandoc — converts Markdown to/from virtually every document format (docx, PDF, HTML, LaTeX)
YAML¶
What It's For¶
YAML ("YAML Ain't Markup Language") is a human-readable data serialization format used for config files, CI/CD pipelines (GitLab CI, GitHub Actions), Kubernetes manifests, Ansible playbooks, and Docker Compose. It's a superset of JSON — any valid JSON is technically valid YAML.
The official spec lives at yaml.org.
Core Syntax¶
Whitespace matters — indentation defines structure (spaces only, never tabs). Comments start with #.
Scalars:
name: Andrew
role: Platform Engineer
active: true
count: 42
ratio: 3.14
nothing: null # or ~, or leave blank
Maps nest via indentation:
server:
host: localhost
port: 8080
tls:
enabled: true
Sequences:
tools:
- terraform
- kubernetes
- elasticsearch
Or inline (flow style, JSON-like): tools: [terraform, kubernetes, elasticsearch]
Multi-line strings:
literal: |
Preserves
line breaks exactly.
folded: >
Folds
newlines into spaces.
|-/>- strip the trailing newline; |+/>+ keep trailing blank lines.
Quoting: unquoted, single-quoted ('...'), double-quoted ("...", supports escapes). Quote anything that could be misread as another type — "yes", "no", "123", "null".
Data Types¶
| Tag | Meaning |
|---|---|
!!seq |
Sequence (list) |
!!map |
Map (dictionary) |
!!str |
String |
!!int |
Integer |
!!float |
Floating-point decimal |
!!null |
Null |
!!binary |
Binary data (base64) |
!!omap |
Ordered map |
!!set |
Unordered set |
Force a type without quotes: version: !!str 13 (treats 13 as a string, not an int).
Custom Tags¶
%TAG ! tag:hostdata:your_tag:
---
record: !YOUR_TAG
id: 1001
region: us-east
A tag directive at the top of a document remaps the ! handle to a custom URI, letting tools disambiguate semantic types. Plain parsers ignore tags they don't understand.
Anchors and Aliases (DRY YAML)¶
defaults: &defaults
adapter: postgres
host: localhost
development:
<<: *defaults
database: dev_db
test:
<<: *defaults
database: test_db
&anchor_namedefines an anchor*anchor_namereferences it<<:merges an anchored map into the current map
Common in Docker Compose and .gitlab-ci.yml for shared config/job templates.
Multiple Documents in One File¶
---
doc: 1
---
doc: 2
--- separates documents; ... optionally marks the end of one. Kubernetes manifests commonly stack resources this way.
Gotchas¶
- Tabs are illegal for indentation.
on,off,yes,noare booleans in YAML 1.1 but not YAML 1.2 — trips people up in GitHub Actions (on:parsed as booleantrueby some tools). Quote if in doubt:"on":.- Norway problem: unquoted
NObecomes booleanfalsein YAML 1.1 parsers. - Leading zeros can trigger octal interpretation (
0755). - Trailing whitespace after
:before a value can cause subtle parse errors.
Tools¶
- yamllint.com — online linter
- yq (mikefarah/yq) —
jqfor YAML; CLI for reading/writing/transforming
bash yq '.spec.replicas' deployment.yaml yq -i '.metadata.labels.env = "prod"' deployment.yaml yq -o json eval file.yaml yamllint(Python package) — the standard for CI/pre-commit linting
JSON¶
What It's For¶
JSON (JavaScript Object Notation) is a strict, minimal data-interchange format. It's the backbone of REST APIs, is natively parseable in virtually every language, and underlies many other formats (Kubernetes accepts JSON as an alternative to YAML for manifests, for instance). Its strictness is the whole point — there's exactly one way to write valid JSON, which makes it ideal for machine-to-machine communication.
Core Syntax¶
{
"name": "Andrew",
"active": true,
"count": 42,
"ratio": 3.14,
"nothing": null,
"tools": ["terraform", "kubernetes", "elasticsearch"],
"server": {
"host": "localhost",
"port": 8080
}
}
Only two structures: objects ({}, unordered key-value pairs) and arrays ([], ordered lists). Only four primitive types: string (double quotes only), number (no distinction between int/float in the spec), boolean (true/false), and null.
Strict Rules (unlike YAML)¶
- Double quotes only — for keys and strings. Single quotes are invalid.
- No comments. Not in the spec, at all. (Some parsers — like
jsoncvariants used by VS Code — allow them as an extension, but plain JSON does not.) - No trailing commas —
["a", "b",]is invalid JSON, though many parsers are lenient here. - No trailing/leading whitespace requirements, but no tabs-vs-spaces issue either since indentation is cosmetic only (unlike YAML).
- Keys must be strings —
{1: "a"}is invalid; must be{"1": "a"}.
Gotchas¶
- No comments means no inline documentation — a common complaint for config files, which is part of why YAML and TOML exist as more human-friendly alternatives for hand-edited config.
- Large numbers can lose precision — JSON numbers are typically parsed as IEEE 754 doubles, so very large integers (like 64-bit IDs) can silently lose precision unless the parser handles big integers specially.
- Duplicate keys are technically undefined behavior — most parsers just take the last one, but the spec doesn't mandate this.
- No native date/time type — dates are just strings, usually ISO 8601 by convention, with no enforcement.
Tools¶
- jq — the standard CLI for querying/transforming JSON
bash cat file.json | jq '.server.port' jq -r '.tools[]' file.json - jsonlint.com — online validator
- Most languages have JSON in their standard library (Python's
json, Go'sencoding/json, etc.) — no third-party dependency usually needed
TOML¶
What It's For¶
TOML (Tom's Obvious, Minimal Language) is a config-file format designed to be easy for humans to read and write, and unambiguous to parse. It's the format behind Rust's Cargo.toml, Python's pyproject.toml, and various other tool configs. Think of it as sitting between JSON's strictness and YAML's flexibility — closer to an INI file with real types.
Spec: toml.io.
Core Syntax¶
Key-value pairs:
name = "Andrew"
active = true
count = 42
ratio = 3.14
Tables (like sections/objects):
[server]
host = "localhost"
port = 8080
[server.tls]
enabled = true
[server.tls] is equivalent to a nested server.tls object — dotted table names express nesting directly.
Arrays:
tools = ["terraform", "kubernetes", "elasticsearch"]
Array of tables (a list of objects — the thing people often reach for TOML specifically for):
[[servers]]
name = "web1"
ip = "10.0.0.1"
[[servers]]
name = "web2"
ip = "10.0.0.2"
Each [[servers]] block appends a new entry to the servers array.
Inline tables:
point = { x = 1, y = 2 }
Multi-line strings:
description = """
Line one.
Line two.
"""
Dates/times are a native type (unlike JSON/YAML, which treat them as strings):
created = 2026-08-25T14:30:00Z
Comments:
# This is a comment
Gotchas¶
- Table order matters for readability but not semantics — once you define
[server], you can't re-open it later in the file out of order in a conflicting way; keys must be defined before any subtable that isn't itself. - Dotted keys vs. tables can express the same nesting two ways (
a.b.c = 1vs.[a.b]\nc = 1), which can look inconsistent across a hand-edited file. - Arrays of tables (
[[x]]) trip up newcomers — it's the least JSON/YAML-like construct and the one place TOML syntax genuinely diverges in shape. - Less common outside the Rust/Python packaging ecosystem, so tooling support (linters, editor plugins) is thinner than for JSON/YAML.
Tools¶
- toml.io — spec and reference, including a "compare with JSON/YAML" cheat sheet
- Taplo — TOML linter/formatter/LSP (used by
rust-analyzertooling) - Most language ecosystems have a native TOML library (Rust's
tomlcrate, Python'stomllibin the standard library since 3.11)
Quick Comparison¶
| Feature | Markdown | YAML | JSON | TOML |
|---|---|---|---|---|
| Purpose | Formatted text | Config / data | Data interchange | Config |
| Comments | N/A (it's prose) | Yes (#) |
No | Yes (#) |
| Native dates | No | No | No | Yes |
| Whitespace-significant | Some | Yes (indentation) | No | No |
| Human-editable | Yes | Yes | Awkward | Yes |
| Typical use | Docs, READMEs | K8s, CI/CD, Ansible | APIs, data exchange | Cargo.toml, pyproject.toml |