CSV är ett av de format som ingen älskar men alla använder. Det har funnits sedan 1970-talet, är fortfarande standardexportformatet för varje kalkylbladsapp på planeten, och sitter nästan säkert någonstans i din datapipeline just nu. Formatet ser trivialt enkelt ut — bara värden separerade med kommatecken — men att tolka det korrekt är förvånansvärt lätt att göra fel. Låt oss gå igenom hur det faktiskt fungerar, var det blir stökigt och hur man hanterar det i Python och JavaScript utan att skjuta sig i foten.

Vad CSV faktiskt är

CSV står för Comma-Separated Values. En CSV-fil är ren text — ingen binär kodning, inga metadata, ingen struktur utöver rader och kolumner. Varje rad är en post, varje värde inom en rad är separerat av en avgränsare (vanligtvis ett kommatecken), och den första raden är konventionellt en rubrikrad med kolumnnamn. Här är hur en riktig CSV-fil ser ut:

text
order_id,customer,product,quantity,unit_price,shipped
1001,Alice Nguyen,USB-C Hub,2,34.99,true
1002,Bob Martinez,Mechanical Keyboard,1,129.00,false
1003,Alice Nguyen,HDMI Cable,3,12.50,true
1004,Carol Smith,Webcam 1080p,1,79.95,false

Det är allt — sex kolumner, fyra datarader och en rubrik. Inga vinkelparenteser, inga klammerparenteser, inga citationstecken behövs (ännu). Den här enkelheten är precis varför CSV kvarstår: nästan vilket verktyg som helst på jorden kan öppna, läsa och producera det. Excel, Google Sheets, PostgreSQLs COPY-kommando, pandas, Rs read.csv() — alla stöder CSV direkt. Avvägningen är att formatet har nästan ingen struktur: inga typer, ingen nesting, inget schema. Varje värde är en sträng tills du bestämmer något annat.

RFC 4180 — Standarden som inte riktigt upprätthålls

Det finns en specifikation: RFC 4180, publicerad 2005. Den definierar saker som CRLF-radslut, dubbla citationstecken för escaping och hur man hanterar inbäddade kommatecken. Men här är saken — RFC 4180 är informationell, inte en standard. Den beskriver vad som redan var vanlig praxis, inte lagstiftar det. Ingen är skyldig att följa den, och i praktiken gör nästan ingen det exakt.

Resultatet är CSV-dialektkaos. Du har filer som använder LF istället för CRLF, filer där den första raden kan vara en rubrik eller inte, filer med ett avslutande radbrytning och filer utan, filer med en UTF-8 BOM tillagd av Excel. Wikipedia-artikeln om CSV har en gedigen genomgång av allt som varierar i praktiken. Det säkraste tillvägagångssättet: anta aldrig något om en CSV-fil du inte producerat själv, och använd alltid en riktig parser istället för att naivt dela på kommatecken.

Citatregler: när kommatecken visas inuti värden

Det är här "dela bara på kommatecken" bryter ihop. Vad händer när ett värde innehåller ett kommatecken? Eller en radbrytning? Eller ett dubbelt citationstecken? RFC 4180 — och de flesta verkliga parsers — hanterar det genom att omsluta fältet med dubbla citationstecken. Här är den fullständiga regeluppsättningen:

  • Om ett fält innehåller ett kommatecken, en radbrytning eller ett dubbelt citationstecken, omslut hela fältet med dubbla citationstecken
  • Om ett fält innehåller ett dubbelt citationstecken, escapa det genom att fördubbla det ("")
  • Citerade fält kan sträcka sig över flera rader — radbrytningen blir en del av värdet
  • Blanksteg inuti citationstecken är betydelsefulla och måste bevaras
text
order_id,customer,notes,unit_price
1005,David Lee,"Wants gift wrapping, express shipping",45.00
1006,Emma Brown,"Said: ""please handle with care""",89.99
1007,Frank Wu,"Address:
123 Main St
Apt 4B",15.50

I det exemplet: Davids notes innehåller ett kommatecken, så det är citerat. Emmas anteckning innehåller dubbla citationstecken, så de fördubblas inuti de yttre citationstecknen. Franks adress sträcker sig över flera rader — radbrytningarna är en del av värdet. Ett naivt line.split(',') i vilket språk som helst kommer att fullständigt förstöra alla tre av dessa. Det är därför du behöver en riktig parser.

Den gyllene regeln för CSV-parsning: Dela aldrig på kommatecken manuellt. Använd alltid ett dedikerat CSV-parsningsbibliotek. Det hanterar citering, escape-sekvenser, flerlinjiga fält och kodning — inget av vilket en enkel split kan göra korrekt.

Avgränsarvarianter: tabbar, semikolon, pipes

