Usando jq para extrair valores e formatar em CSV

44

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.

    
por Kerim 23.10.2014 / 20:36

6 respostas

40

jq tem um filtro, @csv, para converter um array em uma string CSV. Esse filtro leva em consideração a maioria das complexidades associadas ao formato CSV, começando com vírgulas incorporadas nos campos. (O jq 1.5 possui um filtro similar, @tsv, para gerar arquivos separados por tabulação.)

Naturalmente, se os cabeçalhos e valores estiverem garantidamente 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' for 'Smith, Smith e Smith', e se os outros valores forem mostrados abaixo, invocar 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"
    
por 07.09.2015 / 05:12
29

Dado apenas este 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 o array duas vezes, primeiro selecionando a displayName e, em seguida, selecionando o valor, dando-nos dois arrays com apenas os valores dessas chaves. Para cada array, juntamos os elementos com "," formando duas linhas. O argumento -r informa jq para não citar as sequê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.

    
por 23.10.2014 / 20:57
24

Eu prefiro fazer cada registro em uma linha no meu CSV.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'
    
por 13.11.2015 / 02:33
7

Eu achei jq difícil de envolver minha cabeça. Aqui está um pouco de 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 parser JSON ruby barfou sobre a vírgula final antes do colchete de fechamento.

    
por 23.10.2014 / 20:59
2

Como você marcou esse python e assumindo que o nome do arquivo json é 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
    
por 23.10.2014 / 21:29
1

Embora eu tenha que remover a última vírgula em sua entrada de exemplo para que funcione, porque jq estava reclamando sobre a expectativa de outro elemento de matriz, isto:

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. Passei pelo terceiro nível de objetos de dados usando o formulário de campo vazio [] index e .dot notation.
  2. Uma vez profundos, especifiquei os campos de dados que eu queria por nome como .[][].displayName .
  3. Assegurei que meus campos desejados eram autoassociados retornando-os como objetos de matriz separados, como [.[][].displayName], [.[][].value]
  4. E, em seguida, canalizou esses objetos para a função join(", ") a ser unida como entidades separadas.

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

    
por 23.10.2014 / 21:31