Usando jq para extrair valores e formato em CSV

58

Eu tenho o arquivo JSON abaixo:

{
"data": [
    {
        "displayName": "First Name",
        "rank": 1,
        "value": "VALUE"
    },
    {
        "displayName": "Last Name",
        "rank": 2,
        "value": "VALUE"
    },
    {
        "displayName": "Position",
        "rank": 3,
        "value": "VALUE"
    },
    {
        "displayName": "Company Name",
        "rank": 4,
        "value": "VALUE"
    },
    {
        "displayName": "Country",
        "rank": 5,
        "value": "VALUE"
    },
]
}

Eu gostaria de ter um arquivo CSV neste formato:

First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE, VALUE

Isso é possível usando apenas jq? Eu não tenho nenhuma habilidade de programação.

Kerim
fonte
1
Forneci uma resposta abaixo, mas agora estou olhando mais de perto a sua pergunta e não consigo deixar de me perguntar - de onde é o sexto VALUE ?
mikeserv
1
Conexa do SO: stackoverflow.com/questions/25558456/...
Anton Tarasenko
Também relacionado stackoverflow.com/q/32960857/168034
phunehehe

Respostas:

50

jq possui um filtro, @csv, para converter uma matriz em uma sequência CSV. Esse filtro leva em consideração a maioria das complexidades associadas ao formato CSV, começando com vírgulas incorporadas nos campos. (A jq 1.5 possui um filtro semelhante, @tsv, para gerar arquivos com valores separados por tabulação.)

Obviamente, se todos os cabeçalhos e valores estiverem livres de vírgulas e aspas duplas, talvez não seja necessário usar o filtro @csv. Caso contrário, provavelmente seria melhor usá-lo.

Por exemplo, se o 'Nome da empresa' fosse 'Smith, Smith e Smith' e se os outros valores fossem mostrados abaixo, chamar jq com a opção "-r" produziria um CSV válido:

$ jq -r '.data | map(.displayName), map(.value) | @csv' so.json2csv.json
"First Name","Last Name","Position","Company Name","Country"
"John (""Johnnie"")","Doe","Director, Planning and Posterity","Smith, Smith and Smith","Transylvania"
pico
fonte
3
Eu era capaz de 'jq somestuff | mapa (.) | @csv ', muito útil! Graças
flickerfly
3
Seu exemplo vai colocar todos os nomes de exibição na primeira linha e todos os valores na segunda linha, em vez de ter uma linha por registro.
Brian Gordon
33

Prefiro fazer cada registro uma linha no meu CSV.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'
Silas Paul
fonte
2
E se .value for um número? Eu recebo o "string e número não podem ser adicionados" erro
Cos
2
@Cos algo como, em .value|tostringvez de .valueno exemplo acima
matheeeny 20/09/16
4
@ Cos, eu achei parênteses são necessários. (.value|tostring)
Ciscogambo 22/09
Além disso, use jq -rpara retirar as aspas
Clay
30

Dado apenas esse arquivo, você pode fazer algo como:

<testfile jq -r '.data | map(.displayName), map(.value) | join(", ")'

O .operador seleciona um campo de um objeto / hash. Assim, começamos com .data, que retorna a matriz com os dados nela. Em seguida, mapeamos a matriz duas vezes, primeiro selecionando o displayName e depois o valor, fornecendo duas matrizes apenas com os valores dessas chaves. Para cada matriz, juntamos os elementos com "," formando duas linhas. O -rargumento diz jqpara não citar as seqüências resultantes.

Se o seu arquivo atual for mais longo (ou seja, tiver entradas para mais de uma pessoa), você provavelmente precisará de algo um pouco mais complicado.

Steven D
fonte
Não está funcionando para mim. Em um tópico relacionado, a resposta stackoverflow.com/questions/32960857/… está funcionando e muito bem explicada!
usar o seguinte
10

Eu achei jqdifícil de entender. Aqui estão alguns Ruby:

ruby -rjson -rcsv -e '
  data = JSON.parse(File.read "file.json")
  data["data"].collect {|item| [item["displayName"], item["value"]]}
              .transpose
              .each {|row| puts row.to_csv}
'
First Name,Last Name,Position,Company Name,Country
VALUE,VALUE,VALUE,VALUE,VALUE

O analisador de JSON rubi vomitou sobre a vírgula à direita antes do colchete.

Glenn Jackman
fonte
2

Desde que você marcou isso pythone assumindo que o nome do jsonarquivo éx.json

import os, json
with open('x.json') as f:
    x  = json.load(f)
    print '{}{}{}'.format(', '.join(y['displayName'] for y in x['data']), os.linesep,
             ', '.join(y['value'] for y in x['data']))
First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE
iruvar
fonte
1

Embora eu tenha que remover a última vírgula na sua entrada de exemplo para fazê-la funcionar, porque jqestava reclamando de esperar outro elemento da matriz, isso:

INPUT | jq -r '[.[][].displayName], [.[][].value]| join(", ")'

... me pegou ...

First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE

Como funciona em poucas palavras:

  1. Atravessei o terceiro nível de objetos de dados usando o []formulário e a .dotnotação de campo de índice vazio .
  2. Uma vez profundo o suficiente, especifiquei os campos de dados que eu queria pelo nome como .[][].displayName.
  3. Eu assegurei que meus campos desejados fossem autoassociados retornando-os como objetos de matriz separados, como [.[][].displayName], [.[][].value]
  4. E, em seguida, canalizou esses objetos para a join(", ")função a ser unida como entidades separadas.

Na verdade, fazer [.field]é apenas outra maneira de fazê - lo, map(.field)mas isso é um pouco mais específico, pois especifica o nível de profundidade para recuperar os dados desejados.

mikeserv
fonte