CSV-zu-JSON-Konvertierungspipelines in Bash bauen
AI generated
$_
#!/
Bash · CSV · JSON · Datenpipelines
CSV-zu-JSON-Konvertierungspipelines in Bash bauen
von der Rohdatei zum validierten API-Payload

Exportierte CSV-Dateien aus ERP-Systemen, Warenwirtschaften oder alten Datenbanken müssen fast immer als JSON in moderne APIs importiert werden. Eine CSV zu JSON Pipeline aus mlr, jq und Bash-Glue-Code erledigt Typkonvertierung, Verschachtelung und Validierung ohne eine einzige Zeile Python.

18 Min. Lesezeit mlr · jq · awk · Typkonvertierung · Validierung Bash 4.x · 5.x · Linux · macOS

1. Warum CSV zu JSON in Bash und nicht in Python

Eine CSV zu JSON Konvertierung klingt nach einer Aufgabe, für die man reflexartig zu Python mit pandas greift. In der Praxis ist das für viele Anwendungsfälle deutlich überdimensioniert: Ein Cronjob, der einmal pro Nacht einen CSV-Export aus dem ERP-System abholt und als JSON an eine REST-API überträgt, braucht keine 200 Megabyte an Python-Abhängigkeiten. Die Kombination aus mlr (Miller), jq und Bash-Glue-Code erledigt dieselbe CSV zu JSON Konvertierung mit einem einzigen, sofort ausführbaren Skript.

Der Grund, warum diese Pipeline speziell in Bash sinnvoll ist, liegt in der Natur der Aufgabe: Der Datenfluss ist linear, von einer Datei zu einer anderen, mit ein paar Transformationsschritten dazwischen. Genau dafür sind Unix-Pipes gebaut. Eine CSV zu JSON Konvertierung in Bash bedeutet in der Praxis eine Kette aus zwei oder drei spezialisierten Tools, jedes für seinen Teil der Aufgabe optimiert, statt eines monolithischen Skripts in einer allgemeinen Programmiersprache.

Dieser Artikel zeigt, wie eine produktionsreife CSV zu JSON Konvertierung in Bash aufgebaut wird: von der einfachen Grundkonvertierung über Typkonvertierung und Verschachtelung bis zur Validierung und dem sicheren Umgang mit fehlerhaften Zeilen in großen Dateien.

2. Grundlagen: mlr als CSV-zu-JSON-Konverter

mlr (Miller) ist das zentrale Werkzeug für jede CSV zu JSON Konvertierung in der Shell, weil es CSV, TSV und JSON nativ versteht und zwischen den Formaten konvertiert, ohne dass man eine eigene Parsing-Logik schreiben muss. Der Grundaufruf mlr --icsv --ojson cat datei.csv liest CSV ein und gibt ein JSON-Array aus, wobei jede Zeile zu einem Objekt wird und die Kopfzeile automatisch als Schlüssel verwendet wird. Das ersetzt handgeschriebenes awk, das bei Kommas innerhalb von quotierten Feldern schnell an seine Grenzen stößt.

Der entscheidende Vorteil von mlr gegenüber einem naiven awk -F,-Ansatz ist die korrekte Behandlung von RFC-4180-konformem CSV: Felder mit Kommas, die in Anführungszeichen stehen, werden korrekt erkannt, ebenso wie escapte Anführungszeichen innerhalb eines Feldes. Wer eine CSV zu JSON Konvertierung mit reinem awk -F, baut, produziert bei jedem Feld, das ein Komma enthält, kaputte Ausgaben, ohne dass das sofort auffällt.


#!/usr/bin/env bash
# csv-to-json.sh — convert CSV export to a JSON array with Miller
set -euo pipefail

readonly INPUT_CSV="${1:?Usage: csv-to-json.sh <input.csv>}"
readonly OUTPUT_JSON="${INPUT_CSV%.csv}.json"

# --icsv: input is CSV, --ojson: output is a JSON array
# --quote-original preserves quoting exactly as found in the source file
mlr --icsv --ojson cat "$INPUT_CSV" > "$OUTPUT_JSON"

echo "Converted $(wc -l < "$INPUT_CSV") rows to $OUTPUT_JSON"

# Quick sanity check: is the result valid JSON?
jq empty "$OUTPUT_JSON" || { echo "[ERROR] Invalid JSON output" >&2; exit 1; }

