3 puntos por GN⁺ 2023-11-30 | 1 comentarios | Compartir por WhatsApp
  • jaq es un clon de la herramienta de procesamiento de datos JSON jq que busca una implementación más precisa y predecible, manteniendo compatibilidad con jq en la mayoría de los casos
  • El programa de línea de comandos jaq puede usarse como un reemplazo directo de jq, y la biblioteca de Rust jaq-core permite compilar y ejecutar programas jq dentro de programas Rust
  • Ofrece soporte para YAML, CBOR, TOML y XML, formatos que jq no incluye; además, jaq-core puede usarse de forma segura en entornos multihilo y soporta tipos de datos arbitrarios más allá de JSON
  • En la evaluación de rendimiento, jaq-3.0 fue el más rápido en 20 de 31 benchmarks, jq-1.8.1 en 5 y gojq-0.12.18 en 6
  • En seguridad, busca garantizar ausencia de pánicos, seguridad de memoria y límites de I/O para los datos de entrada y los filtros jq, pero no aborda el agotamiento de recursos como tiempo, memoria o pila

Lo que ofrece jaq

  • jaq es un clon de la herramienta de procesamiento de datos JSON jq, y se pronuncia /ʒaːk/, igual que Jacques
  • Tiene soporte para formatos de datos que jq no incluye
    • YAML

    • CBOR

    • TOML

      • XML
      • Tiene un manual independiente, y puede probarse en el playground
      • jaq se ofrece en dos formas
      • Programa de línea de comandos jaq: puede usarse como reemplazo directo de jq
      • Biblioteca jaq-core: permite compilar y ejecutar programas jq dentro de programas Rust

Objetivos de diseño

  • Precisión

    • jaq busca una implementación de jq más precisa y predecible, manteniendo compatibilidad con jq en la mayoría de los casos
  • Rendimiento

    • jaq se creó originalmente porque el largo tiempo de arranque de jq 1.6 resultaba incómodo, y en ese entorno el arranque era de unos 50 ms
    • Ese tiempo de arranque se nota especialmente al procesar una gran cantidad de archivos pequeños
    • En jq 1.7 el tiempo de arranque mejoró mucho, pero jaq sigue siendo más rápido que jq en varios benchmarks
  • Simplicidad

    • jaq apunta a una implementación simple y pequeña para reducir la posibilidad de errores y facilitar las contribuciones

Instalación y compilación

  • Los binarios para Linux, Mac y Windows pueden descargarse desde la releases page
  • En macOS o Linux puede instalarse con homebrew
    • brew install jaq
    • brew install --HEAD jaq
  • Para compilar desde el código fuente se necesita el toolchain de Rust
  • Si se clonó el repositorio, puede compilarse o instalarse con cargo build --release o cargo install --locked --path jaq
  • jaq debería funcionar en cualquier sistema compatible con Rust; si no es así, se pide reportar un issue

Evaluación de rendimiento

  • La evaluación de rendimiento consiste en varios benchmarks que comparan jaq, jq y gojq
  • El benchmark empty mide el tiempo de arranque ejecutando el filtro empty n veces con entrada null
  • El benchmark bf-fib ejecuta un script Brainfuck que genera números de Fibonacci usando un intérprete Brainfuck escrito en jq
  • Los datos de benchmark se generaron en un sistema Linux con AMD Ryzen 5 5500U
    • El comando usado fue bench.sh target/release/jaq jq-1.8.1 gojq-0.12.17 | tee bench.json
    • La tabla muestra resultados de jaq-3.0, jq-1.8.1 y gojq-0.12.18 en milisegundos
    • N/A significa error o más de 10 segundos
  • Resumen de resultados
    • jaq-3.0 fue el más rápido en 20 benchmarks
    • jq-1.8.1 fue el más rápido en 5 benchmarks
    • gojq-0.12.18 fue el más rápido en 6 benchmarks
  • gojq es mucho más rápido en tree-flatten porque implementa el filtro flatten de forma nativa y no como definición

