38 puntos por GN⁺ 2025-12-05 | 10 comentarios | Compartir por WhatsApp
  • JSON, ya establecido como estándar de las API web, es fácil de leer y flexible, pero tiene límites en rendimiento y estabilidad
  • Protobuf (Protocol Buffers) garantiza con claridad la estructura de los datos mediante definiciones de tipos estrictas y generación automática de código
  • Al usar serialización binaria, reduce el tamaño de los datos en más de 3 veces y mejora la velocidad de transferencia frente a JSON
  • Como el servidor y el cliente comparten el mismo esquema .proto, no hay desajustes de tipos ni necesidad de validación manual
  • Aunque depurarlo es más difícil, Protobuf encaja mejor en las API modernas en términos de rendimiento, mantenibilidad y eficiencia de desarrollo

La universalidad de JSON y sus límites

  • JSON es un formato de texto fácil de leer para humanos, y se pueden revisar los datos incluso con un simple console.log()
  • Gracias a su integración perfecta con la web, ha sido ampliamente adoptado en JavaScript y en los frameworks de backend
  • Ofrece la flexibilidad de agregar o quitar campos y cambiar tipos con libertad, pero eso también puede provocar desajustes de estructura o errores
  • Tiene un ecosistema de herramientas muy amplio, por lo que se puede manejar fácilmente incluso solo con un editor de texto o curl
  • Pero, pese a todas esas ventajas, existen alternativas mejores en rendimiento y seguridad de tipos

Visión general de Protobuf

  • Un formato de serialización binaria desarrollado por Google en 2001 y publicado en 2008
  • Se usa ampliamente en sistemas internos y en la comunicación entre microservicios
  • A menudo existe la idea equivocada de que debe usarse junto con gRPC, pero Protobuf también puede utilizarse por sí solo en API HTTP
  • Al principio resultaba menos accesible por la falta de visibilidad del formato binario, pero destaca mucho en eficiencia y estabilidad

Sistema de tipos sólido y generación de código

  • Protobuf define con claridad la estructura de los datos mediante archivos .proto
    • Cada campo tiene un tipo estricto, un identificador numérico y un nombre fijo
  • Ejemplo:
    message User {
      int32 id = 1;
      string name = 2;
      string email = 3;
      bool isActive = 4;
    }
    
  • Con el comando protoc, admite generación automática de código para muchos lenguajes como Dart, TypeScript, Kotlin, Swift, C#, Go y Rust
  • Con el código generado se realiza la serialización (writeToBuffer) y deserialización (fromBuffer), sin necesidad de validación ni parsing manual
  • Como resultado, se logra al mismo tiempo ahorro de tiempo y mejor mantenibilidad

Eficiencia de la serialización binaria

  • Protobuf se serializa como datos binarios en lugar de texto, por lo que es muy compacto y rápido
  • Comparación de tamaño con los mismos datos (objeto User):
    • JSON: 86 bytes (68 bytes sin espacios)
    • Protobuf: 30 bytes
  • Motivos de esa eficiencia:
    • Usa codificación varint para los números
    • Usa etiquetas numéricas en lugar de claves de texto
    • Elimina espacios y sintaxis innecesaria
    • Optimización de campos opcionales
  • El resultado es ahorro de ancho de banda, mejor tiempo de respuesta, menor consumo de datos móviles y mejor experiencia de usuario

Ejemplo de API Protobuf basada en Dart

  • Se configura un servidor HTTP simple con el paquete shelf y se devuelve el objeto User en Protobuf
  • Puntos clave del código del servidor:
    • Se crea un objeto User() y luego se serializa con writeToBuffer()
    • En los headers de respuesta se especifica 'content-type': 'application/protobuf'
  • El cliente usa el paquete http y user.pb.dart para decodificar directamente los datos Protobuf
  • Como el servidor y el cliente comparten el mismo esquema .proto, no se producen discrepancias en la estructura de datos
  • El mismo enfoque puede aplicarse de igual forma en Go, Rust, Kotlin, Swift, C#, TypeScript y otros lenguajes

