CSV to JSON Converter with Streaming Processing
Streams a CSV file line-by-line and emits JSON records without loading the entire file, supporting custom delimiters and headers.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"io"
"os"
"strings"
)
// CSVtoJSONConverter handles streaming CSV to JSON conversion
type CSVtoJSONConverter struct {
reader *csv.Reader
writer *json.Encoder
headers []string
delimiter rune
trimWhitespace bool
}
// NewCSVtoJSONConverter creates a new converter from input and output io.Reader/io.Writer
func NewCSVtoJSONConverter(input io.Reader, output io.Writer, delimiter rune, trimWhitespace bool) *CSVtoJSONConverter {
reader := csv.NewReader(input)
reader.Comma = delimiter
reader.TrimLeadingSpace = trimWhitespace
return &CSVtoJSONConverter{
reader: reader,
writer: json.NewEncoder(output),
delimiter: delimiter,
trimWhitespace: trimWhitespace,
}
}
// Convert processes the CSV file and writes JSON output line-by-line
func (c *CSVtoJSONConverter) Convert() error {
// Read CSV headers
headers, err := c.reader.Read()
if err != nil {
return fmt.Errorf("failed to read headers: %w", err)
}
c.headers = headers
// Write JSON array start
if _, err := fmt.Fprint(c.writer.Writer, "["); err != nil {
return fmt.Errorf("failed to write JSON start: %w", err)
}
firstRow := true
for {
// Read CSV row
row, err := c.reader.Read()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("failed to read row: %w", err)
}
// Skip empty rows
if len(row) == 0 {
continue
}
// Map row to JSON object
jsonRow, err := c.rowToJSON(row)
if err != nil {
return fmt.Errorf("failed to convert row: %w", err)
}
// Add comma separator between rows
if !firstRow {
if _, err := fmt.Fprint(c.writer.Writer, ",\n"); err != nil {
return err
}
} else {
firstRow = false
}
// Encode JSON object
if err := c.writer.Encode(jsonRow); err != nil {
return fmt.Errorf("failed to encode JSON: %w", err)
}
}
// Write JSON array end
if _, err := fmt.Fprint(c.writer.Writer, "]\n"); err != nil {
return fmt.Errorf("failed to write JSON end: %w", err)
}
return nil
}
// rowToJSON converts a single CSV row to a JSON object
func (c *CSVtoJSONConverter) rowToJSON(row []string) (map[string]string, error) {
jsonRow := make(map[string]string)
// Use header for keys; if row is shorter than headers, fill with empty string
for i, header := range c.headers {
if i < len(row) {
value := row[i]
if c.trimWhitespace {
value = strings.TrimSpace(value)
}
jsonRow[header] = value
} else {
jsonRow[header] = ""
}
}
// If row is longer than headers, add with index keys
for i := len(c.headers); i < len(row); i++ {
key := fmt.Sprintf("column_%d", i+1)
value := row[i]
if c.trimWhitespace {
value = strings.TrimSpace(value)
}
jsonRow[key] = value
}
return jsonRow, nil
}
func main() {
// Check command line arguments
if len(os.Args) < 3 {
fmt.Printf("Usage: %s <input.csv> <output.json> [delimiter]\n", os.Args[0])
os.Exit(1)
}
inputPath := os.Args[1]
outputPath := os.Args[2]
delimiter := ','
if len(os.Args) >= 4 && len(os.Args[3]) > 0 {
delimiter = rune(os.Args[3][0])
}
// Open input file
inputFile, err := os.Open(inputPath)
if err != nil {
fmt.Printf("Failed to open input file: %v\n", err)
os.Exit(1)
}
defer inputFile.Close()
// Create output file
outputFile, err := os.Create(outputPath)
if err != nil {
fmt.Printf("Failed to create output file: %v\n", err)
os.Exit(1)
}
defer outputFile.Close()
// Create converter
converter := NewCSVtoJSONConverter(inputFile, outputFile, delimiter, true)
// Run conversion
if err := converter.Convert(); err != nil {
fmt.Printf("Conversion failed: %v\n", err)
os.Exit(1)
}
fmt.Printf("Successfully converted %s to %s\n", inputPath, outputPath)
}How It Works
Streams a CSV file line-by-line and emits JSON records without loading the entire file, supporting custom delimiters and headers.
Opens a CSV reader with configurable comma, reads rows iteratively, maps columns to keys, encodes each record to JSON, and writes to output while handling errors and end-of-file cleanly.
Key Concepts
- 1Streaming approach keeps memory constant for huge files.
- 2Header mapping controls field names and order.
- 3Error handling captures malformed rows without crashing the pipeline.
When to Use This Pattern
- Converting large exports to JSON for ingestion.
- Feeding APIs or message queues from CSV data.
- CLI utilities for quick data format transformations.
Best Practices
- Validate header counts versus row lengths.
- Handle encoding and escaping properly in JSON output.
- Close files and flush writers to avoid partial output.
Go Version1.18
Difficultyintermediate
Production ReadyYes
Lines of Code164