JSONL in Go: bufio, json.Decoder & Nebenläufigkeit

Eine vollständige Anleitung zur Arbeit mit JSONL (JSON Lines)-Dateien in Go. Lernen Sie das Lesen, Schreiben, Streamen und nebenläufige Verarbeiten von JSONL-Daten mit den Standardbibliotheken bufio, encoding/json und Goroutines.

Letzte Aktualisierung: Februar 2026

Warum Go für JSONL?

Go ist eine ausgezeichnete Wahl für die Verarbeitung von JSONL-Dateien, insbesondere wenn Leistung und Nebenläufigkeit wichtig sind. Die Standardbibliothek bietet alles, was Sie brauchen: bufio für effiziente zeilenweise I/O, encoding/json für Parsing und Serialisierung sowie Goroutines für parallele Verarbeitung. Es sind keine Drittanbieter-Abhängigkeiten erforderlich. Die kompilierte Natur von Go bedeutet, dass Ihre JSONL-Pipeline deutlich schneller läuft als vergleichbare Python- oder Node.js-Skripte und oft Millionen von Zeilen pro Sekunde auf bescheidener Hardware verarbeitet.

JSONL (JSON Lines) speichert ein JSON-Objekt pro Zeile und ist damit eine natürliche Ergänzung zu Go's Streaming-I/O-Modell. Anstatt eine gesamte Datei in den Speicher zu laden, können Sie Datensätze einzeln mit einem Scanner oder Decoder lesen und verarbeiten. In Kombination mit Go's leichtgewichtigen Goroutines und Channels können Sie nebenläufige JSONL-Pipelines erstellen, die alle verfügbaren CPU-Kerne auslasten. In dieser Anleitung lernen Sie, wie Sie JSONL mit bufio.Scanner lesen, mit json.Decoder streamen, JSONL effizient schreiben, Datensätze nebenläufig verarbeiten und Fehler robust behandeln.

JSONL-Dateien mit bufio.Scanner lesen

Der einfachste Weg, JSONL in Go zu lesen, ist mit bufio.Scanner. Er liest eine Datei Zeile für Zeile, und Sie parsen jede Zeile mit json.Unmarshal. Dieser Ansatz gibt Ihnen volle Kontrolle über Puffergrößen und Fehlerbehandlung.

Öffnen Sie die Datei, erstellen Sie einen Scanner und iterieren Sie Zeile für Zeile. Jede Zeile wird mit json.Unmarshal in eine Map oder ein Struct geparst. Dies hält den Speicherverbrauch proportional zu einem einzelnen Datensatz, nicht zur gesamten Datei.

Einfaches Lesen mit bufio.Scanner
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("data.jsonl")
if err != nil {
log.Fatal(err)
}
defer file.Close()
var records []map[string]any
scanner := bufio.NewScanner(file)
// Puffer für Zeilen länger als 64KB vergrößern
scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024)
for scanner.Scan() {
var record map[string]any
if err := json.Unmarshal(scanner.Bytes(), &record); err != nil {
log.Printf("skipping invalid JSON: %v", err)
continue
}
records = append(records, record)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("Loaded %d records\n", len(records))
}

Für Produktionscode sollten Sie JSONL-Datensätze in typisierte Go-Structs parsen, anstatt generische Maps zu verwenden. Dies bietet Typsicherheit zur Kompilierzeit, bessere Leistung und klareren Code. Definieren Sie Ihr Struct mit json-Tags, um das Feld-Mapping zu steuern.

Parsen in typisierte Structs
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age,omitempty"`
}
func readUsers(path string) ([]User, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("open file: %w", err)
}
defer file.Close()
var users []User
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
var u User
if err := json.Unmarshal(scanner.Bytes(), &u); err != nil {
return nil, fmt.Errorf("line %d: %w", lineNum, err)
}
users = append(users, u)
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("scanner error: %w", err)
}
return users, nil
}
func main() {
users, err := readUsers("users.jsonl")
if err != nil {
log.Fatal(err)
}
for _, u := range users {
fmt.Printf("%s (%s)\n", u.Name, u.Email)
}
}

