Como filtrar uma matriz de objetos com base em valores em uma matriz interna com jq?

240

Dada esta entrada:

[
  {
    "Id": "cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b",
    "Names": [
      "condescending_jones",
      "loving_hoover"
    ]
  },
  {
    "Id": "186db739b7509eb0114a09e14bcd16bf637019860d23c4fc20e98cbe068b55aa",
    "Names": [
      "foo_data"
    ]
  },
  {
    "Id": "a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19",
    "Names": [
      "jovial_wozniak"
    ]
  },
  {
    "Id": "76b71c496556912012c20dc3cbd37a54a1f05bffad3d5e92466900a003fbb623",
    "Names": [
      "bar_data"
    ]
  }
]

Estou tentando construir um filtro com jq que retorna todos os objetos com Ids que não contêm "dados" na Namesmatriz interna , com a saída sendo separada por nova linha. Para os dados acima, a saída que eu gostaria é

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19

Eu acho que estou um pouco perto disso:

(. - select(.Names[] contains("data"))) | .[] .Id

mas o selectfiltro não está correto e não compila (obtém error: syntax error, unexpected IDENT).

Abe Voelker
fonte

Respostas:

372

Muito perto! Na sua selectexpressão, você deve usar um pipe ( |) antes contains.

Este filtro produz a saída esperada.

. - map(select(.Names[] | contains ("data"))) | .[] .Id

O livro de receitas jq tem um exemplo da sintaxe.

Filtrar objetos com base no conteúdo de uma chave

Por exemplo, eu só quero objetos cuja chave de gênero contenha "casa".

$ json='[{"genre":"deep house"}, {"genre": "progressive house"}, {"genre": "dubstep"}]'
$ echo "$json" | jq -c '.[] | select(.genre | contains("house"))'
{"genre":"deep house"}
{"genre":"progressive house"}

Colin D pergunta como preservar a estrutura JSON da matriz, para que a saída final seja uma única matriz JSON em vez de um fluxo de objetos JSON.

A maneira mais simples é agrupar toda a expressão em um construtor de matriz:

$ echo "$json" | jq -c '[ .[] | select( .genre | contains("house")) ]'
[{"genre":"deep house"},{"genre":"progressive house"}]

Você também pode usar a função de mapa:

$ echo "$json" | jq -c 'map(select(.genre | contains("house")))'
[{"genre":"deep house"},{"genre":"progressive house"}]

O mapa descompacta a matriz de entrada, aplica o filtro a todos os elementos e cria uma nova matriz. Em outras palavras, map(f)é equivalente a [.[]|f].

Iain Samuel McLean Elder
fonte
Obrigado, funciona muito bem! Eu realmente ver esse exemplo, eu só falhou em adaptá-lo ao meu cenário :-)
Abe Voelker
1
Existe alguma maneira de "preservar a estrutura json da matriz"? Eu gosto do exemplo de gênero, mas ele gera duas "linhas json". Eu não conseguia descobrir a parte mapa necessariamente
Colin D
4
@ColinD Eu não estava muito feliz com a solução de redução, então substituí-a por uma explicação da função do mapa. Isso ajuda?
Iain Samuel McLean Elder
@IainElder - O que acontece quando a parte do termo de pesquisa (neste caso, casa) é uma variável? Então diga usando --args term se. Então contém ("hou $ term")
SnazzyBootMan
@ Chris A variável $termseria tratada como uma string, então você deve usar a concatenação de strings:contains("hou" + $term)
Iain Samuel McLean Elder
17

Aqui está outra solução que usa qualquer / 2

map(select(any(.Names[]; contains("data"))|not)|.Id)[]

com os dados da amostra e a -ropção que produz

cb94e7a42732b598ad18a8f27454a886c1aa8bbba6167646d8f064cd86191e2b
a4b7e6f5752d8dcb906a5901f7ab82e403b9dff4eaaeebea767a04bac4aada19
jq170727
fonte
Exatamente o que eu estava procurando - por que isso funciona com ponto .Names[] ; contains()e vírgula e não com um cano .Names[] | contains()?
Matt
3
Ah, é a any(generator; condition)forma. Descobri que, sem o uso any(), acabaria com duplicatas nos meus resultados se select()correspondessem mais de uma vez no mesmo objeto.
Matt