Modelo de seguridad y límites

  • jaq busca garantizar lo siguiente
    • No se producen pánicos, salvo en casos de agotamiento de recursos
    • Seguridad de memoria sin corrupción de memoria
    • Excepto por la lectura de archivos antes de ejecutar filtros jq, los datos de entrada y los filtros jq no pueden iniciar operaciones de I/O
  • Si alguna de estas garantías se rompe, se considera un bug y debe reportarse
  • jaq no aborda ningún tipo de agotamiento de recursos
    • El tiempo de ejecución puede crecer sin límite
    • Puede usar memoria sin límite
    • Puede usar espacio de pila sin límite
  • Como ejemplo, puede producirse un desbordamiento de pila al leer datos de entrada o ejecutar un filtro jq
    • jaq -nr 'repeat("[")' | jaq
    • jaq -n 'def f: 1+f; f'

Auditorías y pruebas

  • jaq core fue auditado por Radically Open Security como parte de dos grants de NLnet
  • La primera y la segunda auditorías de seguridad encontraron problemas de severidad media o baja
  • Todos los problemas detectados en las auditorías fueron resueltos, y se agregaron varios objetivos de fuzzing para jaq en jaq-core/fuzz
  • El parser JSON de jaq, hifijson, ya contaba también con objetivos de fuzzing
  • jaq tiene una suite de pruebas compuesta por más de 500 tests

Casos de uso de usuarios

  • Un usuario comentó que jaq fue de gran ayuda frente a implementar soporte de jq directamente, y que gracias a la extensibilidad mediante el trait ValT pudo agregar fácilmente soporte de jq a sus propios tipos
  • Otro usuario dijo que un programa Rust usando jaq podía ejecutar tres veces todas las consultas sobre un archivo completo mientras el crate jq de PyPI para Python y un bucle en Python ejecutaban una sola consulta sobre ese mismo archivo completo
  • En el caso del intérprete wsjq, se destacó que jaq era considerablemente más rápido que otras implementaciones de jq y que su énfasis en la precisión resultaba impresionante
    • En ese benchmark de wsjq, jaq fue entre 5 y 10 veces más rápido que jq, y entre 15 y 196 veces más rápido que gojq
  • Un usuario que procesaba datos de certificate transparency log con certstream-server tuvo problemas con el pipeline de jq, y al cambiar a jaq pudo seguir el ritmo incluso en una VM de bajos recursos gracias a su arranque más rápido

Financiamiento