Dieses Grundgerüst reicht für einfache, flache CSV-Dateien bereits aus. Sobald aber Zahlen als Strings ausgegeben werden, wo eigentlich Integer oder Float erwartet werden, wird eine weitere Verarbeitungsstufe nötig, die im nächsten Abschnitt behandelt wird. Das ist der häufigste Stolperstein bei jeder CSV zu JSON Konvertierung: CSV kennt grundsätzlich keine Datentypen, jedes Feld ist zunächst ein String.

3. Typkonvertierung: Zahlen, Booleans und Nullwerte korrekt behandeln

Eine naive CSV zu JSON Konvertierung gibt jeden Wert als JSON-String aus, auch Zahlen und Booleans. Für eine API, die "price": "19.99" statt "price": 19.99 erwartet, führt das zu Validierungsfehlern oder stillen Falschinterpretationen auf der Empfängerseite. mlr erkennt Zahlen bei der Konvertierung nach JSON standardmäßig zwar oft automatisch, aber bei gemischten Spalten oder führenden Nullen (zum Beispiel Postleitzahlen) ist explizite Kontrolle nötig.

Die robuste Lösung ist, das Typmapping explizit mit jq nach der Grundkonvertierung anzuwenden. Mit tonumber werden Strings zu Zahlen, mit einer bedingten Ausdrucksweise werden Textwerte wie "true" und "false" zu echten Booleans. Für Felder, die leer sein können, ist wichtig, einen leeren String gezielt zu null zu konvertieren, statt ihn als leeren String im JSON zu belassen, weil viele APIs zwischen "Feld nicht gesetzt" und "Feld ist ein leerer String" unterscheiden.


#!/usr/bin/env bash
# convert-typed.sh — CSV to JSON with explicit type coercion via jq
set -euo pipefail

readonly INPUT_CSV="${1:?Usage: convert-typed.sh <input.csv>}"

mlr --icsv --ojson cat "$INPUT_CSV" | jq '
  map(
    .price      |= (if . == "" then null else (. | tonumber) end)
  | .quantity   |= (if . == "" then null else (. | tonumber) end)
  | .is_active  |= (. == "true" or . == "1")
  | .postal_code |= tostring
  )
' > "${INPUT_CSV%.csv}.typed.json"

echo "Typed conversion complete"

Das Feld postal_code wird bewusst mit tostring zurück in einen String erzwungen, weil führende Nullen in Postleitzahlen bei Konvertierung zu einer Zahl verloren gingen. Diese Art expliziter, feldweiser Typregel ist der Kern einer robusten CSV zu JSON Konvertierung: Jedes Feld bekommt eine bewusste Entscheidung, statt sich auf automatische Heuristiken zu verlassen, die bei Edge Cases wie führenden Nullen oder gemischten Datentypen scheitern.

4. Verschachtelte JSON-Strukturen aus flachen CSV-Spalten bauen

CSV kennt nur flache Tabellen, moderne APIs erwarten aber oft verschachtelte Objekte. Eine typische Anforderung bei einer CSV zu JSON Konvertierung: Spalten wie address_street, address_city und address_zip sollen zu einem verschachtelten address-Objekt zusammengeführt werden, statt als drei separate Top-Level-Felder im JSON zu erscheinen. jq übernimmt diese Umformung elegant mit Objektkonstruktion direkt im Filterausdruck.

Eine zweite häufige Verschachtelung entsteht, wenn mehrere CSV-Zeilen zu einem einzigen JSON-Objekt mit einem Array-Feld zusammengefasst werden müssen, etwa mehrere Bestellpositionen zu einer Bestellung mit einem items-Array. Dafür gruppiert jq mit group_by nach einem gemeinsamen Schlüssel wie order_id und baut pro Gruppe ein Objekt mit dem verschachtelten Array.


#!/usr/bin/env bash
set -euo pipefail

# Flatten address_* columns into a nested address object
mlr --icsv --ojson cat customers.csv | jq '
  map({
    id: .customer_id,
    name: .full_name,
    address: {
      street: .address_street,
      city: .address_city,
      zip: .address_zip
    }
  })
' > customers.nested.json

# Group order line items into a single order object with an items array
mlr --icsv --ojson cat order_lines.csv | jq '
  group_by(.order_id) | map({
    order_id: .[0].order_id,
    customer: .[0].customer_name,
    items: map({sku: .sku, quantity: (.quantity | tonumber)})
  })
' > orders.nested.json

Diese Art der Verschachtelung ist der Punkt, an dem eine CSV zu JSON Konvertierung deutlich über eine reine Formatumwandlung hinausgeht und beginnt, echte Datenmodellierung zu betreiben. Die Entscheidung, welche Spalten in ein Unterobjekt gehören, sollte immer aus dem Zielschema der empfangenden API abgeleitet werden, nicht aus der zufälligen Spaltenreihenfolge in der CSV-Quelle.