Ventajas que aún conserva JSON

  • Con Protobuf es difícil interpretar el significado sin un esquema
    • Como se muestran identificadores numéricos en lugar de nombres de campos, es difícil de leer para humanos
  • Comparación de ejemplo:
    • JSON: { "id": 42, "name": "Alice" }
    • Protobuf: 1: 42, 2: "Alice"
  • Por eso, Protobuf:
    • Requiere herramientas de decodificación especializadas
    • Hace indispensable la gestión de esquemas y de versiones
  • Aun así, las ventajas en rendimiento y eficiencia son mucho mayores

Conclusión

  • Protobuf es una tecnología de serialización madura y de alto rendimiento, y puede usarse perfectamente incluso en API públicas
  • Funciona de forma independiente en una API HTTP común, incluso sin gRPC
  • Es una herramienta que mejora el rendimiento, la solidez, la reducción de errores y la eficiencia de desarrollo
  • Vale completamente la pena considerar adoptar Protobuf en proyectos de próxima generación

10 comentarios

 
GN⁺ 2025-12-05
Opiniones en Hacker News
  • JSON a menudo termina enviando datos ambiguos o no garantizados. Surgen muchos problemas: campos faltantes, errores de tipo, typos en las claves, estructuras no documentadas, etc. Había un texto que afirmaba que Protobuf hace imposible esto al definir claramente la estructura de los mensajes con archivos .proto. Pero eso malinterpreta la filosofía de Protobuf. En proto3, los campos required ni siquiera están soportados. La documentación oficial (Protobuf Best Practices) incluso dice explícitamente que “los campos required eran dañinos y fueron eliminados”. Al final, los clientes de Protobuf también deben escribirse de forma defensiva, igual que una API JSON

    • Ese blog tiene varios malentendidos parecidos. Por ejemplo, en un texto que se opone al uso de SVG no toma en cuenta la ventaja del escalado libre de un formato vectorial
    • El núcleo del problema solo es la diferencia entre lenguajes o implementaciones de cliente/servidor. Yo uso el framework Gooey en el cliente aprovechando el concepto de marshalling de Go. Si superas las limitaciones de Go, se puede usar con mucha seguridad de tipos. Eso sí, es importante bloquear los campos privados con json:"-". Mi proyecto puede verse en Gooey
    • Este texto está confundiendo el formato de serialización con el concepto de contrato (contract)
    • En sistemas de red, el problema del desajuste de datos (skew) siempre existe, sin importar el método de codificación. Eso sí, Protobuf entrega un objeto de tipos estáticos después de decodificar. JSON también se puede validar, pero casi nadie lo hace. Al final, los objetos JSON se van deformando por todos lados y nadie puede estar seguro de su estructura interna
    • Probablemente el autor original solo quería decir que en Protobuf los campos omitidos se inicializan con valores por defecto. Eso no es lo mismo que el concepto de campo “required”
  • JSON comprimido es bastante usable y tiene un costo inicial de comunicación bajo. Claro, si faltan campos o cambian los tipos hay problemas, pero la mayoría de quienes intentan diseñar estructuras perfectamente tipadas y crear procesos para sincronizar versiones fracasan. Al final, gana la opción con menor costo humano. Por eso JSON no va a desaparecer hasta que aparezca un sustituto con un costo de comunicación humana todavía menor

    • Correcto. La mayoría de los arquitectos ni siquiera considera proto a menos que haya una necesidad clara como gRPC. JSON no será reemplazado hasta que aparezca una alternativa que puedas depurar al instante con console.log()
    • La depuración también es una fortaleza de JSON. Simplemente lo abres y lo lees. En cambio, Protobuf requiere tooling
    • Es verdad. Pero la gente prefiere no invertir 15 minutos extra en la etapa de diseño, y luego pasar 3 meses reconstruyendo qué salió mal
    • JSON no va a desaparecer por completo, como COBOL, pero en proyectos nuevos realmente no hay razón para usarlo
  • Protobuf no es perfecto. Si el servidor y el cliente se despliegan en momentos distintos y las versiones de la especificación no coinciden, la seguridad se rompe. Se puede mitigar evitando reutilizar IDs y copiando unknown fields, pero los sistemas distribuidos son complejos por naturaleza. Aun así, protobuf3 resolvió muchos problemas de protobuf2. Antes no se podía distinguir si un valor por defecto había sido establecido o si faltaba; ahora eso se resuelve usando el tipo message

    • Tanto con JSON como con Protobuf, solo es seguro si las pruebas de compatibilidad de versiones se fuerzan en el pipeline de CI
    • Cualquier sistema de tipos se rompe al cruzar la red
  • El texto habla de “súper eficiencia”, pero no menciona gzip. La mayoría de los datos de texto ya se transmiten comprimidos automáticamente. Por eso Protobuf debería compararse con JSON comprimido con gzip

    • Yo también probé varios formatos binarios, pero al final JSON comprimido con gzip resultó abrumadoramente eficiente
    • La desventaja de JSON es la velocidad de serialización/deserialización. Lo demás se puede ir resolviendo de forma gradual
    • También vale la pena considerar JSON/HTML con Brotli o zstd en streaming. Se puede aprovechar la ventana de compresión mientras la conexión sigue abierta
    • Referencia relacionada: comparación de rendimiento de Protobuf de Auth0
    • La combinación de JSON y mod_deflate produce una diferencia muy perceptible
  • Está bien defender protocolos mejores, pero es difícil decir que Protobuf reemplaza a JSON tanto en eficiencia como en usabilidad. Protobuf deja fuera áreas donde JSON funciona bien por su esquema estricto. Más bien, CBOR parece más adecuado como reemplazo de JSON. CBOR es tan flexible como JSON, pero con una codificación más compacta

    • Pero el esquema estricto de Protobuf también podría ser precisamente su ventaja. La mayoría de las APIs ni siquiera publica un esquema JSON. Yo validé con ajv o superstruct, pero con Protobuf no hace falta
    • Sería bueno que el navegador soportara una API de CBOR directamente. La implementación interna ya existe, así que no debería ser tan difícil
  • ASN.1 de 1984 ya hace lo que hace Protobuf, y con más flexibilidad. Si usas codificación DER, no está nada mal. Basta ver este ejemplo de ASN.1 DER. Protobuf es demasiado complejo para lo que logra

    • ASN.1 tiene demasiadas funciones. Si soportas todo, terminas con una librería excesivamente compleja; si soportas solo una parte, entonces ya no es ASN.1 estándar
    • Yo prefiero ASN.1 DER. Publiqué como FOSS un codificador/decodificador DER implementado directamente en C. También creé una extensión “ASN.1X” que incorpora por completo el modelo de datos de JSON
    • Pero en sistemas como SNMP, la excesiva flexibilidad de ASN.1 terminó siendo un problema. Cada fabricante lo extendía a su manera
    • Incluso dentro de Google, la serialización/deserialización de Protobuf consumía mucho CPU
    • ASN.1 está sobreingenierizado (overengineered) y por eso es difícil de soportar. Funciones como la herencia son innecesarias
  • Yo construí todo un sistema de producción con Protobuf, y administrarlo fue doloroso. Técnicamente se ve bien, pero en la práctica JSON es mucho más simple

    • No se puede subestimar la legibilidad y facilidad de depuración de JSON. La mayoría de los equipos elige JSON por eficiencia de corto plazo
    • Me da curiosidad qué problemas hubo. En mi experiencia, el riesgo de corrupción de datos con JSON es mayor que las incomodidades de Protobuf. Con Protobuf se detecta con errores de compilación, mientras que JSON explota en producción
  • Protobuf es excelente, pero es una lástima que no soporte zero-copy. Formatos como Cap’n Proto eliminan el cuello de botella de la serialización/deserialización

    • Pero en la práctica zero-copy incluso puede ser más lento. Copiar dentro de caché es casi gratis, pero manipular estructuras dinámicas directamente genera sobrecarga. En la mayoría de los casos, una sola copia (one-copy) es suficiente
    • Eso es más bien una afirmación del marketing de Cap’n Proto; en la práctica, la diferencia de rendimiento es mínima. Ambos formatos requieren conversión entre tipos nativos y binario. Según el payload, el rendimiento es parecido
    • Esto podría no ser un problema del formato, sino de la implementación de la librería
  • En un proyecto de NodeJS definí toda la API con .proto e hice un servidor que responde con proto o JSON según el Content-Type. Es mucho más estructurado que Swagger. Solo da pena que Google no haya ofrecido esto como librería oficial. gRPC es incómodo por su dependencia de HTTP/2. Por cierto, creo que Text proto es el mejor lenguaje de configuración estática

    • Para ese propósito, Twirp encaja bien. Maneja Protobuf o JSON sobre HTTP simple
    • ConnectRPC también ofrece un enfoque parecido. Aunque todavía no está claro qué tan amplio es su soporte
  • El formato binario con el que sueño sería basado en esquema, pero incluyendo el esquema dentro del mensaje. Así se podría leer directamente con un plugin de vim. Si manejas millones de objetos, agregar un esquema de 1 KB a un mensaje de 2 GB no es una gran carga

    • Dentro de Google ya existe un ecosistema Protobuf con esquema embebido. Vale la pena mirar Riegeli
    • Avro o Yardl también ofrecen un enfoque similar
    • Pero en servicios web muchas veces pasa lo contrario: el esquema pesa 200 KB y el mensaje 1 KB. En esos casos es ineficiente
    • Avro sigue siendo una buena alternativa
 