Streaming mit json.Decoder

Für leistungsstarkes Streaming liest json.Decoder direkt von einem io.Reader ohne zwischengeschaltete Zeilentrennung. Er übernimmt das Buffering intern und ist der empfohlene Ansatz für große JSONL-Dateien oder Netzwerk-Streams, bei denen Sie minimale Allokationen wünschen.

json.NewDecoder umhüllt jeden io.Reader und dekodiert JSON-Werte sequenziell. Jeder Aufruf von Decode liest genau ein JSON-Objekt. Wenn der Stream endet, gibt er io.EOF zurück. Dies ist effizienter als bufio.Scanner + json.Unmarshal, da das Kopieren jeder Zeile in einen separaten Puffer vermieden wird.

Streaming mit json.Decoder
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
)
type LogEntry struct {
Timestamp string `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Service string `json:"service"`
}
func processJSONLStream(path string) error {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("open: %w", err)
}
defer file.Close()
decoder := json.NewDecoder(file)
var count int
for {
var entry LogEntry
err := decoder.Decode(&entry)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return fmt.Errorf("decode record %d: %w", count+1, err)
}
count++
// Jeden Datensatz beim Dekodieren verarbeiten
if entry.Level == "ERROR" {
fmt.Printf("[%s] %s: %s\n",
entry.Timestamp, entry.Service, entry.Message)
}
}
fmt.Printf("Processed %d log entries\n", count)
return nil
}
func main() {
if err := processJSONLStream("logs.jsonl"); err != nil {
log.Fatal(err)
}
}

JSONL-Dateien in Go schreiben

Das Schreiben von JSONL in Go ist unkompliziert: Serialisieren Sie jeden Datensatz mit json.Marshal, hängen Sie ein Newline-Byte an und schreiben Sie in eine Datei. Für beste Leistung umhüllen Sie den File-Writer mit einem bufio.Writer, um Systemaufrufe zu reduzieren.

Verwenden Sie json.Marshal, um jeden Datensatz in JSON-Bytes zu serialisieren, und schreiben Sie diese gefolgt von einem Newline. Dieser Ansatz ist einfach und funktioniert gut für kleine bis mittlere Dateien.

Einfaches Schreiben mit json.Marshal
package main
import (
"encoding/json"
"fmt"
"log"
"os"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
func main() {
products := []Product{
{ID: 1, Name: "Laptop", Price: 999.99},
{ID: 2, Name: "Mouse", Price: 29.99},
{ID: 3, Name: "Keyboard", Price: 79.99},
}
file, err := os.Create("products.jsonl")
if err != nil {
log.Fatal(err)
}
defer file.Close()
for _, p := range products {
data, err := json.Marshal(p)
if err != nil {
log.Printf("skip marshal error: %v", err)
continue
}
data = append(data, '\n')
if _, err := file.Write(data); err != nil {
log.Fatal(err)
}
}
fmt.Printf("Wrote %d records to products.jsonl\n", len(products))
}

Für das Schreiben mit hohem Durchsatz verwenden Sie json.NewEncoder mit einem bufio.Writer. Der Encoder hängt nach jedem Encode-Aufruf automatisch ein Newline an, und der gepufferte Writer fasst kleine Schreibvorgänge zu größeren Systemaufrufen zusammen. Rufen Sie immer Flush auf, bevor Sie die Datei schließen.

Gepuffertes Schreiben mit json.Encoder
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
)
type Event struct {
Type string `json:"type"`
Timestamp int64 `json:"timestamp"`
Payload map[string]any `json:"payload"`
}
func writeEvents(path string, events []Event) error {
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("create file: %w", err)
}
defer file.Close()
writer := bufio.NewWriter(file)
encoder := json.NewEncoder(writer)
// HTML-Escaping für sauberere Ausgabe deaktivieren
encoder.SetEscapeHTML(false)
for i, event := range events {
if err := encoder.Encode(event); err != nil {
return fmt.Errorf("encode event %d: %w", i, err)
}
}
// Gepufferte Daten in die Datei schreiben
if err := writer.Flush(); err != nil {
return fmt.Errorf("flush: %w", err)
}
fmt.Printf("Wrote %d events to %s\n", len(events), path)
return nil
}
func main() {
events := []Event{
{Type: "click", Timestamp: 1700000001, Payload: map[string]any{"x": 120, "y": 450}},
{Type: "view", Timestamp: 1700000002, Payload: map[string]any{"page": "/home"}},
}
if err := writeEvents("events.jsonl", events); err != nil {
log.Fatal(err)
}
}

Nebenläufige JSONL-Verarbeitung mit Goroutines

Go's Goroutines und Channels machen es einfach, nebenläufige JSONL-Pipelines zu erstellen. Das Fan-Out-Muster liest Datensätze aus einer Datei, verteilt sie auf mehrere Worker-Goroutines und sammelt Ergebnisse über einen Channel. Dies ist ideal für CPU-intensive Transformationen großer JSONL-Dateien.

Nebenläufige JSONL-Verarbeitung mit Goroutines
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
"runtime"
"sync"
)
type Record struct {
ID int `json:"id"`
Data map[string]any `json:"data"`
}
type Result struct {
ID int `json:"id"`
Processed bool `json:"processed"`
Score int `json:"score"`
}
func process(r Record) Result {
// CPU-intensive Arbeit simulieren
score := len(r.Data) * r.ID
return Result{ID: r.ID, Processed: true, Score: score}
}
func main() {
file, err := os.Open("records.jsonl")
if err != nil {
log.Fatal(err)
}
defer file.Close()
numWorkers := runtime.NumCPU()
jobs := make(chan Record, numWorkers*2)
results := make(chan Result, numWorkers*2)
// Worker starten
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for record := range jobs {
results <- process(record)
}
}()
}
// Results-Channel schließen wenn alle Worker fertig sind
go func() {
wg.Wait()
close(results)
}()
// Datei lesen und Datensätze an Worker senden
go func() {
scanner := bufio.NewScanner(file)
for scanner.Scan() {
var r Record
if err := json.Unmarshal(scanner.Bytes(), &r); err != nil {
log.Printf("skip invalid line: %v", err)
continue
}
jobs <- r
}
close(jobs)
}()
// Ergebnisse sammeln und schreiben
outFile, err := os.Create("results.jsonl")
if err != nil {
log.Fatal(err)
}
defer outFile.Close()
writer := bufio.NewWriter(outFile)
encoder := json.NewEncoder(writer)
var count int
for result := range results {
if err := encoder.Encode(result); err != nil {
log.Printf("encode error: %v", err)
}
count++
}
writer.Flush()
fmt.Printf("Processed %d records with %d workers\n", count, numWorkers)
}

Dieses Fan-Out-Muster verteilt JSONL-Datensätze auf Goroutines, deren Anzahl der CPU-Kerne entspricht. Die gepufferten Channels verhindern, dass Goroutines beim Senden oder Empfangen blockieren. Die sync.WaitGroup stellt sicher, dass alle Worker fertig sind, bevor der Results-Channel geschlossen wird. Für I/O-intensive Workloads wie API-Aufrufe können Sie numWorkers über die CPU-Anzahl hinaus erhöhen. Beachten Sie, dass die Ausgabereihenfolge bei nebenläufiger Verarbeitung nicht garantiert ist.

Best Practices für die Fehlerbehandlung

Robuste JSONL-Verarbeitung in Go erfordert sorgfältige Fehlerbehandlung. JSONL-Dateien in der Praxis können fehlerhafte Zeilen, Kodierungsprobleme oder unerwartet große Datensätze enthalten. Dieses Muster bietet Fehlerverfolgung auf Zeilenebene, konfigurierbares Überspringen bei Fehlern und zusammenfassende Berichterstattung.

Best Practices für die Fehlerbehandlung
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
)
type ParseError struct {
Line int
Err error
Raw string
}
func (e *ParseError) Error() string {
return fmt.Sprintf("line %d: %v", e.Line, e.Err)
}
type JSONLReader struct {
scanner *bufio.Scanner
lineNum int
Errors []ParseError
Skipped int
Success int
}
func NewJSONLReader(file *os.File) *JSONLReader {
scanner := bufio.NewScanner(file)
scanner.Buffer(make([]byte, 0, 1024*1024), 10*1024*1024)
return &JSONLReader{scanner: scanner}
}
func (r *JSONLReader) ReadAll(target any) error {
slice, ok := target.(*[]map[string]any)
if !ok {
return fmt.Errorf("target must be *[]map[string]any")
}
for r.scanner.Scan() {
r.lineNum++
line := r.scanner.Bytes()
// Leere Zeilen überspringen
if len(line) == 0 {
continue
}
var record map[string]any
if err := json.Unmarshal(line, &record); err != nil {
r.Errors = append(r.Errors, ParseError{
Line: r.lineNum,
Err: err,
Raw: string(line),
})
r.Skipped++
continue
}
*slice = append(*slice, record)
r.Success++
}
return r.scanner.Err()
}
func (r *JSONLReader) Summary() string {
return fmt.Sprintf(
"Lines: %d | Success: %d | Skipped: %d | Errors: %d",
r.lineNum, r.Success, r.Skipped, len(r.Errors),
)
}
func main() {
file, err := os.Open("data.jsonl")
if err != nil {
log.Fatal(err)
}
defer file.Close()
reader := NewJSONLReader(file)
var records []map[string]any
if err := reader.ReadAll(&records); err != nil {
log.Fatal(err)
}
fmt.Println(reader.Summary())
for _, e := range reader.Errors {
log.Printf("Parse error at %s", e.Error())
}
}

Dieses JSONLReader-Struct verfolgt Zeilennummern, sammelt Parse-Fehler mit ihrem Rohinhalt und gibt eine Zusammenfassung von Erfolgen und Fehlern aus. Der 10-MB-Scanner-Puffer verarbeitet ungewöhnlich große Zeilen, ohne abzustürzen. In der Produktion könnten Sie dies mit context.Context-Unterstützung für Abbruch, einer maximalen Fehlerschwelle zum Abbrechen der Verarbeitung oder strukturiertem Logging mit slog erweitern.

Go-Pakete für die JSONL-Verarbeitung

Go's Standardbibliothek bietet alle Bausteine, die Sie für die JSONL-Verarbeitung benötigen. Hier sind die drei Hauptansätze und wann Sie welchen verwenden sollten.

bufio.Scanner

Flexibel

Der standardmäßige zeilenweise Reader. Wird mit json.Unmarshal für explizites Parsen kombiniert. Am besten geeignet, wenn Sie Kontrolle über Puffergrößen benötigen, Rohzeilen überspringen oder inspizieren möchten oder Zeilennummern für die Fehlerberichterstattung brauchen. Verarbeitet Zeilen bis zur konfigurierten Puffergröße.

json.Decoder

Empfohlen

Streaming-JSON-Decoder, der direkt von einem io.Reader liest. Effizienter als Scanner + Unmarshal, da das Kopieren von Daten vermieden wird. Ideal für große Dateien und Netzwerk-Streams. Verarbeitet aufeinanderfolgende JSON-Werte automatisch ohne explizite Newline-Trennung.

json.Encoder

Schreiben

Streaming-JSON-Encoder, der in einen io.Writer schreibt. Hängt nach jedem Encode-Aufruf automatisch ein Newline an, was ihn perfekt für JSONL-Ausgabe macht. Kombinieren Sie ihn mit bufio.Writer für Schreiben mit hohem Durchsatz. Unterstützt SetEscapeHTML- und SetIndent-Konfiguration.

Testen Sie unsere kostenlosen JSONL-Tools

Möchten Sie keinen Code schreiben? Verwenden Sie unsere kostenlosen Online-Tools, um JSONL-Dateien direkt in Ihrem Browser anzuzeigen, zu validieren und zu konvertieren.

JSONL-Dateien online bearbeiten

JSONL-Dateien bis zu 1 GB direkt in Ihrem Browser anzeigen, validieren und konvertieren. Kein Upload erforderlich, 100 % privat.

Häufig gestellte Fragen

JSONL in Go — bufio.Scanner, json.Decoder & Nebenläufigke...