5. jq zur Nachbearbeitung und API-Formatierung einsetzen

Nach der Grundkonvertierung und Typisierung bleibt oft noch ein letzter Schritt: Das JSON muss in das genaue Payload-Format der Ziel-API gebracht werden. Viele REST-APIs erwarten kein rohes Array, sondern ein Wrapper-Objekt wie {"data": [...], "meta": {"count": N}}. Für eine CSV zu JSON Konvertierung, die direkt für einen API-Import genutzt wird, gehört dieser letzte Formatierungsschritt zwingend dazu, sonst schlägt der Import mit einem Schema-Fehler fehl.

jq baut diesen Wrapper mit wenigen Zeilen: {data: ., meta: {count: length, generated_at: now}} erzeugt aus dem rohen Array genau die Struktur, die viele Bulk-Import-Endpunkte erwarten. Die Funktion now liefert einen Unix-Zeitstempel, der bei Bedarf mit todate in ein ISO-8601-Format umgewandelt wird, was von den meisten APIs bevorzugt wird.


#!/usr/bin/env bash
set -euo pipefail

mlr --icsv --ojson cat products.csv | jq '{
  data: .,
  meta: {
    count: length,
    generated_at: (now | todate),
    source: "products.csv"
  }
}' > products.payload.json

echo "Payload ready: $(jq '.meta.count' products.payload.json) records"

6. Validierung vor dem Import: Schema-Prüfung mit jq

Eine CSV zu JSON Konvertierung, die ungeprüft in eine API geschickt wird, führt bei fehlenden Pflichtfeldern zu einem abgebrochenen Bulk-Import mitten in der Verarbeitung, was besonders bei nicht-transaktionalen APIs zu inkonsistenten Datenständen führt. Vor dem eigentlichen Versand lohnt sich deshalb eine Validierungsstufe, die mit jq prüft, ob jedes Objekt die erwarteten Pflichtfelder enthält und ob die Werte plausibel sind.

Der Ansatz: Ein jq-Filter, der alle Objekte herausfiltert, denen ein Pflichtfeld fehlt oder deren Wert außerhalb eines plausiblen Bereichs liegt, und diese als separate Fehlerliste ausgibt. Nur wenn diese Fehlerliste leer ist, geht die CSV zu JSON Konvertierung in den nächsten Schritt. Diese Prüfung ist deutlich günstiger als ein fehlgeschlagener API-Import mit hunderten Datensätzen, von denen die Hälfte bereits verarbeitet wurde.


#!/usr/bin/env bash
set -euo pipefail

readonly JSON_FILE="products.typed.json"

# Find records missing required fields or with implausible values
invalid_records="$(jq '
  [.[] | select(
    (.sku == null or .sku == "") or
    (.price == null or .price < 0)
  )]
' "$JSON_FILE")"

invalid_count="$(echo "$invalid_records" | jq 'length')"

if (( invalid_count > 0 )); then
  echo "[ERROR] $invalid_count invalid records found:" >&2
  echo "$invalid_records" | jq -c '.[]' >&2
  exit 1
fi

echo "All records valid, proceeding with import"

7. Große CSV-Dateien in Batches konvertieren und hochladen

Ein CSV-Export mit hunderttausend Zeilen als einzelnes JSON-Array an eine API zu schicken, führt regelmäßig zu Timeouts oder Speicherproblemen auf Serverseite. Für eine CSV zu JSON Konvertierung in dieser Größenordnung ist Batching Pflicht: Die Datei wird in Blöcke fester Größe aufgeteilt, jeder Block einzeln konvertiert und einzeln hochgeladen, mit Checkpointing zwischen den Batches.

mlr unterstützt mit split das Aufteilen großer CSV-Dateien in kleinere Blöcke, wobei die Kopfzeile automatisch in jedem Teil erhalten bleibt. Alternativ übernimmt ein Bash-Loop mit tail und head dieselbe Aufgabe manuell, was mehr Kontrolle über die Batch-Größe und den Fortschritt gibt.


#!/usr/bin/env bash
set -euo pipefail

readonly INPUT_CSV="large-export.csv"
readonly BATCH_SIZE=5000
readonly API_ENDPOINT="https://api.example.com/v1/products/bulk"

header="$(head -n1 "$INPUT_CSV")"
total_lines="$(($(wc -l < "$INPUT_CSV") - 1))"
batch_num=0

