- Herramienta CLI en Rust para buscar documentos JSON por rutas, con mayor velocidad de búsqueda que
jq, jmespath, jsonpath-rust y jql
- Expresa las consultas como un lenguaje regular y las compila a un DFA, recorriendo el árbol JSON en una sola pasada para procesarlo en tiempo O(n)
- Usa
serde_json_borrow, que admite parsing zero-copy, para minimizar las asignaciones de memoria, y fue diseñada tomando como referencia la filosofía de rendimiento de ripgrep
- Según los benchmarks, ofrece el mejor rendimiento end-to-end incluso con JSON de gran tamaño, y proporciona un lenguaje de consulta simple centrado en la búsqueda
- Publicado bajo licencia MIT, y su motor de consultas basado en DFA puede reutilizarse como biblioteca Rust
Resumen de jsongrep
- jsongrep es una herramienta CLI basada en Rust para buscar valores en documentos JSON por ruta, con el objetivo de ser más rápida que
jq, jmespath, jsonpath-rust y jql
- Considera el documento JSON como un árbol y expresa la ruta (path) como un lenguaje regular (regular language), que luego compila a un DFA (Deterministic Finite Automaton) para recorrerlo en una sola pasada
- El lenguaje de consulta es simple y está diseñado en torno a la búsqueda, por lo que no incluye funciones de transformación ni cálculo
- Minimiza las asignaciones de memoria mediante parsing zero-copy con
serde_json_borrow
- Fue desarrollado tomando como referencia la filosofía de diseño y el enfoque de rendimiento de
ripgrep
Ejemplos de uso de jsongrep
- El comando
jg recibe una consulta y una entrada JSON, y muestra todos los valores cuya ruta coincide con la consulta
- Acceso a campos anidados con notación de punto (dot path)
jg 'roommates[0].name' → "Alice"
- Uso de comodines (
*, [*]) para coincidir con todas las claves o índices
- Alternation (
|) para seleccionar una entre varias rutas
- Búsqueda recursiva (
(* | [*])*) para encontrar campos a cualquier profundidad
- Optional (
?) para admitir coincidencias de 0 o 1 vez
- La opción
-F permite buscar rápidamente un nombre de campo específico
- Al usar pipes (
| less, | sort), omite automáticamente la salida de rutas; puede forzarse con --with-path
Conceptos clave de jsongrep
- JSON es una estructura en árbol, y las claves de objetos y los índices de arreglos actúan como aristas (edge)
- La consulta define un conjunto de rutas desde la raíz hasta un nodo específico
- El lenguaje de consulta está diseñado como lenguaje regular, por lo que puede transformarse en un DFA
- El DFA lee la entrada una sola vez y realiza la búsqueda en tiempo O(n) sin backtracking
- Herramientas existentes como
jq y jmespath interpretan la consulta y recorren de forma recursiva, mientras que jsongrep usa un DFA precompilado para hacer el recorrido en una sola pasada
Estructura del motor de consultas basado en DFA
- El pipeline consta de 5 etapas
- Parsear el JSON como árbol con
serde_json_borrow
- Parsear la consulta a un AST
- Generar un NFA con el algoritmo de Glushkov
- Convertirlo a un DFA con Subset Construction
- Recorrer el árbol JSON con un único DFS siguiendo las transiciones del DFA
-
Parsing de consultas
- Convierte la consulta a un AST
Query con una gramática PEG (usando la biblioteca pest)
- Elementos sintácticos principales:
Field, Index, Range, FieldWildcard, ArrayWildcard, Optional, KleeneStar, Disjunction, Sequence
- Ejemplo:
roommates[*].name → Sequence(Field("roommates"), ArrayWildcard, Field("name"))
-
Modelo de árbol JSON
- Las claves de objetos y los índices de arreglos son aristas, y los valores son nodos
- Ejemplo:
roommates[*].name recorre la ruta roommates → [0] → name
-
Construcción del NFA (algoritmo de Glushkov)
- Genera un NFA sin transiciones ε
- Pasos
- Asignar números de posición a los símbolos de la consulta
- Calcular los conjuntos First/Last/Follows
- Construir transiciones entre posiciones
- El NFA para la consulta de ejemplo
roommates[*].name tiene una estructura lineal simple de 4 estados
-
Conversión a DFA (Subset Construction)
- Genera un DFA determinista a partir de conjuntos de estados del NFA
- Cada estado corresponde a un conjunto de estados del NFA
- Agrega el símbolo
Other para saltarse de forma eficiente las claves innecesarias
- Las consultas simples se convierten en un DFA con la misma estructura que el NFA
-
Búsqueda basada en DFS
- Comienza en la raíz y ejecuta transiciones del DFA siguiendo cada arista
- Si no hay transición, se poda (prune) ese subárbol
- Si el estado del DFA es accepting, registra la ruta y el valor
- Cada nodo se visita como máximo una vez, por lo que el recorrido completo es O(n)
serde_json_borrow referencia el buffer original sin copiar cadenas
Metodología de benchmark
- Benchmarks estadísticos realizados con Criterion.rs
-
Conjuntos de datos
simple.json (106B), kubernetes-definitions.json (~992KB), kestra-0.19.0.json (~7.6MB), citylots.json (~190MB)
-
Herramientas comparadas
jsongrep, jsonpath-rust, jmespath, jaq, jql
-
Grupos de benchmark
document_parse: velocidad de parsing del JSON
query_compile: tiempo de compilación de la consulta
query_search: solo realiza la búsqueda
end_to_end: pipeline completo
-
Consideraciones de equidad
- La ventaja del parsing zero-copy se midió por separado
- El costo de compilación del DFA se midió por separado
- Las herramientas sin cierta funcionalidad se excluyeron de esa prueba
- El costo de duplicación de datos se trató por separado
Resultados de benchmark
- Tiempo de parsing del documento:
serde_json_borrow fue el más rápido
- Tiempo de compilación de la consulta:
jsongrep tuvo el mayor costo por la generación del DFA, mientras que jmespath fue mucho más rápido
- Tiempo de búsqueda:
jsongrep fue el más rápido entre todas las herramientas
- Rendimiento end-to-end: incluso sobre el dataset de 190MB, fue abrumadoramente más rápido que
jq, jmespath, jsonpath-rust y jql
- Los resultados completos pueden verse en el sitio de benchmarks en vivo
Licencia y uso
- Software de código abierto bajo licencia MIT
- Disponible en GitHub, Crates.io y Docs.rs
- El motor de consultas basado en DFA puede reutilizarse como biblioteca, e integrarse directamente en proyectos Rust
Referencias
- Glushkov, V. M. (1961), The Abstract Theory of Automata
- Rabin, M. O., & Scott, D. (1959), Finite Automata and Their Decision Problems
3 comentarios
Qué genial
| ¿Por qué el símbolo de tubería se ve diferente en el cuerpo? Qué curioso..
Comentarios en Hacker News
La sintaxis de jq es demasiado críptica, así que cada vez que quiero sacar aunque sea un valor simple de JSON, tengo que buscar cómo hacerlo
Normalmente escribo filtros de una sola vez, así que paso más tiempo escribiéndolos que leyéndolos
Quizá mis casos de uso son simples o jq encaja bien con mi forma de pensar
Sueño con un mundo donde todas las herramientas CLI lean y escriban JSON y se conecten con jq, aunque supongo que para ti eso sería una pesadilla
Cada vez que la uso siento que tengo que aprenderla de nuevo, así que no se siente intuitiva
Incluso con Turing complete, la mayoría usa
sedsolo para reemplazos con regexMe gusta jq, pero hubo veces en que no podía entender consultas que yo mismo había escrito antes
celq usa el lenguaje CEL, que resulta más familiar
Básicamente es una forma de manejar JSON con JavaScript, y sorprendentemente es más rápida que jq
Se usa así:
$ cat package.json | dq 'Object.keys(data).slice(0, 5)'Como aprendí Clojure, ahora uso EDN en vez de JSON
Es más conciso, más fácil de leer y más cómodo de manejar estructuralmente
Últimamente manejo datos con borkdude/jet o babashka, y los visualizo con djblue/portal
No entiendo por qué insistir en operadores complejos como los de jq
Me importa el rendimiento, pero comparar en nanosegundos me parece más bien rendimiento para presumir
En la mayoría de los casos, las herramientas que ya uso son suficientes
Por ejemplo, yo solo uso
rgen lugar degrepcuando trabajo con archivos grandesLa diferencia entre 2 ms y 0.2 ms puede parecer mínima, pero para quien procesa streams de tamaño TB sí importa
El hardware se ha vuelto más rápido, pero el software en realidad se ha vuelto más lento
Negarse a optimizar se siente como pereza y falta de imaginación
Quedarse tranquilo solo porque es más rápido que la latencia de red suena a excusa
Si el JSON es demasiado grande, entonces probablemente deberías usar un formato binario en lugar de JSON
Si necesitas armar pipelines complejos en la CLI, yo diría que mejor escribas un programa
Muchas herramientas CLI nuevas se venden con el mensaje de “somos más rápidas”, pero casi nunca he sentido que jq sea lento
Incluso tareas simples como renombrar campos con jq son demasiado lentas, así que las proceso yo mismo con scripts en Node o Rust
En entornos de hiperescaladores, se descargan y analizan directamente varios TB de logs
Dependiendo de la resolución del monitoreo, la diferencia de rendimiento puede notarse
Implementan solo parte de la funcionalidad y luego proclaman victoria con benchmarks
Este proyecto también parece parte de esa tendencia de “el subconjunto es más rápido”
Desde entonces, todo te empieza a parecer lento
Como con ripgrep: una vez que usas una herramienta rápida, cuesta volver atrás
He usado tanto jq como yq, pero aunque yq es bastante más lento, nunca me molestó
Si existe una herramienta más rápida que jq, genial, pero eso solo le hace falta a cierto tipo de usuarios
Aun así, como alguien que ama la optimización, mis respetos
En la etapa ETL sí toma bastante tiempo
Cuando abrí la página por primera vez, había un fallo de colores en modo claro
Si cambiabas a modo oscuro y luego regresabas, se arreglaba
Me cambié a Jaq por la precisión
Dicen que además rinde mejor que jq
La reputación de jq como lento parece deberse a cómo lo empaquetan las distribuciones
En el trabajo manejo mucho newline-delimited JSON (jsonl)
Cada línea es un objeto JSON completo, y me da curiosidad saber si las principales herramientas CLI soportan este formato
He usado varias herramientas CLI para procesar datos como jq, mlr, htmlq, xsv y yq,
pero desde que descubrí Nushell, reemplazó a todas
Fue una experiencia refrescante poder manejar todos los formatos con una sola sintaxis
Solo sigo usando jq, yq y mlr cuando colaboro con compañeros
Hay algunas pequeñas molestias con la configuración del autocompletado y la facilidad para encontrar comandos, pero es muchísimo mejor que oh-my-zsh
Si algún día agrega obligatoriedad de anotaciones de tipos, compilación a binarios estáticos y una librería TUI, hasta serviría para escribir apps pequeñas
¡Gran herramienta! Pero la visualización de benchmarks me pareció un poco floja
Todas las herramientas tienen el mismo color, así que cuesta encontrar dónde está jsongrep
Además jq ni siquiera aparece en la gráfica, lo que me confundió
El archivo xLarge tiene 190MiB, que me parece pequeño; yo suelo manejar JSON de 400MiB a 1GiB
Si conocen documentos JSON públicos más grandes, sería bueno que me los compartieran
La visualización de benchmarks se siente tosca
Estaría bien usar colores o formas para expresar más dimensiones
Tener que leer directamente las rutas de archivos para entender los resultados es incómodo