- Ferramenta CLI em Rust para buscar documentos JSON com base em caminhos, com velocidade de busca superior a
jq, jmespath, jsonpath-rust e jql
- Expressa consultas como linguagem regular e as compila para DFA, percorrendo a árvore JSON em uma única passagem e processando em tempo O(n)
- Usa
serde_json_borrow, que oferece parsing zero-copy, para minimizar alocações de memória, e foi projetada com base na filosofia de desempenho do ripgrep
- Nos benchmarks, apresentou o melhor desempenho de ponta a ponta mesmo com JSONs grandes, oferecendo uma linguagem de consulta simples voltada à busca
- Disponível sob licença MIT, com o mecanismo de consultas baseado em DFA reutilizável como biblioteca Rust
Visão geral do jsongrep
- jsongrep é uma ferramenta CLI em Rust para buscar valores em documentos JSON com base em caminhos, com o objetivo de ser mais rápida que
jq, jmespath, jsonpath-rust e jql
- Trata documentos JSON como árvores e expressa caminhos (path) como linguagens regulares (regular language), compilando-os em DFAs (Deterministic Finite Automata) para realizar a busca em uma única passagem
- A linguagem de consulta é simples e foi projetada com foco em busca, sem recursos de transformação ou cálculo
- Minimiza alocações de memória com parsing zero-copy usando
serde_json_borrow
- Foi desenvolvida com referência à filosofia de design e à abordagem de desempenho do
ripgrep
Exemplos de uso do jsongrep
- O comando
jg recebe uma consulta e uma entrada JSON, e imprime todos os valores cujos caminhos correspondem à consulta
- Acesso a campos aninhados com notação por ponto (dot path)
jg 'roommates[0].name' → "Alice"
- Wildcards (
*, [*]) para corresponder a todas as chaves ou índices
- Alternation (
|) para escolher um entre vários caminhos
- Busca recursiva (
(* | [*])*) para pesquisar campos em qualquer profundidade
- Optional (
?) permite correspondência de 0 ou 1 vez
- A opção
-F permite buscar rapidamente por um nome de campo específico
- Ao usar pipe (
| less, | sort), a exibição do caminho é omitida automaticamente; --with-path pode forçar sua exibição
Conceitos centrais do jsongrep
- JSON é uma estrutura em árvore, e chaves de objetos e índices de arrays atuam como arestas (edges)
- A consulta define um conjunto de caminhos do nó raiz até nós específicos
- A linguagem de consulta é projetada como uma linguagem regular, o que permite convertê-la em um DFA
- O DFA lê a entrada apenas uma vez e faz a busca em tempo O(n), sem backtracking
- Ferramentas existentes (
jq, jmespath etc.) interpretam a consulta e fazem busca recursiva, enquanto o jsongrep usa um DFA pré-compilado para busca em passagem única
Estrutura do mecanismo de consultas baseado em DFA
- O pipeline é composto por 5 etapas
- Parse do JSON em árvore com
serde_json_borrow
- Parse da consulta em AST
- Geração de NFA com o algoritmo de Glushkov
- Conversão para DFA com Subset Construction
- Percurso da árvore JSON com uma única DFS seguindo as transições do DFA
-
Parse da consulta
- Converte a consulta em AST
Query com gramática PEG (usando a biblioteca pest)
- Principais elementos sintáticos:
Field, Index, Range, FieldWildcard, ArrayWildcard, Optional, KleeneStar, Disjunction, Sequence
- Ex.:
roommates[*].name → Sequence(Field("roommates"), ArrayWildcard, Field("name"))
-
Modelo de árvore JSON
- Chaves de objetos e índices de arrays são arestas, e valores são nós
- Ex.:
roommates[*].name percorre o caminho roommates → [0] → name
-
Construção do NFA (algoritmo de Glushkov)
- Gera um NFA sem transições ε
- Etapas
- Atribuir números de posição aos símbolos da consulta
- Calcular os conjuntos First/Last/Follows
- Construir as transições entre posições
- O NFA da consulta de exemplo
roommates[*].name forma uma estrutura linear simples com 4 estados
-
Conversão para DFA (Subset Construction)
- Gera um DFA determinístico com base em conjuntos de estados do NFA
- Cada estado corresponde a um conjunto de estados do NFA
- Adiciona o símbolo
Other para pular com eficiência chaves desnecessárias
- Consultas simples são convertidas em um DFA com estrutura idêntica à do NFA
-
Busca baseada em DFS
- A partir da raiz, executa transições do DFA seguindo cada aresta
- Se não houver transição, aquela subárvore é podada (prune)
- Se o estado do DFA for accepting, registra o caminho e o valor
- Cada nó é visitado no máximo uma vez, então a busca total é O(n)
- Com
serde_json_borrow, referencia o buffer original sem copiar strings
Metodologia de benchmark
- Benchmarks estatísticos realizados com Criterion.rs
-
Conjuntos de dados
simple.json (106B), kubernetes-definitions.json (~992KB), kestra-0.19.0.json (~7.6MB), citylots.json (~190MB)
-
Ferramentas comparadas
jsongrep, jsonpath-rust, jmespath, jaq, jql
-
Grupos de benchmark
document_parse: velocidade de parsing do JSON
query_compile: tempo de compilação da consulta
query_search: apenas a busca
end_to_end: pipeline completo
-
Considerações de justiça na comparação
- A vantagem do parsing zero-copy foi medida separadamente
- O custo de compilação do DFA foi medido separadamente
- Ferramentas sem determinado recurso foram excluídas do teste correspondente
- O custo de duplicação de dados foi tratado separadamente
Resultados dos benchmarks
- Tempo de parsing do documento:
serde_json_borrow foi o mais rápido
- Tempo de compilação da consulta:
jsongrep teve o maior custo por causa da geração do DFA, enquanto jmespath foi muito mais rápido
- Tempo de busca:
jsongrep foi o mais rápido entre todas as ferramentas
- Desempenho de ponta a ponta: mesmo no conjunto de dados de 190MB, foi amplamente mais rápido que
jq, jmespath, jsonpath-rust e jql
- Os resultados completos podem ser vistos no site de benchmarks ao vivo
Licença e uso
- Software de código aberto sob licença MIT
- Disponível no GitHub, Crates.io e Docs.rs
- O mecanismo de consultas baseado em DFA pode ser reutilizado como biblioteca, sendo integrado diretamente a projetos Rust
Referências
- Glushkov, V. M. (1961), The Abstract Theory of Automata
- Rabin, M. O., & Scott, D. (1959), Finite Automata and Their Decision Problems
3 comentários
Muito legal.
| Por que o caractere de pipe aparece diferente no corpo do texto? Que curioso..
Comentários do Hacker News
A sintaxe do jq é difícil demais, então sempre preciso pesquisar até para pegar um único valor simples de JSON
Como normalmente escrevo filtros de uso único, passo mais tempo escrevendo do que lendo
Talvez meu caso de uso seja simples, ou jq combine bem com a forma como penso
Sonho com um mundo em que todas as ferramentas CLI façam entrada e saída em JSON e sejam conectadas com jq, mas isso provavelmente seria um pesadelo para você
Toda vez que uso, preciso reaprender, então não parece intuitivo
Mesmo que o sed seja Turing-completo, a maioria das pessoas só o usa para substituição com regex
Gosto de jq, mas às vezes não conseguia entender consultas que eu mesmo havia escrito antes
O celq usa a linguagem CEL, que é mais familiar
Ela simplesmente manipula JSON com JavaScript e, surpreendentemente, é mais rápida que jq
Eu uso assim:
$ cat package.json | dq 'Object.keys(data).slice(0, 5)'Como aprendi Clojure, agora uso EDN em vez de JSON
É mais conciso, mais fácil de ler e mais simples de manipular estruturalmente
Hoje em dia uso borkdude/jet ou babashka para lidar com dados, e djblue/portal para visualização
Não entendo por que insistir nos operadores complexos do jq
Eu valorizo desempenho, mas comparações em nanossegundos parecem mais uma performance exibicionista
Na maioria dos casos, a ferramenta que já uso é suficiente
Por exemplo, eu só uso rg no lugar de grep quando o arquivo é grande
A diferença entre 2 ms e 0,2 ms pode parecer pequena, mas para quem processa streams em escala de TB isso importa
O hardware ficou mais rápido, mas o software acabou ficando mais lento
Recusar otimização parece preguiça e falta de imaginação
Ficar tranquilo só porque é mais rápido que a latência de rede soa como desculpa
Se o JSON é grande demais, então talvez fosse melhor usar um formato binário em vez de JSON
Se for preciso montar pipelines complexos na CLI, acho melhor escrever um programa de uma vez
Muitas ferramentas CLI novas se vendem como “mais rápidas”, mas quase nunca senti que jq fosse lento de verdade
Até tarefas simples, como só renomear campos com jq, são lentas demais, então faço isso direto com scripts em Node ou Rust
Em ambientes de hyperscaler, as pessoas baixam e analisam diretamente logs com vários TB
Dependendo da resolução do monitoramento, a diferença de desempenho pode ser perceptível
Ela implementa só parte das funcionalidades e depois reivindica vitória com benchmarks
Este projeto também parece fazer parte dessa tendência de “o subconjunto é mais rápido”
A partir daí, tudo começa a parecer lento
Depois de usar uma ferramenta rápida como o ripgrep, fica difícil voltar atrás
Já usei tanto jq quanto yq, mas nunca me incomodei com o fato de yq ser muito mais lento
Se existir uma ferramenta mais rápida que jq, ótimo, mas isso só é necessário para um grupo específico de usuários
Ainda assim, como alguém que ama otimização, deixo meu respeito
Na etapa de ETL isso consome bastante tempo
Quando abri a página pela primeira vez, havia um problema de cores quebradas no modo claro
Se eu alternasse para o modo escuro e depois voltasse, resolvia
Eu migrei para Jaq por causa da correção
Dizem que o desempenho dele também é melhor que o do jq
A reputação de lentidão do jq parece vir de problemas de empacotamento nas distribuições
No trabalho eu lido com frequência com JSON delimitado por nova linha (jsonl)
Cada linha é um objeto JSON completo, e fiquei curioso para saber se as principais ferramentas CLI suportam esse formato
Já usei várias ferramentas CLI de processamento de dados como jq, mlr, htmlq, xsv e yq,
mas depois que descobri o Nushell, ele substituiu todas elas
Foi uma experiência renovadora poder lidar com todos os formatos com uma única sintaxe
Só continuo usando jq, yq e mlr em paralelo quando preciso colaborar com colegas
Ainda tenho algum incômodo com configuração de autocomplete e capacidade de descoberta de comandos, mas ele é muito melhor que oh-my-zsh
Se ganhar imposição de anotações de tipo, compilação para binário estático e até uma biblioteca TUI, eu usaria até para escrever apps pequenos
Ferramenta legal! Só achei a visualização dos benchmarks um pouco fraca
Como todas as ferramentas têm a mesma cor, fica difícil localizar o jsongrep
O próprio jq também não aparece no gráfico, o que me deixou confuso
O arquivo xLarge tem 190 MiB, o que ainda é pequeno; eu costumo lidar com JSONs de 400 MiB a 1 GiB
Se alguém souber de documentos JSON públicos maiores, seria ótimo indicar
A visualização dos benchmarks parece meio grosseira
Seria melhor usar cores ou formas para representar mais dimensões
Ter de ler diretamente o caminho do arquivo para entender os resultados é inconveniente