Trots namnet behöver CSV inte använda kommatecken. Flera vanliga varianter använder olika avgränsare och du kommer att stöta på alla i verkligheten:

  • TSV (Tab-Separated Values) — använder \t som avgränsare. Vanligt inom bioinformatik (BLAST-utdata, VCF-filer), databasexporter och överallt där värden ofta innehåller kommatecken
  • Semikolonseparerat — standard i Excel för regioner där kommatecken är decimalseparatorn (Tyskland, Frankrike, större delen av EU). Om du någonsin öppnat en CSV från en europeisk kollega och fått en enda gigantisk kolumn är det därför
  • Pipeseparerat — använder |. Vanligt i äldre bank- och försäkringsdataexporter där tabbar kan tas bort av mainframe-system
  • Fast bredd — inte tekniskt CSV, men ofta i samma kategori. Kolumner är utfyllda till fasta bredder istället för avgränsade

De flesta CSV-parsers låter dig ange avgränsaren explicit. När du skriver en CSV som andra ska konsumera, dokumentera din avgränsare. När du läser en du inte producerade, kontrollera de första raderna innan du antar. Verktyget CSV Formatter kan hjälpa dig inspektera och reformatera filer med icke-standardavgränsare.

Parsning av CSV i Python

Pythons standardbibliotek inkluderar modulen csv, och den är genuint bra. De två klasserna du kommer att använda mest är csv.reader för rad-som-lista-åtkomst och csv.DictReader för rad-som-dict-åtkomst (vilket nästan alltid är vad du vill ha).

python
import csv

# csv.reader — each row is a list of strings
with open('orders.csv', 'r', encoding='utf-8', newline='') as f:
    reader = csv.reader(f)
    header = next(reader)          # consume the header row
    for row in reader:
        order_id, customer, product, quantity, price, shipped = row
        print(f"Order {order_id}: {quantity}x {product} for {customer}")

# csv.DictReader — each row is an OrderedDict keyed by the header
with open('orders.csv', 'r', encoding='utf-8', newline='') as f:
    reader = csv.DictReader(f)
    for row in reader:
        if row['shipped'] == 'false':
            print(f"Pending: {row['order_id']} — {row['customer']}")

Två saker att alltid inkludera: encoding='utf-8' (eller vad filen faktiskt använder) och newline=''. Det andra är kritiskt — modulen csv gör sin egen radbrytningshantering och behöver råa bytes. Utan newline='' kan du få extra tomma rader på Windows.

Skrivning är lika enkelt med csv.writer:

python
import csv

orders = [
    {'order_id': 1008, 'customer': 'Grace Kim', 'product': 'Laptop Stand', 'quantity': 1, 'unit_price': 49.99, 'shipped': False},
    {'order_id': 1009, 'customer': 'Henry Park', 'product': 'USB-C Hub', 'quantity': 2, 'unit_price': 34.99, 'shipped': True},
]

with open('new_orders.csv', 'w', encoding='utf-8', newline='') as f:
    fieldnames = ['order_id', 'customer', 'product', 'quantity', 'unit_price', 'shipped']
    writer = csv.DictWriter(f, fieldnames=fieldnames)

    writer.writeheader()
    writer.writerows(orders)

# To use a different delimiter (e.g. tab-separated)
with open('new_orders.tsv', 'w', encoding='utf-8', newline='') as f:
    writer = csv.writer(f, delimiter='\t')
    writer.writerow(['order_id', 'customer', 'product'])
    writer.writerow([1008, 'Grace Kim', 'Laptop Stand'])

Modulen csv sköter all citering automatiskt — om ett fält innehåller ett kommatecken eller en radbrytning omsluter den det med dubbla citationstecken utan att du behöver tänka på det. Det är hela poängen med att använda ett bibliotek.

Parsning av CSV i JavaScript / Node.js

JavaScript har ingen inbyggd CSV-parser. Frestelsen är att göra detta:

js
// ❌ Don't do this — breaks immediately on quoted fields
const rows = csvText.split('\n').map(line => line.split(','));

Det misslyckas omedelbart när ett värde innehåller ett kommatecken, en radbrytning inuti citationstecken eller ett escapat dubbelt citationstecken. För något verkligt, använd ett bibliotek. PapaParse är förstahandsvalet — det är snabbt, hanterar alla kantfall, fungerar i både webbläsare och Node.js och stödjer streaming för stora filer.

js
import Papa from 'papaparse';
import fs from 'fs';

// Parse a CSV string
const csvText = fs.readFileSync('orders.csv', 'utf8');

const result = Papa.parse(csvText, {
  header: true,          // first row becomes object keys
  skipEmptyLines: true,  // ignore blank lines at end of file
  dynamicTyping: true,   // converts "true"/"false" to booleans, numbers to numbers
});

console.log(result.data);
// [
//   { order_id: 1001, customer: 'Alice Nguyen', product: 'USB-C Hub', quantity: 2, unit_price: 34.99, shipped: true },
//   { order_id: 1002, customer: 'Bob Martinez', product: 'Mechanical Keyboard', quantity: 1, unit_price: 129, shipped: false },
//   ...
// ]

// Check for parse errors
if (result.errors.length > 0) {
  console.error('Parse errors:', result.errors);
}

// Generate CSV from an array of objects
const orders = [
  { order_id: 1008, customer: 'Grace Kim', product: 'Laptop Stand', quantity: 1, unit_price: 49.99 },
  { order_id: 1009, customer: 'Henry Park', product: 'USB-C Hub', quantity: 2, unit_price: 34.99 },
];

