2015-04-16 16 views
10

Wenn Sie eine CSV-Datensatz wie dieses:CSV zu JSON mit jq

name, age, gender 
john, 20, male 
jane, 30, female 
bob, 25, male 

Können Sie diese:

[ {"name": "john", "age": 20, "gender: "male"}, 
    {"name": "jane", "age": 30, "gender: "female"}, 
    {"name": "bob", "age": 25, "gender: "male"} ] 

nur jq mit?

Ich fand this Artikel, der zeigt, was ich versuche zu tun, aber es verwendet eine 'manuelle' Zuordnung der Header-Felder zu den Werten. Ich brauche/will nicht die Headerfelder umbenennen und habe einige davon. Ich möchte auch nicht jedes Mal, wenn sich das Layout ändert, ein Skript/einen Befehl ändern müssen.

Ist es möglich, dynamisch die Header zu extrahieren und sie dann mit den Werten mit einem jq Einzeiler kombinieren?

+2

Was Sie fragen, keinen Sinn zu tun macht. 'jq' ist ein Werkzeug, das json als Eingabe verwendet und eine Ausgabe generiert. CSV ist nicht JSON. Sie können nicht erwarten, dass dieses Tool das verarbeitet, es ist nicht das, wofür es gemacht wurde. Sie müssen ein Tool oder eine andere Skriptsprache verwenden, die csv verarbeiten kann. –

+1

anstatt zu versuchen, 'jq' in diesem zu erzwingen, könnte es sehr gut mit einem fast trivialen Shell/Sed-Skript, z.B. basierend auf http://stackoverflow.com/questions/4286469/how-to-have-bash-parse-a-csv-file –

+0

Ich bin der Autor des Artikels, auf den das OP verweist. Während das Beispiel, das ich zur Verfügung gestellt habe, nützlich für Quick-n-Dirty-jq-Projekte sein kann, ist es nicht sehr robust. Es gibt einige nette Tools für die Arbeit mit CSV und ich würde stattdessen eine davon empfehlen, zum Beispiel: http://johnkerl.org/miller/doc/ (wie jq für CSV) oder dieses NPM-Paket https: // www. npmjs.com/package/csv2json oder dieses Juwel https://rubygems.org/gems/csv2json/versions/0.3.0 –

Antwort

5

hatte ich ein kleines Spiel und kam mit dieser. Aber ist es vielleicht nicht der beste Weg, und ich würde interessiert sein zu sehen, , was Ihre Versuche waren wie, denn schließlich, wenn wir beide bei einer Lösung kamen, bin ich mir sicher, es wäre doppelt so gut!

Aber ich würde von etwas anfangen wie:

true as $doHeaders 
| ./"\n" 
| map(./", ") 
| (if $doHeaders then .[0] else [range(0; (.[0] | length)) | tostring] end) as $headers 
| .[if $doHeaders then 1 else 0 end:][] 
| . as $values 
| keys 
| map({($headers[.]): $values[.]}) 

Working Example

Die Variable $doHeaders steuert, ob die oberste Zeile als Kopfzeile zu lesen. In Ihrem Fall wollen Sie es als wahr, aber ich habe es für zukünftige SO-Nutzer hinzugefügt und weil, nun, ich hatte heute ein ausgezeichnetes Frühstück und das Wetter ist schön, also warum nicht?

Kleine Erklärung:

1) ./"\n" Split Zeile ...

2) map(./", ") ... und Komma (Big Gotcha: In Ihrer Version, werden Sie wollen einen regulären Ausdruck verwenden, Basiert split, weil Sie so auf Kommas innerhalb Anführungszeichen teilen. Ich habe nur verwendet, weil es kurz ist, und das macht meine Lösung cool aussehen?)

3) if $doHeaders then... Hier erstellen wir ein Array von Strings Keys oder Zahlen abhängig von der Anzahl der Elemente in der ersten Reihe und ob die erste Zeile eine Kopfzeile ist

4) .[if $doHeaders then 1 else 0 end:] Ok, so schneidet die obere Linie, wenn es ein Kopf

5) map({($headers[.]): $values[.]}) Above wir gehen über jede Zeile in der ehemaligen csv aus, und setze die $values in eine Variable und die Schlüssel in eine Pipe. Dann konstruieren wir Ihr gewünschtes Objekt.

Natürlich sollten Sie ein paar regulären Ausdrücke verwenden, in dem gotchas zu füllen, aber ich hoffe, dass Sie beginnt auf dem Weg.

+0

upvoted für die bewundernswerte Mühe :-) –

+0

