Markdown, JSON, XML, YAML y TOML son formatos de texto plano con estructura definida. Cada uno ocupa un papel distinto: documentación, intercambio de datos con APIs, configuración de herramientas o configuración local con variables de entorno. La pregunta no es «cuál es mejor», sino qué se guarda en cada uno:
Markdown es un lenguaje de marcas ligero para escribir texto con formato desde un editor cualquiera; se convierte a HTML para presentarlo. Se usa en README.md, documentación técnica, PR de GitHub y ficheros como AGENTS.md:
# Título 1
## Título 2
Párrafo con **negrita**, *cursiva* y `código en línea`.
- Elemento de lista
1. Primer paso
[Enlace](https://example.com)

> Cita destacada
| Comando | Acción |
|---------|--------|
| `git status` | Estado de los archivos |
```java
public class Main { }
```
# para el título principal, ## debajo, ### más abajo.
JSON (*JavaScript Object Notation*) es el formato estándar para intercambiar información estructurada entre aplicaciones, sobre todo en las APIs REST: el cliente envía JSON y recibe JSON. También sirve como fichero de configuración del ecosistema JavaScript (package.json, tsconfig.json):
{
"id": 42,
"title": "Código limpio",
"available": true,
"authors": ["Robert C. Martin"],
"price": null,
"publisher": { "name": "Anaya" }
}
| Elemento | Sintaxis |
|---|---|
| Objeto | { “clave”: valor } |
| Array | [valor, valor] |
| Clave | siempre cadena entre comillas dobles |
| Valores | cadena, número, true/false, null, objeto, array |
Los valores se anidan: un array de objetos, un objeto dentro de otro. Desde TypeScript, JSON.parse(texto) convierte texto JSON en objeto y JSON.stringify(objeto) al revés; response.json() ya lo aplica al cuerpo de una respuesta (tema 07). JSON no admite comentarios ni fechas como tipo nativo (se representan como texto ISO: “2026-10-12”).
XML (*eXtensible Markup Language*) describe documentos y ficheros de configuración con estructura jerárquica: pom.xml de Maven, web.xml de Tomcat o la configuración de frameworks:
<?xml version="1.0" encoding="UTF-8"?>
<library>
<book id="42" available="true">
<title>Código limpio</title>
<tag>craft</tag>
</book>
</library>
| Elemento | Sintaxis |
|---|---|
| Prólogo | <?xml version=“1.0” encoding=“UTF-8”?> |
| Elemento | etiqueta de apertura y cierre: <book>…</book> |
| Atributo | dato en la etiqueta de apertura: id=“42” |
| Elemento vacío | <tag/> |
| Comentario | <!– texto –> |
Los espacios de nombres (xmlns) agrupan vocabularios para que etiquetas del mismo nombre no colisionen, como se ve en web.xml.
Un documento bien formado cumple las reglas sintácticas: un único elemento raíz, etiquetas cerradas y correctamente anidadas. La validación comprueba además que el documento respeta una estructura acordada; se define con XSD (XML Schema), que aporta tipos de datos y obligatoriedad:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="book">
<xs:complexType>
<xs:attribute name="id" type="xs:int" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
Con ese esquema, un <book> con id no numérico no es válido.
YAML es un formato de configuración sin etiquetas: la indentación define la estructura con pares clave: valor. Es el formato de Docker Compose, GitHub Actions y Kubernetes:
# compose.yaml
services:
db:
image: mariadb:11
ports:
- "3306:3306"
environment:
MARIADB_ROOT_PASSWORD: ${DB_PASSWORD}
web:
build: .
depends_on:
- db
| Estructura | Sintaxis |
|---|---|
| Mapa | pares clave: valor anidados por indentación |
| Lista | elementos con - al mismo nivel |
| Texto | comillas solo cuando el valor es ambiguo |
| Comentario | # al inicio de línea |
La sustitución ${DB_PASSWORD} toma el valor de la variable de entorno definida en el .env (apartado 6).
version: “3.8” (si no, se lee como número 3.8) o enabled: “yes” (si no, se convierte en booleano).
TOML organiza la configuración en secciones con clave = valor; los tipos básicos (texto, número, booleano, array) son nativos. Es el formato de pyproject.toml de Python y Cargo.toml de Rust:
[project] name = "biblioteca" version = "1.0.0" tags = ["novela", "ensayo"] [build] target = "dist"
[sección] agrupa las claves hasta la siguiente sección.elementos repite una sección para representar listas de objetos.
La configuración que cambia por máquina (puertos, rutas de base de datos, claves de API) no se escribe en el código ni se sube al repositorio: se lee de las variables de entorno. Durante el desarrollo se declaran en un .env:
# .env PORT=3000 DATABASE_URL=mariadb://app:contrasenya@localhost:3306/library
CLAVE=VALOR; los comentarios con #..env al arrancar la aplicación: dotenv en Node; en Docker Compose el propio fichero alimenta las sustituciones ${VAR}, y en Spring las variables de entorno alimentan el application.properties.process.env.PORT en Node, System.getenv(“PORT”) en Java, os.environ en Python.
Al repositorio solo se sube un .env.example con los nombres de las variables y sin valores, para que cada máquina cree su .env propio.
.env nunca se sube al repositorio: contiene claves y contraseñas. Se añade a .gitignore y se publica el .env.example equivalente.
| Formato | Guarda | Aparece en |
|---|---|---|
| Markdown | Documentación con formato | README.md, AGENTS.md, PR |
| JSON | Datos para intercambiar; configuración JS | APIs REST, package.json |
| XML | Documentos y configuración Java | pom.xml, web.xml |
| YAML | Configuración de despliegue y CI | compose.yaml, GitHub Actions |
| TOML | Configuración de herramientas y proyectos | pyproject.toml, Cargo.toml |
| .env | Configuración por máquina y secretos | .env, .env.example |
.env se parecen a primera vista, pero la sintaxis es distinta. Para los datos que viajan por una API, JSON; para lo que depende de cada máquina, variables de entorno.