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.


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


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"

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")'

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.


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.


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

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.