würde ich einfach wieder blinzeln, aber man benötigt mehr schreiben in einem Kommentar in diesen Tagen. – Tom

+0

Danke für die Mühe! Zugegeben, das ist eher eine Theorie als eine praktische Frage. Am Ende habe ich das in bash gemacht, aber ich habe darüber nachgedacht, ob es nur in jq gemacht werden kann, fragte ich. Das obige ist nah. Es gibt '[{" name ":" john "}, {" age ": 20}, {" gender ":" männlich "} ...' – jpl1079

11

Kurz gesagt - ja!

jq ist oft gut geeignet, um Text Gerangel, und dies gilt insbesondere für Versionen mit regex Unterstützung. Bei der Regex-Unterstützung ist zum Beispiel das Trimmen, das von der gegebenen Problembeschreibung benötigt wird, trivial.

Seit jq 1.5rc1 enthält Regex-Unterstützung und ist seit dem 1. Januar 2015 verfügbar, das folgende Programm geht von einer Version von jq 1.5 aus; Wenn Sie möchten, dass es mit jq 1.4 funktioniert, dann sehen Sie sich die zwei "For jq 1.4" Kommentare an.

Bitte beachten Sie auch, dass dieses Programm nicht CSV in all seiner Allgemeinheit und Komplexität behandelt. (Für einen ähnlichen Ansatz, den CSV generell nicht handhaben, sehen https://github.com/stedolan/jq/wiki/Cookbook#convert-a-csv-file-with-headers-to-json)

# objectify/1 takes an array of string values as inputs, converts 
# numeric values to numbers, and packages the results into an object 
# with keys specified by the "headers" array 
def objectify(headers): 
    # For jq 1.4, replace the following line by: def tonumberq: .; 
    def tonumberq: tonumber? // .; 
    . as $in 
    | reduce range(0; headers|length) as $i ({}; .[headers[$i]] = ($in[$i] | tonumberq)); 

def csv2table: 
    # For jq 1.4, replace the following line by: def trim: .; 
    def trim: sub("^ +";"") | sub(" +$";""); 
    split("\n") | map(split(",") | map(trim)); 

def csv2json: 
    csv2table 
    | .[0] as $headers 
    | reduce (.[1:][] | select(length > 0)) as $row 
     ([]; . + [ $row|objectify($headers) ]); 

csv2json 

Beispiel (unter der Annahme csv.csv ist die CSV-Textdatei angegeben):

$ jq -R -s -f csv2json.jq csv.csv 
[ 
    { 
    "name": "john", 
    "age": 20, 
    "gender": "male" 
    }, 
    { 
    "name": "jane", 
    "age": 30, 
    "gender": "female" 
    }, 
    { 
    "name": "bob", 
    "age": 25, 
    "gender": "male" 
    } 
] 
+0

Ich bestätige, dass dies wirklich funktioniert (getestet mit jq Version 1.5). Schöne Präsentation der modularisierten Lösung. Jedenfalls werde ich einige Zeit brauchen, um alle Konstruktionen zu verstehen. Nett. –

+0

@peak Wie kann das erweitert werden, um "" in "" zu "" "zu konvertieren und FALSE in" false "und TRUE in" true "umzuwandeln? – philk

+0

@peak Ich habe es geschafft, die booleschen Werte selbst zu konvertieren, aber die doppelten Anführungszeichen in Strings stören mich immer noch. Auch da, wo ich hätte zu verlängern 'csv2json' def nur zu konvertieren Zeilen whos' Marke‘Wert‚Mybrand‘? – philk

1

Hier ist eine Lösung, die Sie übernimmt Führen Sie jq mit den Optionen -s und -R aus.

[ 
    [            
    split("\n")[]     # transform csv input into array 
    | split(", ")     # where first element has key names 
    | select(length==3)    # and other elements have values 
    ]         
    | {h:.[0], v:.[1:][]}   # {h:[keys], v:[values]} 
    | [.h, (.v|map(tonumber?//.))] # [ [keys], [values] ] 
    | [ transpose[]     # [ [key,value], [key,value], ... ] 
     | {key:.[0], value:.[1]}  # [ {"key":key, "value":value}, ... ] 
    ] 
    | from_entries     # { key:value, key:value, ... } 
] 

Probelauf:

jq -s -R -f filter.jq data.csv 

Beispielausgabe

[ 
    { 
    "name": "john", 
    "age": 20, 
    "gender": "male" 
    }, 
    { 
    "name": "jane", 
    "age": 30, 
    "gender": "female" 
    }, 
    { 
    "name": "bob", 
    "age": 25, 
    "gender": "male" 
    } 
] 
Verwandte Themen