Utilizzo di jq per estrarre valori e formattare in CSV


58

Ho il seguente file JSON:

{
"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"
    },
]
}

Vorrei avere un file CSV in questo formato:

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

È possibile solo usando jq? Non ho alcuna abilità di programmazione.


1
Di seguito ho fornito una risposta, ma ora sto esaminando più da vicino la tua domanda e non posso fare a meno di chiedermi: da dove dovrebbe provenire il 6 ° VALORE ?
Mikeserv,


Risposte:


50

jq ha un filtro, @csv, per convertire un array in una stringa CSV. Questo filtro tiene conto della maggior parte delle complessità associate al formato CSV, a partire dalle virgole incorporate nei campi. (jq 1.5 ha un filtro simile, @tsv, per generare file con valori separati da tabulazioni.)

Naturalmente, se le intestazioni e i valori sono tutti garantiti come privi di virgole e virgolette doppie, potrebbe non essere necessario utilizzare il filtro @csv. Altrimenti, probabilmente sarebbe meglio usarlo.

Ad esempio, se "Nome azienda" fosse "Smith, Smith e Smith" e se gli altri valori fossero come mostrato di seguito, invocare jq con l'opzione "-r" produrrebbe CSV valido:

$ 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
Sono stato in grado di 'jq somestuff | mappa (.) | @csv ', molto utile! Grazie
flickerfly il

3
Il tuo esempio metterà tutti i nomi visualizzati sulla prima riga e tutti i valori sulla seconda riga, invece di avere una riga per record.
Brian Gordon,

33

Preferisco rendere ogni record una riga nel mio CSV.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'

2
Cosa succede se .value è un numero? Ricevo l'errore "impossibile aggiungere la stringa e il numero"
Cos

2
@Cos qualcosa di simile .value|tostringinvece che .valuenell'esempio sopra
matheeeny

4
@Cos, ho trovato che sono necessarie le parentesi. (.value|tostring)
ciscogambo,

Inoltre, usa jq -rper eliminare le virgolette
Clay il

30

Dato solo questo file, puoi fare qualcosa del tipo:

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

L' .operatore seleziona un campo da un oggetto / hash. Quindi, iniziamo con .data, che restituisce l'array con i dati al suo interno. Quindi mappiamo due volte sull'array, prima selezionando displayName, quindi selezionando il valore, dandoci due matrici con solo i valori di quelle chiavi. Per ogni array, uniamo gli elementi con "," formando due linee. L' -rargomento dice jqdi non citare le stringhe risultanti.

Se il tuo file attuale è più lungo (cioè contiene voci per più di una persona), probabilmente avrai bisogno di qualcosa di un po 'più complicato.


Non funziona per me. In un argomento correlato, la risposta stackoverflow.com/questions/32960857/… funziona allo stesso modo e molto ben spiegata!
Herve

10

Ho trovato jqdifficile avvolgermi la testa. Ecco un po 'di rubino:

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

Il parser JSON ruby ​​ha scoperto la virgola finale prima della parentesi chiusa.


2

Dal momento che hai taggato questo pythone assumendo il nome del jsonfile è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

Anche se ho dovuto rimuovere l'ultima virgola nel tuo input di esempio per farlo funzionare perché jqsi lamentava di aspettarsi un altro elemento dell'array, questo:

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

... mi ha preso ...

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

Come funziona in breve:

  1. Sono passato al terzo livello di oggetti dati usando la []forma e la .dotnotazione del campo indice vuoto .
  2. Una volta abbastanza profondo ho specificato i campi di dati che volevo per nome come .[][].displayName.
  3. Ho assicurato che i miei campi desiderati erano auto-associati restituendoli come oggetti array separati come [.[][].displayName], [.[][].value]
  4. E quindi reindirizzato quegli oggetti alla join(", ")funzione per unirli come entità separate.

In verità, fare [.field]è solo un altro modo per farlo, map(.field)ma questo è un po 'più specifico in quanto specifica il livello di profondità per il recupero dei dati desiderati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.