Verwenden von jq zum Extrahieren von Werten und Formatieren in CSV

57

Ich habe die folgende JSON-Datei:

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

Ich hätte gerne eine CSV-Datei in diesem Format:

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

Ist dies nur mit möglich jq? Ich habe keine Programmierkenntnisse.

Kerim
quelle
1
Ich habe unten eine Antwort gegeben, aber jetzt schaue ich mir Ihre Frage genauer an und frage mich - wo soll der 6. WERT herkommen?
mikeserv
1
Related from SO: stackoverflow.com/questions/25558456/…
Anton Tarasenko
Siehe
phunehehe

Antworten:

49

jq hat einen Filter, @csv, um ein Array in einen CSV-String umzuwandeln. Dieser Filter berücksichtigt die meisten mit dem CSV-Format verbundenen Komplexitäten, beginnend mit Kommas, die in Felder eingebettet sind. (jq 1.5 hat einen ähnlichen Filter, @tsv, um Dateien mit durch Tabulatoren getrennten Werten zu generieren.)

Wenn die Überschriften und Werte garantiert alle frei von Kommas und doppelten Anführungszeichen sind, muss der @csv-Filter möglicherweise nicht verwendet werden. Andernfalls wäre es wahrscheinlich besser, es zu verwenden.

Wenn zum Beispiel der 'Firmenname' 'Smith, Smith und Smith' wäre und die anderen Werte wie unten gezeigt wären, würde das Aufrufen von jq mit der Option "-r" eine gültige CSV erzeugen:

$ 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"
Gipfel
quelle
3
Ich konnte etwas tun Karte (.) | @csv ', sehr praktisch! Danke
Flickerfly
3
In Ihrem Beispiel werden alle Anzeigenamen in die erste Zeile und alle Werte in die zweite Zeile eingefügt, anstatt eine Zeile pro Datensatz.
Brian Gordon
32

Ich ziehe es vor, jede Aufzeichnung in meiner CSV als Zeile zu kennzeichnen.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'
Silas Paul
quelle
2
Was ist, wenn .value eine Zahl ist? Ich erhalte die Fehlermeldung "Zeichenfolge und Nummer können nicht hinzugefügt werden"
Cos
2
@Cos so etwas wie .value|tostringstatt .valueim obigen Beispiel
matheeeny
4
@Cos, ich fand, dass Klammern erforderlich sind. (.value|tostring)
Ciscogambo
Verwenden Sie auch jq -r, um die Anführungszeichen zu streifen
Clay
30

Wenn Sie nur diese Datei angeben, können Sie Folgendes tun:

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

Der .Operator wählt ein Feld aus einem Objekt / Hash aus. Wir beginnen also mit .data, das das Array mit den Daten darin zurückgibt. Wir ordnen das Array dann zweimal zu, indem wir zuerst den displayName und dann den Wert auswählen, sodass wir zwei Arrays erhalten, die nur die Werte dieser Schlüssel enthalten. Für jedes Array verbinden wir die Elemente mit "," und bilden zwei Zeilen. Das -rArgument besagt jq, dass die resultierenden Zeichenfolgen nicht in Anführungszeichen gesetzt werden sollen.

Wenn Ihre eigentliche Datei länger ist (dh Einträge für mehr als eine Person enthält), benötigen Sie wahrscheinlich etwas Komplizierteres.

Steven D
quelle
Es funktioniert nicht für mich. In einem verwandten Thema funktioniert die Antwort stackoverflow.com/questions/32960857/… und ist sehr gut erklärt!
Herve
10

Ich habe es jqschwer gefunden , meinen Kopf herumzuwickeln. Hier ist etwas Rubin:

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

Der rubinrote JSON-Parser machte vor der schließenden Klammer einen Strich durch das Komma.

Glenn Jackman
quelle
2

Da hast du diesen getaggt pythonund nimmst den jsonDateinamen anx.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
quelle
1

Obwohl ich das letzte Komma in Ihrer Beispieleingabe entfernen musste, damit es funktioniert, weil ich mich jqbeschwerte, ein anderes Array-Element zu erwarten:

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

...hast mich...

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

So funktioniert es auf den Punkt gebracht:

  1. Ich bin mit dem leeren []Indexfeld und der .dotNotation zur dritten Ebene der Datenobjekte durchgegangen.
  2. Einmal tief genug spezifizierte ich die gewünschten Datenfelder mit Namen wie .[][].displayName.
  3. Ich versicherte, dass meine gewünschten Felder selbst zugeordnet waren, indem ich sie als separate Array-Objekte zurückgab, wie z [.[][].displayName], [.[][].value]
  4. Anschließend wurden diese Objekte an die join(", ")Funktion weitergeleitet, die als separate Entitäten verbunden werden soll.

In Wahrheit [.field]ist dies nur eine andere Möglichkeit, map(.field)aber dies ist insofern etwas spezifischer, als es die Tiefenebene zum Abrufen der gewünschten Daten angibt.

mikeserv
quelle