const csvOutput = Papa.unparse(orders);
fs.writeFileSync('export.csv', csvOutput, 'utf8');

Alternativet dynamicTyping är praktiskt men värt att känna till — det konverterar saker som "34.99" till ett tal automatiskt. Det är vanligtvis vad du vill ha, men det kan överraska dig om ett fält som order_id råkar vara ett tal i CSV men du ville ha det som en sträng. Stäng av om du behöver strikt strängutdata.

För snabba konverteringar mellan CSV och andra format hanterar verktyget CSV till JSON de flesta vanliga fall i webbläsaren utan någon kod — användbart för engångsdatatransformationer. Det finns också CSV till XML om du behöver mata data till ett XML-baserat system.

Vanliga fallgropar du definitivt kommer att stöta på

Även med en bra parser har CSV några välkända minor. Här är de som dyker upp mest:

  • BOM-bytes från Excel. När Excel exporterar en CSV som "UTF-8" lägger den ofta till en UTF-8 BOM (EF BB BF i hex, eller \ufeff som ett tecken). Det gör att det första kolumnnamnet ser ut som order_id istället för order_id. I Python, öppna filen med encoding='utf-8-sig' istället för utf-8 för att ta bort den automatiskt. PapaParse hanterar det transparent.
  • CRLF vs LF-radslut. RFC 4180 specificerar CRLF (\r\n), men Unix-verktyg producerar LF (\n) och gamla Mac-filer använder CR (\r) ensam. Det är därför Pythons csv-modul behöver newline='' — den hanterar alla tre internt. Om du läser råa bytes och delar manuellt måste du ta bort \r explicit.
  • Kodningsproblem. CSV har inget sätt att deklarera sin egen kodning — till skillnad från HTMLs <meta charset> eller XMLs <?xml encoding="..."?>. Excel sparar ofta filer i Windows-1252 (aka CP1252) istället för UTF-8, vilket förstör accentuerade tecken. Om du ser tecken som é istället för é har du en UTF-8-fil som avkodas som Latin-1, eller vice versa. Etablera alltid kodning utanför bandet med den som producerar filen.
  • Tal som ser ut som datum. Excel konverterar tyst värden som 1-2 eller 03/04 till datum när du öppnar eller sparar. Om du exporterar produktkoder eller versionsnummer, sätt ett enkelt citationstecken framför dem i Excel ('1-2) för att förhindra detta — eller säg till den som producerar filen att göra det.
  • Avslutande kommatecken. Vissa exporterare avger ett avslutande kommatecken i slutet av varje rad, vilket skapar en spöklik tom kolumn. En robust parser ignorerar det; en naiv split skapar ett extra tomt strängelement.

Om du har att göra med en fil som ser konstig ut kan verktyget CSV Validator snabbt berätta om filen är välformad och flagga kodnings- eller strukturella problem innan du försöker bearbeta den.

CSV vs JSON vs Excel — när man ska använda vad

Dessa tre format överlappar mycket i praktiken, men var och en har en tydlig styrka:

  • Använd CSV när du flyttar platt, tabellformad data mellan system — databasexporter, analyspipelines, kalkylbladsimporter, massdataladdning. Det stöds universellt, är litet och trivialt diff-bart i git. Begränsningen: det är platt. Ingen nesting, inga typer, inga relationer.
  • Använd JSON när data är hierarkisk eller schema spelar roll. En order med flera rader, en konfigurationsfil med kapslade objekt, ett API-svar — dessa är naturligt JSON. CSV skulle tvinga dig att denormalisera data eller uppfinna din egen nestingkonvention. JSON-specifikationen är ren och entydig; formatet bevarar typer (tal, booleaner, null, arrayer, objekt).
  • Använd Excel (.xlsx) när utdata är för människor, inte maskiner. Formatering, formler, flera ark, diagram — om en affärsanvändare är den slutliga konsumenten är Excel ofta rätt val. Använd det aldrig som ett utbytesformat mellan system. OOXML-specifikationen är enorm och formatet är skört mellan versioner.

Det finns en praktisk heuristik: om du naturligt skulle titta på data i ett kalkylblad är CSV förmodligen bra. Om du skulle titta på det i ett träd eller en kodredigerare, använd JSON. Om du skickar det till en affärsintressent som kommer att filtrera och sortera det, använd Excel.

Sammanfattning

CSV är enkelt av design och vilseledande knepigt i praktiken. Formatet har ingen upprätthållen standard, levereras i flera avgränsarvarianter och förlitar sig på citatregler som bryter varje manuellt parsningsförsök. Lösningen är alltid densamma: använd en riktig parser (Pythons inbyggda csv-modul eller PapaParse i JavaScript), ange alltid kodningen explicit och se upp för BOM-bytes från Excel. När du väl har en pålitlig parser på plats är CSV faktiskt ett bra format — snabbt att producera, lätt att inspektera i vilken textredigerare som helst och stöds överallt. För dagligt arbete med CSV-filer hanterar verktygen CSV till JSON, CSV Formatter och CSV Validator de vanliga operationerna utan att skriva en enda kodrad.