Usando jq , como a codificação JSON arbitrária de uma matriz de objetos superficiais pode ser convertida em CSV?
Há uma abundância de perguntas e respostas neste site que cobrem modelos de dados específicos que codificam os campos, mas as respostas a esta pergunta devem funcionar em qualquer JSON, com a única restrição de que é uma matriz de objetos com propriedades escalares (sem profundidade / complexa / subobjetos, já que achatá-los é outra questão). O resultado deve conter uma linha de cabeçalho com os nomes dos campos. Será dada preferência a respostas que preservem a ordem dos campos do primeiro objeto, mas não é um requisito. Os resultados podem incluir todas as células com aspas duplas ou incluir apenas aquelas que requerem aspas (por exemplo, 'a, b').
Exemplos
Entrada:
[ {"code": "NSW", "name": "New South Wales", "level":"state", "country": "AU"}, {"code": "AB", "name": "Alberta", "level":"province", "country": "CA"}, {"code": "ABD", "name": "Aberdeenshire", "level":"council area", "country": "GB"}, {"code": "AK", "name": "Alaska", "level":"state", "country": "US"} ]
Resultado possível:
code,name,level,country NSW,New South Wales,state,AU AB,Alberta,province,CA ABD,Aberdeenshire,council area,GB AK,Alaska,state,US
Resultado possível:
"code","name","level","country" "NSW","New South Wales","state","AU" "AB","Alberta","province","CA" "ABD","Aberdeenshire","council area","GB" "AK","Alaska","state","US"
Entrada:
[ {"name": "bang", "value": "!", "level": 0}, {"name": "letters", "value": "a,b,c", "level": 0}, {"name": "letters", "value": "x,y,z", "level": 1}, {"name": "bang", "value": "\"!\"", "level": 1} ]
Resultado possível:
name,value,level bang,!,0 letters,"a,b,c",0 letters,"x,y,z",1 bang,"""!""",0
Resultado possível:
"name","value","level" "bang","!","0" "letters","a,b,c","0" "letters","x,y,z","1" "bang","""!""","1"
json2csv
está em stackoverflow.com/questions/57242240/…Respostas:
Primeiro, obtenha uma matriz contendo todos os nomes de propriedades de objeto diferentes em sua entrada de matriz de objeto. Essas serão as colunas do seu CSV:
Em seguida, para cada objeto na entrada da matriz do objeto, mapeie os nomes das colunas obtidos para as propriedades correspondentes no objeto. Essas serão as linhas do seu CSV.
Finalmente, coloque os nomes das colunas antes das linhas, como um cabeçalho para o CSV, e passe o fluxo de linhas resultante para o
@csv
filtro.Todos juntos agora. Lembre-se de usar o
-r
sinalizador para obter o resultado como uma string bruta:fonte
$rows
atribuição de variável apenas inserindo-a:(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
$rows
não precisa ser atribuído a uma variável; Só pensei que atribuí-lo a uma variável tornava a explicação mais agradável.The Skinny
ou:
Os detalhes
a parte, de lado
Descrever os detalhes é complicado porque jq é orientado a fluxo, o que significa que opera em uma sequência de dados JSON, em vez de um único valor. O fluxo JSON de entrada é convertido em algum tipo interno que é passado pelos filtros e, em seguida, codificado em um fluxo de saída no final do programa. O tipo interno não é modelado por JSON e não existe como um tipo nomeado. É mais facilmente demonstrado examinando a saída de um índice simples (
.[]
) ou do operador vírgula (examiná-lo diretamente poderia ser feito com um depurador, mas isso seria em termos de tipos de dados internos de jq, em vez dos tipos de dados conceituais por trás de JSON) .Observe que a saída não é uma matriz (o que seria
["a", "b"]
). A saída compacta (a-c
opção) mostra que cada elemento da matriz (ou argumento do,
filtro) se torna um objeto separado na saída (cada um está em uma linha separada).Um stream é como um JSON-seq , mas usa novas linhas em vez de RS como separador de saída quando codificado. Consequentemente, este tipo interno é referido pelo termo genérico "sequência" nesta resposta, com "fluxo" sendo reservado para a entrada e saída codificadas.
Construindo o Filtro
As chaves do primeiro objeto podem ser extraídas com:
Geralmente, as chaves são mantidas em sua ordem original, mas a preservação da ordem exata não é garantida. Conseqüentemente, eles precisarão ser usados para indexar os objetos para obter os valores na mesma ordem. Isso também evitará que os valores estejam nas colunas erradas se alguns objetos tiverem uma ordem de chave diferente.
Para gerar as chaves como a primeira linha e torná-las disponíveis para indexação, elas são armazenadas em uma variável. O próximo estágio do pipeline faz referência a essa variável e usa o operador vírgula para anexar o cabeçalho ao fluxo de saída.
A expressão após a vírgula é um pouco complicada. O operador de índice em um objeto pode receber uma sequência de strings (por exemplo
"name", "value"
), retornando uma sequência de valores de propriedade para essas strings.$keys
é uma matriz, não uma sequência, então[]
é aplicada para convertê-la em uma sequência,que pode então ser passado para
.[]
Isso também produz uma sequência, portanto, o construtor de matriz é usado para convertê-la em uma matriz.
Esta expressão deve ser aplicada a um único objeto.
map()
é usado para aplicá-lo a todos os objetos na matriz externa:Por último, para este estágio, isso é convertido em uma sequência para que cada item se torne uma linha separada na saída.
Por que agrupar a sequência em um array dentro do
map
apenas para descompactá-la fora?map
produz uma matriz;.[ $keys[] ]
produz uma sequência. Aplicarmap
à sequência de.[ $keys[] ]
produziria uma matriz de sequências de valores, mas como as sequências não são do tipo JSON, em vez disso, você obtém uma matriz achatada contendo todos os valores.Os valores de cada objeto precisam ser mantidos separados, para que se tornem linhas separadas na saída final.
Por fim, a sequência é passada pelo
@csv
formatador.Alternar
Os itens podem ser separados mais tarde, em vez de mais cedo. Em vez de usar o operador vírgula para obter uma sequência (passando uma sequência como o operando correto), a sequência de cabeçalho (
$keys
) pode ser agrupada em uma matriz e+
usada para anexar a matriz de valores. Isso ainda precisa ser convertido em uma sequência antes de ser passado para@csv
.fonte
keys_unsorted
vez dekeys
para preservar a ordem das chaves do primeiro objeto?[{"a":1,"b":2,"c":3}]
.Criei uma função que produz uma matriz de objetos ou matrizes para csv com cabeçalhos. As colunas estariam na ordem dos cabeçalhos.
Então você pode usá-lo assim:
fonte
O filtro a seguir é ligeiramente diferente, pois garante que cada valor seja convertido em uma string. (Nota: use jq 1.5+)
Filtro:
filter.jq
fonte
unique
é classificada de qualquer maneira, portanto,unique|sort
pode ser simplificada paraunique
.-r
opção. Caso contrário, todas as aspas"
terão escape extra, o que não é um CSV válido.Esta variante do programa de Santiago também é segura, mas garante que os nomes das chaves no primeiro objeto sejam usados como os cabeçalhos das primeiras colunas, na mesma ordem em que aparecem nesse objeto:
fonte