for (( offset = 1; offset <= total_lines; offset += BATCH_SIZE )); do
  batch_num=$(( batch_num + 1 ))
  batch_file="$(mktemp)"

  { echo "$header"; tail -n +$((offset + 1)) "$INPUT_CSV" | head -n "$BATCH_SIZE"; } > "$batch_file"

  json_payload="$(mlr --icsv --ojson cat "$batch_file")"

  curl -sf -X POST "$API_ENDPOINT" \
    -H "Content-Type: application/json" \
    -d "$json_payload" \
    -o "/var/log/import-batch-${batch_num}.json" \
    || { echo "[ERROR] Batch $batch_num failed" >&2; rm -f "$batch_file"; exit 1; }

  echo "[OK] Batch $batch_num uploaded (offset $offset)"
  rm -f "$batch_file"
done

8. Fehlerhafte Zeilen erkennen statt die Pipeline abzubrechen

Reale CSV-Exporte enthalten fast immer ein paar fehlerhafte Zeilen: eine falsch quotierte Spalte, eine abweichende Spaltenanzahl, ein Encoding-Problem. Eine CSV zu JSON Konvertierung, die bei der ersten defekten Zeile komplett abbricht, ist in der Produktion unpraktisch, weil ein einzelner fehlerhafter Datensatz dann die Verarbeitung von zehntausend korrekten Zeilen blockiert. Der bessere Ansatz ist, fehlerhafte Zeilen zu isolieren, zu protokollieren und die Verarbeitung mit den restlichen Zeilen fortzusetzen.

mlr gibt bei einer strukturell inkonsistenten Zeile (falsche Spaltenanzahl) eine Fehlermeldung auf stderr aus und bricht standardmäßig ab. Mit dem Flag --allow-ragged-csv-input toleriert mlr Zeilen mit abweichender Feldanzahl, indem fehlende Felder als leer und überzählige Felder mit generischen Schlüsselnamen aufgefüllt werden. Für eine produktionsreife CSV zu JSON Konvertierung kombiniert man das mit einer nachgelagerten Prüfung, die diese unvollständigen Zeilen erkennt und getrennt protokolliert.


#!/usr/bin/env bash
set -euo pipefail

readonly INPUT_CSV="messy-export.csv"
readonly GOOD_JSON="clean-records.json"
readonly BAD_LOG="rejected-rows.log"

# Tolerate ragged rows instead of aborting on the first structural error
raw_json="$(mlr --icsv --ojson --allow-ragged-csv-input cat "$INPUT_CSV")"

# Split into records with a valid sku vs. everything else
echo "$raw_json" | jq '[.[] | select(.sku != null and .sku != "")]' > "$GOOD_JSON"
echo "$raw_json" | jq -c '.[] | select(.sku == null or .sku == "")' > "$BAD_LOG"

rejected_count="$(wc -l < "$BAD_LOG")"
if (( rejected_count > 0 )); then
  echo "[WARN] $rejected_count rows rejected, logged to $BAD_LOG" >&2
fi

echo "Clean records: $(jq 'length' "$GOOD_JSON")"

Dieses Muster, gute und schlechte Datensätze zu trennen statt die gesamte CSV zu JSON Konvertierung abzubrechen, entspricht dem gängigen Dead-Letter-Queue-Prinzip aus der Datenverarbeitung: fehlerhafte Einheiten werden isoliert und später manuell nachbearbeitet, während der Großteil der Daten ungehindert weiterfließt.

9. Werkzeuge im Vergleich: mlr, jq, awk und Python

Die Wahl des richtigen Werkzeugs für eine CSV zu JSON Konvertierung hängt stark von der Komplexität der Transformation und der Größe der Datei ab. Für einfache, flache Konvertierungen ist mlr allein oft ausreichend. Sobald Typkonvertierung, Verschachtelung oder Validierung ins Spiel kommen, ergänzt jq die fehlenden Fähigkeiten.

Werkzeug Stärke Schwäche Empfohlen für
mlr (Miller) RFC-4180-CSV korrekt parsen Komplexe Verschachtelung umständlich Grundkonvertierung CSV/TSV/JSON
jq Beliebige JSON-Transformationen Kein natives CSV-Parsing Typisierung, Verschachtelung, Validierung
awk -F, Überall vorinstalliert, sehr schnell Bricht bei quotierten Kommas Simple, garantiert unquotierte CSV
Python + pandas Komplexe Business-Logik, Statistik Schwere Abhängigkeit, langsamer Start Datenanalyse statt reiner Konvertierung
mlr + jq kombiniert Beste Kombination aus beidem Zwei Tools statt einem lernen Produktionsreife Bash-Pipelines

