JSON, YAML o TOML: cuál usar
Estos tres formatos difieren menos en lo que pueden expresar que en cómo fallan, y en los fallos es donde vas a gastar tu tiempo.
Cada afirmación de esta página está medida o tiene fuente. Cuando no es ninguna de las dos, lo dice.
Una tubería de despliegue lee una lista de países de un archivo YAML. Alguien añade Noruega con su código ISO, NO, y la tubería empieza a saltarse ese mercado sin dar error en ninguna parte. El valor que llegó a la aplicación era el booleano false.
Eso es la clase de cosa que debería decidir qué formato eliges, no una tabla de «¿admite comentarios? sí/no». Los tres formatos pueden contener un mapa de cadenas a valores. Lo que los separa es lo que te hacen cuando nadie mira.
JSON: aburrido, y ese es justo el asunto
JSON es un formato de cable. Tiene seis tipos, cuatro caracteres de espacio en blanco permitidos (espacio, tabulador, retorno de carro, salto de línea, según la RFC 8259), ni comentarios, ni comas finales, ni tipo fecha, y exactamente un tipo numérico que cualquier lector es libre de interpretar como float64. Está poco especificado en un par de sitios que importan, sobre todo las claves duplicadas, donde la RFC dice que las claves DEBERÍAN ser únicas y luego deja el comportamiento sin definir. JavaScript y Python se quedan ambos con la última.
Sus virtudes son enteramente no técnicas. Todo lenguaje trae un parser en su biblioteca estándar. Todo cliente HTTP sabe qué hacer con él. Prácticamente no hay desfase de versiones: un documento JSON escrito en 2008 se parsea hoy, idénticamente, en todas partes. Cuando serializas para un salto de red, una línea de log, una cola de mensajes o una caché, ninguna de las prestaciones orientadas a humanos de los otros dos formatos te aporta nada, y la universalidad te aporta mucho.
Los modos de fallo están muy transitados y casi todos van de números. Number.MAX_SAFE_INTEGER es 9007199254740991, y los IDs por encima se reescriben en silencio, lo cual es un artículo propio. Las fechas son cadenas por convención y nada hace cumplir la convención, lo cual es también un artículo propio. Ninguna de las dos cosas es razón para elegir otro formato para el transporte. Son razones para tener cuidado.
YAML: ergonomía real, factura real
La gente no elige YAML porque sea elegante. Lo elige porque un manifiesto de Kubernetes o una tubería de CI es algo que una persona edita a mano todos los días, y JSON es genuinamente desagradable de editar a mano: sin comentarios, comillas obligatorias y una coma que falta cuatrocientas líneas más arriba. YAML te da comentarios, cadenas multilínea que se leen, y nada de ruido de puntuación. Eso vale algo.
Esto es lo que estás pagando.
El problema de Noruega
YAML 1.1 resuelve no, yes, on, off, y y n sin comillas como booleanos. El esquema núcleo de YAML 1.2 no lo hace, y los deja como cadenas. El mismo documento, la misma clave, dos respuestas:
a: no
Con la resolución núcleo de YAML 1.2 ese valor es la cadena "no". Con las reglas de 1.1 es el booleano false. Cuál te toca depende de tu biblioteca, no de tu archivo: PyYAML y el Psych de Ruby resuelven con reglas de 1.1, mientras que js-yaml sigue 1.2. El yaml.v3 de Go queda en medio: resuelve no como cadena salvo que el campo destino sea un bool tipado, en cuyo caso todavía acepta la grafía de 1.1. Un servicio en Python y uno en Node leyendo el mismo archivo de configuración discrepan sobre el valor, y ninguno registra nada.
El arreglo es entrecomillar toda cadena que pudiera confundirse con otra cosa. Códigos de país, números de versión (1.10 es un float, "1.10" no), cualquier cosa que empiece por cero, y cualquier valor que aporte un usuario. Si generas YAML por programa, haz que el emisor entrecomille a la defensiva en vez de confiar en tu propia revisión.
El espacio en blanco es sintaxis y los tabuladores son ilegales
La indentación carga la estructura, así que una línea mal alineada es un documento distinto en vez de un error. Peor: la especificación de YAML prohíbe rotundamente los caracteres de tabulación para la indentación. Un editor configurado para insertar un tabulador produce un archivo que falla al parsear con un mensaje sobre un carácter que es invisible en tu terminal. Configura tu editor por tipo de archivo y deja de pensar en ello.
Los anclajes se expanden a la salida
Los anclajes y alias te dejan definir un bloque una vez y reutilizarlo:
defaults: &defaults
timeout: 30
retries: 3
staging:
<<: *defaults
host: stage.internal
Esta es la prestación que le vende YAML a quien mantiene cuarenta definiciones de servicio casi idénticas. También es una prestación que el modelo de datos no tiene. Convierte ese archivo a JSON y la clave de fusión se resuelve, el alias se expande, y defaults aparece entero dentro de staging. Vuelve a YAML y obtienes dos copias literales. Nada está mal, exactamente, pero aquello que mantenías ha desaparecido. Un archivo YAML que se apoya en anclajes no es realmente convertible, solo es legible una vez.
yaml.load ejecuta tu configuración
El YAML completo admite etiquetas específicas de lenguaje que construyen objetos arbitrarios. En Python eso significa que un documento que contenga !!python/object/apply:os.system puede ejecutar un comando durante el parseo. yaml.safe_load es la versión que solo construye tipos estándar, y es la que quieres para cualquier cosa que no hayas escrito tú. PyYAML acabó haciendo difícil equivocarse al exigir un argumento de loader explícito, pero mucho código es anterior a eso, y muchos otros lenguajes todavía tienen un valor por defecto inseguro a una llamada de distancia.
import yaml
with open("config.yaml") as f:
cfg = yaml.safe_load(f) # no yaml.load
El detalle del superconjunto
YAML 1.2 se diseñó como superconjunto de JSON, y la especificación afirma que todo documento JSON válido es también un documento YAML 1.2 válido, así que un parser 1.2 lee tu JSON. YAML 1.1 no del todo: quiere un espacio tras los dos puntos, así que un {"a":1} compacto es un error de parseo ahí, y las reglas de resolución de 1.1 seguirán convirtiendo algunas de tus cadenas en booleanos. Si te apoyas en «solo dale el JSON al parser de YAML», comprueba antes qué versión implementa tu biblioteca. En cualquier caso, puedes ir en el otro sentido limpiamente con el conversor de YAML a JSON.
TOML: sin ambigüedad hasta que anida
TOML existe porque los archivos INI eran agradables e imprecisos. Arregla la imprecisión: enteros y flotantes son tipos distintos, los booleanos son solo true y false, y hay cuatro tipos reales de fecha y hora (fecha-hora con desplazamiento, fecha-hora local, fecha local, hora local) integrados en la gramática en vez de colados a través de cadenas. Los comentarios son de primera clase. Definir la misma clave dos veces es un error duro en lugar de comportamiento indefinido, que es una cosa pequeña que caza una clase real de error de fusión.
Para una configuración plana o poco profunda es el mejor de los tres. Cargo.toml y pyproject.toml son los casos obvios: unas pocas secciones, valores de cadena y de lista, algún anidamiento ocasional de un nivel. Nada es ambiguo y nada necesita comillas por seguridad.
Se pone feo rápido cuando los datos son un árbol. El anidamiento profundo significa o cabeceras largas con puntos o claves largas con puntos:
[servers.production.database.replica]
host = "10.0.0.4"
port = 5432
Y un array de objetos necesita la forma de array de tablas con dobles corchetes, repetida por elemento:
[[targets]]
name = "web"
port = 8080
[[targets]]
name = "worker"
port = 8081
Eso se lee bien con dos entradas. Con treinta entradas de tres campos cada una, y tablas en línea que deben caber en una sola línea, estás peleando con el formato. Si tu configuración es genuinamente jerárquica, TOML es la forma equivocada y lo notarás cada vez que añadas un nivel.
Lo que ninguno te da
Un tipo decimal. Los tres te dan un float, que es una aproximación binaria. El dinero sigue perteneciendo a las unidades menores como entero o a una cadena.
Datos binarios. JSON y TOML no tienen representación alguna, así que es base64 en una cadena. YAML tiene una etiqueta !!binary, que funciona y no sobrevive a la conversión a ninguno de los otros dos.
Un esquema que venga con el formato. JSON Schema es la opción madura, y como YAML 1.2 se corresponde con el mismo modelo de datos, también puedes validar YAML con él. Así es como funciona en realidad casi toda la validación de YAML. TOML no tiene equivalente con adopción comparable.
Comentarios que sobrevivan a una conversión. Esta es la puerta de un solo sentido. Los comentarios viven en la sintaxis, no en el modelo de datos, así que un archivo YAML o TOML convertido a JSON pierde todos sus comentarios de forma permanente, y no hay herramienta ingeniosa que los recupere. Si los comentarios de un archivo soportan peso, la fuente de verdad es ese archivo y el JSON es solo un artefacto que generas. La falta de comentarios en JSON es deliberada, y es la razón de que esta asimetría exista.
Elegir, en forma de preguntas
¿El único lector es una máquina? JSON. No hagas que una API hable YAML.
¿Lo editará una persona cada semana y es jerárquico? YAML, con disciplina de comillas y safe_load.
¿Lo editará una persona y son sobre todo secciones planas de escalares? TOML. No pierdes nada y ganas tipos sin ambigüedad.
¿Necesitas que los comentarios sobrevivan? Elijas lo que elijas, ese archivo es la fuente de verdad. Genera hacia abajo, nunca edites la copia generada.
¿Los valores los producen personas no desarrolladoras o una interfaz? JSON, generado por un programa, validado contra un esquema. Todas las trampas de YAML de arriba las dispara una cadena que alguien tecleó.
¿Estás convirtiendo entre ellos ahora mismo? Hazlo en el conversor de JSON a YAML y lee la salida en vez de fiarte de ella, sobre todo los booleanos, y pasa el resultado por el validador antes de que llegue a nada que despliegue.