Wie kann mit jq eine beliebige JSON-Codierung eines Arrays flacher Objekte in CSV konvertiert werden?
Auf dieser Website gibt es zahlreiche Fragen und Antworten, die sich auf bestimmte Datenmodelle beziehen, die die Felder hart codieren. Die Antworten auf diese Frage sollten jedoch bei jedem JSON funktionieren, mit der einzigen Einschränkung, dass es sich um ein Array von Objekten mit skalaren Eigenschaften handelt (keine tiefen / komplexen / Unterobjekte, da das Abflachen dieser eine andere Frage ist). Das Ergebnis sollte eine Kopfzeile mit den Feldnamen enthalten. Antworten, die die Feldreihenfolge des ersten Objekts beibehalten, werden bevorzugt, dies ist jedoch keine Voraussetzung. Die Ergebnisse können alle Zellen in doppelte Anführungszeichen oder nur diejenigen einschließen, die in Anführungszeichen gesetzt werden müssen (z. B. 'a, b').
Beispiele
Eingang:
[ {"code": "NSW", "name": "New South Wales", "level":"state", "country": "AU"}, {"code": "AB", "name": "Alberta", "level":"province", "country": "CA"}, {"code": "ABD", "name": "Aberdeenshire", "level":"council area", "country": "GB"}, {"code": "AK", "name": "Alaska", "level":"state", "country": "US"} ]
Mögliche Ausgabe:
code,name,level,country NSW,New South Wales,state,AU AB,Alberta,province,CA ABD,Aberdeenshire,council area,GB AK,Alaska,state,US
Mögliche Ausgabe:
"code","name","level","country" "NSW","New South Wales","state","AU" "AB","Alberta","province","CA" "ABD","Aberdeenshire","council area","GB" "AK","Alaska","state","US"
Eingang:
[ {"name": "bang", "value": "!", "level": 0}, {"name": "letters", "value": "a,b,c", "level": 0}, {"name": "letters", "value": "x,y,z", "level": 1}, {"name": "bang", "value": "\"!\"", "level": 1} ]
Mögliche Ausgabe:
name,value,level bang,!,0 letters,"a,b,c",0 letters,"x,y,z",1 bang,"""!""",0
Mögliche Ausgabe:
"name","value","level" "bang","!","0" "letters","a,b,c","0" "letters","x,y,z","1" "bang","""!""","1"
json2csv
unter stackoverflow.com/questions/57242240/…Antworten:
Rufen Sie zunächst ein Array ab, das alle verschiedenen Objekteigenschaftsnamen in Ihrer Objektarray-Eingabe enthält. Dies sind die Spalten Ihrer CSV:
Ordnen Sie dann für jedes Objekt in der Objektarray-Eingabe die erhaltenen Spaltennamen den entsprechenden Eigenschaften im Objekt zu. Dies sind die Zeilen Ihrer CSV.
Fügen Sie abschließend die Spaltennamen als Überschrift für die CSV vor die Zeilen ein und übergeben Sie den resultierenden Zeilenstrom an den
@csv
Filter.Jetzt alle zusammen. Denken Sie daran, das
-r
Flag zu verwenden, um das Ergebnis als Rohzeichenfolge zu erhalten:quelle
$rows
Variablenzuweisung(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
$rows
dies keiner Variablen zugeordnet werden muss; Ich dachte nur, die Zuordnung zu einer Variablen würde die Erklärung schöner machen.Die Dünne
oder:
Die Details
Beiseite
Das Beschreiben der Details ist schwierig, da jq streamorientiert ist, was bedeutet, dass es mit einer Folge von JSON-Daten und nicht mit einem einzelnen Wert arbeitet. Der Eingabe-JSON-Stream wird in einen internen Typ konvertiert, der durch die Filter geleitet und am Ende des Programms in einem Ausgabestream codiert wird. Der interne Typ wird nicht von JSON modelliert und existiert nicht als benannter Typ. Dies lässt sich am einfachsten anhand der Ausgabe eines Bare-Index (
.[]
) oder des Komma-Operators demonstrieren (die direkte Überprüfung könnte mit einem Debugger erfolgen, dies würde sich jedoch eher auf die internen Datentypen von jq als auf die konzeptionellen Datentypen hinter JSON beziehen). .Beachten Sie, dass die Ausgabe kein Array ist (was wäre
["a", "b"]
). Die kompakte Ausgabe (die-c
Option) zeigt, dass jedes Array-Element (oder Argument für den,
Filter) zu einem separaten Objekt in der Ausgabe wird (jedes befindet sich in einer separaten Zeile).Ein Stream ähnelt einer JSON- Sequenz, verwendet jedoch bei der Codierung Zeilenumbrüche anstelle von RS als Ausgabetrennzeichen. Folglich wird dieser interne Typ in dieser Antwort mit dem Oberbegriff "Sequenz" bezeichnet, wobei "Stream" für die codierte Eingabe und Ausgabe reserviert ist.
Filter konstruieren
Die Schlüssel des ersten Objekts können extrahiert werden mit:
Die Schlüssel werden im Allgemeinen in ihrer ursprünglichen Reihenfolge aufbewahrt, die genaue Reihenfolge wird jedoch nicht garantiert. Folglich müssen sie zum Indizieren der Objekte verwendet werden, um die Werte in derselben Reihenfolge zu erhalten. Dadurch wird auch verhindert, dass sich Werte in den falschen Spalten befinden, wenn einige Objekte eine andere Schlüsselreihenfolge haben.
Damit beide die Schlüssel als erste Zeile ausgeben und für die Indizierung verfügbar machen, werden sie in einer Variablen gespeichert. Die nächste Stufe der Pipeline verweist dann auf diese Variable und verwendet den Kommaoperator, um den Header dem Ausgabestream voranzustellen.
Der Ausdruck nach dem Komma ist ein wenig kompliziert. Der Indexoperator für ein Objekt kann eine Folge von Zeichenfolgen (z. B.
"name", "value"
) annehmen und eine Folge von Eigenschaftswerten für diese Zeichenfolgen zurückgeben.$keys
ist ein Array, keine Sequenz, wird also[]
angewendet, um es in eine Sequenz zu konvertieren.die dann weitergegeben werden kann
.[]
Auch dies erzeugt eine Sequenz, sodass der Array-Konstruktor verwendet wird, um sie in ein Array zu konvertieren.
Dieser Ausdruck soll auf ein einzelnes Objekt angewendet werden.
map()
wird verwendet, um es auf alle Objekte im äußeren Array anzuwenden:Zuletzt wird dies für diese Phase in eine Sequenz konvertiert, sodass jedes Element zu einer separaten Zeile in der Ausgabe wird.
Warum die Sequenz in einem Array bündeln,
map
um sie nur außerhalb zu entbündeln?map
erzeugt ein Array;.[ $keys[] ]
erzeugt eine Sequenz. Das Anwendenmap
auf die Sequenz von.[ $keys[] ]
würde ein Array von Wertesequenzen erzeugen. Da Sequenzen jedoch kein JSON-Typ sind, erhalten Sie stattdessen ein abgeflachtes Array, das alle Werte enthält.Die Werte von jedem Objekt müssen getrennt gehalten werden, damit sie in der endgültigen Ausgabe zu getrennten Zeilen werden.
Schließlich wird die Sequenz durch den
@csv
Formatierer geleitet.Wechseln
Die Elemente können eher spät als früh getrennt werden. Anstatt den Komma-Operator zum Abrufen einer Sequenz zu verwenden (indem eine Sequenz als rechter Operand übergeben wird), kann die Header-Sequenz (
$keys
) in ein Array eingeschlossen und+
zum Anhängen des Wertearrays verwendet werden. Dies muss noch in eine Sequenz konvertiert werden, bevor es an übergeben wird@csv
.quelle
keys_unsorted
anstelle von verwendenkeys
, um die Schlüsselreihenfolge vom ersten Objekt beizubehalten?[{"a":1,"b":2,"c":3}]
.Der folgende Filter unterscheidet sich geringfügig darin, dass sichergestellt wird, dass jeder Wert in eine Zeichenfolge konvertiert wird. (Hinweis: Verwenden Sie jq 1.5+)
Filter:
filter.jq
quelle
unique
ist sowieso sortiert,unique|sort
kann also vereinfacht werdenunique
.-r
Option aktiviert werden . Andernfalls werden alle Anführungszeichen"
extra maskiert, was keine gültige CSV ist.Ich habe eine Funktion erstellt, die ein Array von Objekten oder Arrays mit Headern an csv ausgibt. Die Spalten würden in der Reihenfolge der Überschriften sein.
Sie könnten es also so verwenden:
quelle
Diese Variante des Santiago-Programms ist ebenfalls sicher, stellt jedoch sicher, dass die Schlüsselnamen im ersten Objekt als erste Spaltenüberschriften in derselben Reihenfolge verwendet werden, in der sie in diesem Objekt angezeigt werden:
quelle