In der Praxis zeigt sich: mlr allein deckt die Grundkonvertierung ab, jq allein kann kein CSV parsen, aber die Kombination beider Tools über eine Pipe deckt praktisch jeden Anwendungsfall einer CSV zu JSON Konvertierung ab, den man sonst mit deutlich mehr Aufwand in Python nachbauen würde.

Mironsoft

Shell-Automatisierung, Datenpipelines und API-Integrationen

CSV-Exporte, die zuverlässig in eure API-Landschaft fließen?

Wir bauen robuste CSV-zu-JSON-Pipelines mit Typkonvertierung, Validierung und Batch-Verarbeitung für eure Import- und Migrationsprozesse, ohne zusätzliche Runtime-Abhängigkeiten.

Pipeline-Design

mlr und jq kombiniert für Typkonvertierung und verschachtelte Strukturen

Validierung

Schema-Prüfung vor dem Import, damit Bulk-Imports nicht mittendrin scheitern

Batch-Verarbeitung

Große Exporte in kontrollierten Batches mit Checkpointing und Fehler-Log

10. Zusammenfassung

Eine solide CSV zu JSON Konvertierung in Bash kombiniert wenige, spezialisierte Werkzeuge zu einer robusten Pipeline: mlr übernimmt das korrekte Parsen von RFC-4180-CSV inklusive quotierter Kommas, jq übernimmt Typkonvertierung, Verschachtelung und Validierung. Die explizite Umwandlung von Strings zu Zahlen und Booleans verhindert Validierungsfehler auf API-Seite, verschachtelte Objekte und Arrays entstehen mit wenigen Zeilen jq-Filterlogik statt komplexer Programmierung.

Für große Dateien gehört Batching mit Checkpointing zum Standardrepertoire, damit ein Timeout oder ein API-Fehler nicht die gesamte Verarbeitung zunichtemacht. Fehlerhafte Zeilen sollten isoliert und protokolliert werden, statt die komplette CSV zu JSON Konvertierung abzubrechen. Diese Bausteine zusammen ergeben eine Pipeline, die in Produktionsumgebungen genauso zuverlässig läuft wie eine Python-Lösung, aber ohne zusätzliche Laufzeitabhängigkeiten auskommt.

CSV-zu-JSON-Konvertierungspipelines: Das Wichtigste auf einen Blick

Grundkonvertierung

mlr --icsv --ojson cat datei.csv parst RFC-4180-CSV korrekt, inklusive quotierter Kommas.

Typisierung

jq mit tonumber und bedingten Ausdrücken wandelt Strings gezielt in Zahlen und Booleans um.

Validierung

jq-Filter prüfen Pflichtfelder vor dem Import, um abgebrochene Bulk-Imports zu vermeiden.

Skalierung

Batching mit Checkpointing für große Dateien, fehlerhafte Zeilen isolieren statt abbrechen.

11. FAQ: CSV-zu-JSON-Konvertierungspipelines

1Warum reicht awk nicht?
awk teilt naiv am Komma. mlr parst RFC-4180-CSV korrekt, auch mit quotierten Kommas.
2CSV-Strings in Zahlen umwandeln?
jq mit tonumber, leere Felder vorher explizit zu null konvertieren, um Fehler zu vermeiden.
3Spalten zu verschachteltem Objekt?
jq mit direkter Objektkonstruktion im Filter fasst flache Spalten zu einem Unterobjekt zusammen.
4Zeilen zu Objekt mit Array gruppieren?
jq group_by nach gemeinsamem Schlüssel, dann map für ein Objekt mit items-Array pro Gruppe.
5Fehlerhafte Zeilen in großen Dateien?
--allow-ragged-csv-input toleriert abweichende Feldanzahl, jq trennt danach gültige von fehlerhaften Zeilen.
6Warum Batching bei großen Exporten?
Verhindert Timeouts und Speicherprobleme auf API-Seite, macht den Import wiederaufnehmbar.
7JSON vor Import validieren?
jq-Filter identifiziert Objekte ohne Pflichtfelder, Import wird bei Fehlern gestoppt.
8Wrapper-Objekt direkt in mlr?
Nein, jq übernimmt diesen Schritt nach der Grundkonvertierung mit data/meta-Struktur.
9Führende Null in Postleitzahlen?
Nur mit explizitem tostring erhalten, sonst entfernt automatische Zahlenerkennung die Null.
10Wann lohnt sich Python statt Bash?
Bei komplexer Business-Logik oder Statistik. Für reine Konvertierung reicht mlr plus jq.