tested 2025-12-09
 
onixboox 2025-12-08

¿Qué tal esto?

 
cosine20 2025-12-08

MessagePack también está bien.

 
savvykang 2025-12-06

Me parece contradictorio afirmar que un formato es maduro cuando ni siquiera tiene un decodificador oficial para depuración.

 
vipeen 2025-12-06

"Pero depurar es difícil"

Descartado

 
jjw9512151 2025-12-05

Como todas las herramientas, no es una solución universal, pero creo que Protobuf también es una herramienta bastante buena.

En particular, hubo una vez en que tuve que enviar datos de gran volumen y alta frecuencia (20 veces por segundo) a varios clientes en distintos lenguajes dentro de un entorno embebido, y lo resolví de forma limpia con nanopb.

 
ifmkl 2025-12-05

Si te pones tan estricto, ¿no terminaría llegando en XML? jaja

 
click 2025-12-06

Si el esquema también se define con DTD y se usa caché del lado del parser, entonces también se podría lograr el efecto de enviar el esquema solo una vez.

 
bakyeono 2025-12-05
  • El formato binario que imagino está basado en esquemas, pero incluye el esquema dentro del mensaje. Así se puede leer directamente con un plugin de vim. Cuando se manejan millones de objetos, adjuntar un esquema de 1 KB a un mensaje de 2 GB no es una gran carga
  • Pero en los servicios web suele pasar lo contrario: el esquema puede pesar 200 KB y el mensaje 1 KB. En ese caso es ineficiente

=> De todos modos, ¿no hay que transmitir el esquema obligatoriamente al menos una vez? Incluso con JSON no es que no haya esquema; más bien está incluido implícitamente en los datos, así que no creo que realmente se esté evitando transmitirlo. De hecho, es más ineficiente porque se transmite el esquema de forma redundante en cada campo. La idea de un formato "basado en esquemas, pero que incluya el esquema dentro del mensaje" suena bastante bien.