Truques para usar jq em transformações complexas de JSON em pipelines

1. Fundamentos e Configuração do Ambiente

O jq é um processador JSON de linha de comando leve e flexível, essencial para qualquer pipeline de dados moderno. Para instalar, utilize:

# Linux (Debian/Ubuntu)
sudo apt-get install jq

# macOS
brew install jq

# Windows (WSL)
sudo apt-get install jq

# Verificar versão
jq --version

Flags essenciais para pipelines:
- -c — saída compacta (sem quebras de linha)
- -r — saída raw (remove aspas de strings)
- -s — lê múltiplos objetos JSON como um array
- -n — entrada nula (útil com --arg)

Estrutura básica de filtros:

# Selecionar campo aninhado
echo '{"user":{"name":"Alice","age":30}}' | jq '.user.name'

# Pipe entre filtros
echo '[1,2,3,4]' | jq '.[] | . * 2'

# Operadores aritméticos
echo '{"a":10,"b":20}' | jq '.a + .b'

2. Filtros Avançados de Seleção e Filtragem

Combinando select() com operadores lógicos:

# Selecionar usuários ativos com idade > 25
echo '[{"name":"Ana","age":28,"active":true},{"name":"Bob","age":22,"active":false}]' | \
jq '.[] | select(.active == true and .age > 25)'

# Negação com not
jq '.[] | select(.status != "inactive")'

Navegação por arrays aninhados:

# map() para transformar cada elemento
echo '[[1,2],[3,4],[5,6]]' | jq 'map(add)'

# flatten() para achatar arrays
echo '[[1,[2,3]],[4,[5,6]]]' | jq 'flatten'

# map_values() para objetos
echo '{"a":1,"b":2,"c":3}' | jq 'map_values(. * 2)'

Localizando e removendo chaves com paths() e del():

# Encontrar caminhos de chaves específicas
echo '{"a":{"b":{"c":1}}}' | jq 'paths'

# Remover chave aninhada
echo '{"user":{"name":"Ana","password":"12345"}}' | jq 'del(.user.password)'

3. Transformações com Construtores de Objetos e Arrays

Criação dinâmica de objetos:

# Chaves calculadas
echo '{"name":"Ana","role":"admin"}' | jq '{(.role): .name}'

# Objetos condicionais
jq '{name, status: (if .age > 18 then "adult" else "minor" end)}'

Agregação com group_by() e reduce():

# Agrupar por departamento e contar
echo '[{"dept":"TI","name":"Ana"},{"dept":"RH","name":"Bob"},{"dept":"TI","name":"Carlos"}]' | \
jq 'group_by(.dept) | map({dept: .[0].dept, count: length})'

# reduce para somar valores
echo '[1,2,3,4]' | jq 'reduce .[] as $item (0; . + $item)'

Transformação entre arrays e objetos:

# to_entries: objeto -> array de pares
echo '{"a":1,"b":2}' | jq 'to_entries'

# from_entries: array de pares -> objeto
echo '[{"key":"x","value":10},{"key":"y","value":20}]' | jq 'from_entries'

4. Manipulação de Strings e Expressões Regulares

Funções regex avançadas:

# test() para validação
echo '{"email":"user@example.com"}' | jq 'select(.email | test("^[a-z]+@[a-z]+\\.[a-z]+$"))'

# capture() para extrair grupos
echo '{"log":"ERROR: file not found (line 42)"}' | \
jq '.log | capture("(?<level>ERROR|WARN): (?<msg>.+) \\(line (?<line>\\d+)\\)")'

# match() com flags
echo '{"text":"Hello World"}' | jq '.text | match("hello"; "gi")'

Substituição de padrões:

# sub() para primeira ocorrência
echo '{"phone":"(11) 99999-8888"}' | jq '.phone | sub("[^0-9]"; "")'

# gsub() para todas ocorrências
echo '{"data":"2024-01-15"}' | jq '.data | gsub("-"; "/")'

# split() para dividir strings
echo '{"csv":"a,b,c,d"}' | jq '.csv | split(",")'

Combinação de regex com select():

# Filtrar logs por padrão
jq '.[] | select(.message | test("error|fail|critical"; "i"))'

5. Integração com Pipelines Unix e Ferramentas Externas

Encadeamento com curl:

# API REST com paginação
curl -s "https://api.github.com/repos/stedolan/jq/issues?per_page=100" | \
jq -r '.[] | "\(.number) \(.title)"' | \
head -10

# Combinar com xargs para processamento paralelo
curl -s "https://api.github.com/repos/stedolan/jq/issues" | \
jq -r '.[].url' | \
xargs -P4 -I{} sh -c 'curl -s "$1" | jq ".title"' _ {}

Passando variáveis do shell:

# --arg para strings
LIMITE=5
curl -s "https://api.exemplo.com/dados" | jq --arg lim "$LIMITE" '.[:$lim | tonumber]'

# --argjson para valores JSON
jq -n --argjson filtro '{"status":"active"}' '$filtro'

Parsing de logs JSON em tempo real:

# Monitorar logs do Docker
docker logs -f container_app | jq -c 'select(.level == "ERROR") | {time, message}'

6. Técnicas de Performance e Depuração

Tratamento de erros:

# Operador // para valores nulos
echo '{"name":"Ana"}' | jq '.age // 0'

# try/catch
jq 'try .data catch "Erro ao processar dados"'

# Evitar erros com optional (?.)
jq '.user?.address?.city // "Desconhecido"'

Depuração com debugger e stderr:

# debugger interativo
jq 'debugger | .name'

# stderr para logs intermediários
jq '.[] | (stderr | "Processando: \(.id)"), .name'

# Inspecionar tipo de dado
jq 'type'

Otimização com --stream:

# Processar JSON gigante sem carregar tudo na memória
jq --stream 'select(.[0] | length == 2) | .[1]' arquivo_enorme.json

# Filtrar apenas campos específicos
jq --stream 'select(.[0][0] == "users") | .[1]' dados.json

7. Casos de Uso Reais em Automação e DevOps

Transformação de docker inspect:

# Listar containers com IP e porta
docker inspect $(docker ps -q) | jq -r '.[] | 
  {name: .Name, 
   ip: .NetworkSettings.Networks[].IPAddress, 
   ports: .NetworkSettings.Ports | to_entries[] | 
     {container: .key, host: .value[0].HostPort}}'

Normalização de dados de CI/CD:

# GitHub Actions - extrair status de jobs
curl -s "https://api.github.com/repos/user/repo/actions/runs/12345/jobs" | \
jq -r '.jobs[] | select(.conclusion == "failure") | "\(.name): \(.step)"'

Geração de configurações dinâmicas:

# Template de configuração Nginx a partir de JSON
echo '{"domains":["site1.com","site2.com"],"port":8080}' | \
jq -r '.domains[] | 
  "server {\n  listen \(.port);\n  server_name \(.);\n  location / {\n    proxy_pass http://localhost:\(.port);\n  }\n}"' > nginx.conf

Referências