1 comentarios

 
GN⁺ 2023-11-30
Opiniones de Hacker News
  • Considerando que el desarrollo de jq estuvo detenido durante 5 años y recién hace poco volvió a activarse, no es raro que se hayan acumulado reportes durante ese tiempo, fueran bugs conocidos o nuevos
    Ahora parece que va a recuperar ritmo y empezar a ordenar poco a poco la lista de pendientes acumulada desde hace tiempo

  • Me gusta la forma en que el README presenta juntos proyectos similares o inspirados en este, pero que no son reemplazos
    Gracias al README de este proyecto conocí https://github.com/yamafaktory/jql, una herramienta que llevaba mucho tiempo buscando, así que se agradece
    No quiero menospreciar a JAQ, pero la sintaxis estilo JQ es demasiado difícil de entender, así que jql me queda mejor

    • Desde este punto de vista, gron también es bueno
      Aplana JSON en líneas con formato clave-valor, lo que lo hace encajar bien con operaciones simples de streaming como grep: https://github.com/tomnomnom/gron
    • Es un buen hallazgo, pienso probarlo
      Aunque esperaba una experiencia realmente parecida a SQL. No entiendo por qué no simplemente copian SQL para poder consultar algo como "SELECT * FROM $json WHERE x>1"
      Parece que todos quieren crear su propio lenguaje de consultas críptico basado en símbolos, como si estuvieran haciendo code golf. Ojalá nos alejemos de esa sintaxis antigua de Unix, extremadamente breve pero nada evidente, y nos acerquemos más al enfoque de PowerShell
    • También vale la pena revisar https://github.com/tidwall/jj
    • Entiendo en parte esa incomodidad, pero al menos jql no me parece la solución
      |={"b""d"=2, "c"} parece significar algo como select(."b"."d" == 2 or ."c" != null) en jq, y aunque la versión de jq sea más larga, se siente más clara
      En la práctica probablemente haría falta .[] | select(...), pero jql podría tener una premisa similar; no estoy seguro de si el ejemplo está completo, así que eso no cambia mucho la conclusión
    • La homoiconicidad de jql parece bastante Lisp
      También parece posible aplicarlo sobre sí mismo o usar “macros”
  • Me gusta la idea de jq, pero como no lo uso seguido, cada vez que quiero hacer algo tengo que buscar la sintaxis en el manual
    Lamentablemente, el 99% de lo que hago con jq es | jq .

    • Me pasaba lo mismo
      Por otro lado, empecé a crear un lenguaje de configuración y resultó que también funcionaba bastante bien para consultas JSON: https://docs.ruuda.nl/rcl/rcl_query/
      Aquí hay un ejemplo que no pude resolver con jq, pero sí con RCL: https://fosstodon.org/@ruuda/111120049523534027
    • Por el mismo problema, no había podido aprovechar bien la potencia de jq, y en estos casos tener Copilot ayuda muchísimo
      Si le das la tarea necesaria junto con una muestra reducida del JSON original, genera el script jq correcto
      Para requisitos complejos, es más fácil y confiable ir iterando poco a poco con Copilot y guiarlo hacia la solución, en vez de intentar explicarlo todo con precisión de una sola vez. Durante la iteración, a veces también se te ocurren ideas mejores que al principio
      ChatGPT u otras herramientas probablemente funcionen de forma similar
    • Últimamente obtuve rápidamente la sintaxis de jq que necesitaba con ChatGPT: https://chat.openai.com/share/40b68d73-d2dd-412d-867f-9f375e...
  • https://github.com/01mf02/jaq/blob/main/Cargo.lock
    Tiene bastantes dependencias

    • Comparado con gojq, son muchísimas: https://github.com/itchyny/gojq/blob/main/go.mod
    • Me pregunto cómo suelen evolucionar estos casos en el ecosistema de Rust
      Si hay muchas dependencias, parece que con el tiempo aumenta el riesgo de que terminen siendo fundamentalmente incompatibles entre sí, y el mantenimiento podría volverse un gran trabajo
      Por ejemplo, me pregunto si seguirá compilando dentro de 2 años
  • jq es una herramienta muy potente, pero últimamente también se usa mucho DuckDB
    Si los datos tienen más o menos forma tabular, SQL es un lenguaje mucho más natural

    • Hace tiempo probé Retool y tenía “Query JSON with SQL”, que resultó bastante cómodo: https://docs.retool.com/queries/guides/sql/query-json
      Se parece un poco a LINQ de C#, pero SQL está más estandarizado, así que me gusta más
      Sería genial poder consultar colecciones primitivas dentro del lenguaje con SQL y, más aún, poder almacenar colecciones de forma transparente en Sqlite
      Cada vez que veo código que trae datos de una base de datos y luego hace procesamiento simple con loops o APIs de streams, me queda una sensación de oportunidad perdida. Para este tipo de uso, SQL es de mucho más alto nivel y más conciso que Java/Kotlin/Python/JavaScript
    • Siento algo parecido
      Guardo toda la salida JSON original en una tabla sqlite, creo columnas virtuales ahí y luego paso el resultado de select por un loop de shell
      Se eliminan los loops anidados, y como puedo verificar en la DB el registro exacto y volver a ejecutar, la capacidad de depuración mejora muchísimo
      Me di cuenta de que lo que estoy construyendo es un DAG, y siempre reinicio desde el último registro procesado correctamente. Me pregunto si existe alguna herramienta parecida a Make para expresar esto
      Make no tiene objetivos SQL, y los procesadores DAG completos como Airflow son demasiado pesados para pegar fragmentos de shell
    • Exacto. Para datos relacionales con un esquema estricto, SQL es mucho mejor
      Aun así, sigue siendo difícil encontrar una forma concisa de expresar consultas recursivas en SQL
    • Para este uso, personalmente me gusta más textql. El modelo mental es más simple
      https://github.com/dinedal/textql
  • Desde el punto de vista de la exactitud, me pregunto si puede mostrar números uint64 sin truncarlos
    Es lo que más me molesta actualmente de jq

    • Por desgracia, si se ven los números JSON como punto flotante de 64 bits, según el estándar habría que tratarlos así, y la precisión de enteros queda en 53 bits
      Aunque hago la corrección de que la especificación más reciente, RFC 8259, solo define el formato textual de los números y no su semántica
      En la práctica, la mayoría de las implementaciones tratan JSON como un subconjunto de JavaScript, lo que lleva a asumir que los números son de punto flotante de 64 bits
    • Tengo entendido que esto mejoró en jq 1.7: https://github.com/jqlang/jq/releases/tag/jq-1.7
      Dice que usa literales numéricos decimales para preservar la precisión, y que las operaciones de comparación respetan la precisión, aunque las aritméticas pueden truncarse
    • jq 1.7 conserva enteros grandes, pero se truncan si se realiza alguna operación sobre ellos
      Actualmente los trunca a decimal64, lo cual es un poco confuso; en la próxima versión se corregirá para truncarlos a binary64(double), en línea con la recomendación de la especificación JSON: https://github.com/jqlang/jq/pull/2949
  • Desde que me pasé a jless, no he mirado atrás
    Su interfaz de usuario está muy por delante de las demás

    • No son del mismo tipo
      jq no es un simple visor, sino un procesador de lenguaje de consultas JSON
  • Es simpático que exista en algún lugar de Rust una biblioteca de arte de líneas para terminal, pero al ejecutar jaq empezó a volcar megabytes de códigos de escape en iTerm hasta que finalmente iTerm intentó imprimirlos en una impresora
    Se pasó de listo
    En un contexto como echo *json | rush -- jaq -rf ./this-program.jq {} | datamash ..., no creo que sea apropiado intentar meter efectos artísticos en la TTY
    La causa del error, en cualquier caso, era que jaq no tenía strftime

  • Mi primera impresión es que tiene mensajes de error bonitos, pero no tiene halt_error/0
    Después de comentar halt_error, fue más lento que jq y gojq
    Con la misma entrada, jq tardó unos 0.023 segundos, gojq unos 0.070 segundos y jaq unos 0.103 segundos
    El aoc22-13.jq usado está en https://pastebin.com/raw/YiUjEu2n y input.txt está en https://pastebin.com/raw/X0FSyTNf

  • Empecé a usar yq en lugar de jq, y me pregunto si hay alguna diferencia importante

    • Depende de cuál yq sea
      Personalmente prefiero https://github.com/mikefarah/yq sobre https://github.com/kislyuk/yq
    • jq se siente como una herramienta mucho más sólida que yq
      Entiendo que procesar YAML es mucho más difícil que JSON, pero aunque yq cambió su sintaxis de la versión 3 a la 4 para parecerse más a jq, por alguna razón no termina de ser igual
      Además, yq no tiene if-then-else, lo que parece un mal diseño o una omisión: https://github.com/mikefarah/yq/issues/95
      Cuando hay que procesar YAML, yq funciona bien y también maneja bastante bien los comentarios, pero para procesar JSON puro, jq es una mejor herramienta