Claude Code voor correctheid van data-import/export: definieer validatieregels, consistente foutformaten en fuzz-tests voor CSV/JSON-imports om edge-case supporttickets te verminderen.

Imports falen zelden omdat de code “fout” is. Ze falen omdat echte data rommelig en inconsistent is, en vaak door mensen is gemaakt die jouw aannames nooit hebben gezien.
CSV-problemen gaan meestal over vorm en opmaak. JSON-problemen gaan meestal over betekenis en types. Beide kunnen op manieren breken die klein lijken maar verwarrende resultaten geven.
Deze issues komen steeds terug in supporttickets:
Correctheid is niet alleen “is het geïmporteerd”. Je moet beslissen welke uitkomsten toegestaan zijn, want gebruikers merken stille fouten eerder op dan luide failures.
De meeste teams kunnen het eens worden over drie uitkomsten:
Edge-cases zorgen voor extra werk wanneer mensen niet snel kunnen zien wat er misging of hoe ze het moeten oplossen. Een veelvoorkomend scenario: een klant uploadt een CSV van 5.000 rijen, de importer meldt “Invalid format”, en ze proberen drie keer opnieuw met willekeurige aanpassingen. Dat wordt meerdere tickets plus iemand van jouw kant die probeert het bestand lokaal te reproduceren.
Stel doelen die de cyclus verkorten: minder retries, snellere fixes, voorspelbare resultaten. Voordat je regels schrijft, bepaal wat “deels” betekent (en of je het toestaat), hoe je rij-niveau problemen rapporteert en wat gebruikers vervolgens moeten doen (bestand bewerken, velden mappen of een gecorrigeerde versie exporteren). Als je een vibe-coding platform gebruikt zoals Koder.ai (koder.ai) om snel validators en tests te genereren, blijft het importcontract dat gedrag consistent houden terwijl het product zich ontwikkelt.
Voordat je één validatieregel schrijft, bepaal wat “geldige input” betekent voor je product. De meeste importbugs ontstaan door mismatch tussen wat gebruikers uploaden en wat jouw systeem stilletjes aanneemt.
Begin met formaten en wees expliciet. “CSV” kan komma of puntkomma betekenen, een header-rij of niet, UTF-8 of “wat Excel produceerde.” Voor JSON: bepaal of je een enkel object, een array van records of JSON Lines (één JSON-object per regel) accepteert. Als je geneste JSON accepteert, definieer welke paden je leest en welke je negeert.
Vergrendel daarna het veldcontract. Voor elk veld: verplicht, optioneel of optioneel met een default? Defaults zijn onderdeel van het contract, geen implementatiedetail. Als country ontbreekt, default je naar leeg, kies je een specifiek land of wijs je de rij af?
Parsinggedrag is waar “tolerante” imports op lange termijn pijn bezorgen. Bepaal vooraf hoe strikt je bent met trimmen van spaties, normaliseren van case en het accepteren van varianten zoals "yes"/"true"/"1". Tolerantie is prima als het voorspelbaar en gedocumenteerd is.
Duplicaten zijn ook een contractbeslissing die correctheid en vertrouwen beïnvloedt. Definieer wat als duplicaat telt (zelfde email, dezelfde external_id, of een combinatie), waar je het detecteert (binnen het bestand, tegen bestaande data of beide) en wat je doet als het voorkomt (houd eerste, houd laatste, merge of weiger).
Een checklist voor je spec:
Voorbeeld: importeren van “customers.” Als email de unieke sleutel is, bepaal of " [email protected] " gelijk is aan "[email protected]", of een ontbrekende email is toegestaan als external_id aanwezig is, en of duplicaten binnen het bestand moeten worden afgewezen zelfs als de database geen match heeft. Zodra het contract vastligt, is consistent gedrag over UI en API veel eenvoudiger, of je het nu in Koder.ai implementeert of elders.
Rommelige imports beginnen vaak met één gigantische validate()-functie. Een schonere aanpak is gelaagde regels met duidelijke namen en kleine functies. Dat maakt wijzigingen makkelijker te reviewen en tests eenvoudiger te schrijven.
Begin met veldniveau-regels: checks die één waarde op zichzelf laten slagen of falen (type, bereik, lengte, toegestane waarden, regex). Houd ze saai en voorspelbaar. Voorbeelden: email matcht een basis email-patroon, age is een integer tussen 0 en 120, status is één van active|paused|deleted.
Voeg cross-field regels alleen toe waar het ertoe doet. Deze checks hangen af van meerdere velden en hierin verbergen zich vaak bugs. Klassieke voorbeelden: startDate moet vóór endDate liggen, of total moet gelijk zijn aan subtotal + tax - discount. Schrijf deze regels zo dat ze naar specifieke velden kunnen wijzen, niet alleen “record invalid”.
Scheids record-niveau en file-niveau regels. Een record-niveau regel controleert één rij (CSV) of één object (JSON). Een file-niveau regel controleert de hele upload: verplichte headers bestaan, een unieke sleutel herhaalt niet over rijen, kolomcount komt overeen, of het bestand de juiste versie opgeeft.
Normalisatie moet expliciet zijn, niet “magisch”. Bepaal wat je normaliseert vóór validatie en documenteer het. Veelvoorkomende voorbeelden: spaties trimmen, Unicode-normalisatie (zodat visueel identieke tekens gelijk vergelijken) en telefoonnummers formatteren naar één consistente opslagvorm.
Een structuur die leesbaar blijft:
Versiebeheer je regels. Zet een schemaVersion (of import "profile") in het bestand of API-request. Als je verandert wat “geldig” betekent, kun je nog steeds oudere exports opnieuw importeren met de oudere versie. Die ene keuze voorkomt veel “vandaag werkte het nog” tickets.
Een goede importer faalt op een behulpzame manier. Vage fouten leiden tot willekeurige retries en vermijdbaar supportwerk. Een duidelijk foutformaat helpt gebruikers het bestand snel te repareren en helpt jou validatie te verbeteren zonder clients te breken.
Begin met een stabiel foutobject en houd het consistent voor CSV en JSON. Je kunt Claude Code gebruiken om een schema en een paar realistische voorbeelden voor te stellen, en het dan vastleggen als onderdeel van het importcontract.
Behandel elke fout als een klein record met velden die niet veranderen. Het bericht kan evolueren, maar de code en locatie moeten stabiel blijven.
code: een korte, stabiele identifier zoals REQUIRED_MISSING of INVALID_DATE\n- message: een gebruiksvriendelijke zin voor de UI\n- path: waar het probleem zit (JSON-pointer zoals /customer/email of een kolomnaam zoals email)\n- row of line: voor CSV: 1-gebaseerd rijnummer (en optioneel de originele regel)\n- severity: minstens error en warningMaak fouten actiegericht. Geef wat je verwachtte en wat je daadwerkelijk zag, en waar mogelijk laat een voorbeeld zien dat zou slagen. Bijvoorbeeld: verwacht YYYY-MM-DD, kreeg 03/12/24.
Zelfs als je een platte lijst terugstuurt, geef genoeg data om fouten te groeperen per rij en per veld. Veel UIs willen “Rij 12 heeft 3 issues” en dan elke kolom highlighten. Supportteams vinden groeperen handig omdat patronen duidelijk worden (bijvoorbeeld: elke rij mist country).
Een compact antwoord kan er zo uitzien:
{
"importId": "imp_123",
"status": "failed",
"errors": [
{
"code": "INVALID_DATE",
"message": "Signup date must be in YYYY-MM-DD.",
"path": "signup_date",
"row": 12,
"severity": "error",
"expected": "YYYY-MM-DD",
"actual": "03/12/24"
},
{
"code": "UNKNOWN_FIELD",
"message": "Column 'fav_colour' is not recognized.",
"path": "fav_colour",
"row": 1,
"severity": "warning"
}
]
}
Plan voor lokalisatie zonder foutcodes te veranderen. Houd code taal-neutraal en duurzaam, en behandel message als vervangbare tekst. Als je later messageKey of vertaalde berichten toevoegt, kunnen oude clients nog steeds op dezelfde codes vertrouwen voor filtering, groepering en analytics.
Om “mysterie-imports” te voorkomen, moet je API-reactie twee vragen beantwoorden: wat gebeurde er en wat moet de gebruiker nu doen.
Zelfs bij fouten: geef een consistente samenvatting zodat UI en support-tooling elke import hetzelfde kunnen behandelen.
Includeer:\n
created, updated, skipped, failed aantallen\n- totalRows (of totalRecords voor JSON)\n- mode (bijvoorbeeld: "createOnly", "upsert" of "updateOnly")\n- startedAt en finishedAt timestamps\n- een correlationId waar support naar kan vragenDie correlationId is het waard. Als iemand meldt “het werd niet geïmporteerd”, kun je de exacte run en het foutrapport vinden zonder gokken.
Stop niet 10.000 rijfouten in de response. Retourneer een kleine sample (bijvoorbeeld 20) die het patroon toont en bied een aparte manier om het volledige rapport op te halen indien nodig.
Maak elke fout specifiek en stabiel:\n
Voorbeeld response-vorm (success met enkele rijfouten):
{
"importId": "imp_01HZY...",
"correlationId": "c_9f1f2c2a",
"status": "completed_with_errors",
"summary": {
"totalRows": 1200,
"created": 950,
"updated": 200,
"skipped": 10,
"failed": 40
},
"errorsSample": [
{
"row": 17,
"field": "email",
"code": "invalid_format",
"message": "Email must contain '@'.",
"value": "maria.example.com"
}
],
"report": {
"hasMore": true,
"nextPageToken": "p_002"
},
"next": {
"suggestedAction": "review_errors"
}
}
Let op het next-veld. Zelfs een minimale success-payload moet helpen om het product verder te laten gaan: toon een reviewscherm, bied een retry aan of open de geïmporteerde collectie.
Mensen retryen. Netwerken vallen uit. Als hetzelfde bestand twee keer wordt geïmporteerd, wil je voorspelbare resultaten.
Wees expliciet over idempotentie: accepteer een idempotencyKey (of bereken een file-hash) en retourneer dezelfde importId als het verzoek een herhaling is. Als je mode upsert is, definieer de matching-regel (bijvoorbeeld: “email is de unieke sleutel”). Als het create-only is, retourneer “skipped” voor duplicaten, niet “created again.”
Als het hele verzoek ongeldig is (slechte auth, verkeerde content-type, onleesbaar bestand), faal snel en retourneer status: "rejected" met een korte foutlijst. Als het bestand geldig is maar rij-niveau problemen heeft, behandel het als een afgeronde job met failed > 0 zodat gebruikers kunnen herstellen en opnieuw uploaden zonder de samenvatting te verliezen.
Een handige gewoonte: laat het model het contract schrijven in een gestructureerd formaat, niet als proza. “Behulpzame paragrafen” missen vaak details zoals trimregels, standaardwaarden en of een lege cel “ontbrekend” of “leeg” betekent.
Gebruik een prompt die een tabel forceert die een mens snel kan reviewen en een ontwikkelaar direct kan omzetten naar code. Vraag per veld naar de regel, pass- en fail-voorbeelden, en een expliciete opmerking bij alles vaags (bijvoorbeeld lege string vs null).
You are helping design an importer for CSV and JSON.
Output a Markdown table with columns:
Field | Type | Required? | Normalization | Validation rules | Default | Pass examples | Fail examples
Rules must be testable (no vague wording).
Then output:
1) A list of edge cases to test (CSV + JSON).
2) Proposed test names with expected result (pass/fail + error code).
Finally, list any contradictions you notice (required vs default, min/max vs examples).
Na de eerste versie: vraag om één positief en één negatief voorbeeld per regel. Dat dwingt dekking van lastige hoeken zoals lege strings, alleen-whitespace waarden, ontbrekende kolommen, null vs "null", zeer grote integers, wetenschappelijke notatie, duplicate IDs en extra JSON-velden.
Voor een concreet scenario: stel je voor dat je “customers” importeert uit CSV: email is verplicht, phone is optioneel en signup_date default naar vandaag als het ontbreekt. Het model moet een contradictie signaleren als je ook zegt dat signup_date verplicht is. Het zou tests voorstellen zoals import_customers_missing_email_returns_row_error en de foutcode en berichtvorm specificeren die je terugstuurt.
Doe één extra reviewronde voor implementatie: vraag het model de regels als checklist te herhalen en aanwijzingen te geven waar defaults, verplichte velden en normalisatie kunnen conflicteren. Die review vangt veel ticketwaardige gedrag af.
Fuzz-testing voorkomt dat “vreemde bestanden” supporttickets worden. Begin met een kleine set bekende-goede CSV/JSON-bestanden en genereer daarna duizenden licht gebroken varianten zodat je importer veilig en duidelijk reageert.
Begin met een kleine seed-corpus van geldige voorbeelden die echte gebruiksgevallen vertegenwoordigen: het kleinste geldige bestand, een typisch bestand en een groot bestand. Voor JSON: includeer één object, veel objecten en geneste structuren als je die ondersteunt.
Voeg vervolgens een geautomatiseerde mutator toe die telkens één ding aanpast. Maak mutaties reproduceerbaar door de random seed te loggen zodat je failures kunt replayen.
Fuzz-dimensies die de meeste problemen vangen:
Stop niet bij syntaxis. Voeg ook semantische fuzz toe: verwissel vergelijkbare velden (email vs username), extreme datums, duplicate IDs, negatieve hoeveelheden of waarden die enums schenden.
Fuzz-tests helpen alleen als de slagcriteria strikt zijn. Je importer mag nooit crashen of hangen, en fouten moeten consistent en actiegericht zijn.
Een praktische set pass-regels:\n
Draai deze tests in CI bij elke wijziging. Als je een failure vindt, bewaar het exacte bestand als fixture en voeg een regressietest toe zodat het nooit terugkeert.
Als je Claude Code gebruikt voor dit werk, laat het seed-fixtures genereren die bij je contract passen, een mutatieplan en de verwachte foutoutputs. Jij kiest nog steeds de regels, maar je krijgt snel een breed testoppervlak, vooral voor CSV-quoting en JSON-hoekjes.
De meeste importtickets komen van onduidelijke regels en onbehulpzame feedback.
Een veelvoorkomende valkuil is “best effort” parsing die niet op papier staat. Als je importer stilletjes spaties trimt, zowel komma's als puntkomma's accepteert of datumformaten raadt, bouwen gebruikers workflows rond die gissingen. Een kleine verandering of een ander bestandsgenerator breekt dan alles. Kies het gedrag, documenteer het en test het.
Nog een veelvoorkomende fout is het generieke foutbericht. “Invalid CSV” of “Bad request” dwingt gebruikers te raden. Ze uploaden hetzelfde bestand vijf keer en support moet alsnog om het bestand vragen. Fouten moeten wijzen naar een rij, een veld, een duidelijke reden en een stabiele code.
Het afkeuren van het hele bestand vanwege één slechte rij is ook pijnlijk. Soms is dat juist (bij financiële imports kan gedeeltelijke data gevaarlijk zijn). Veel business-imports kunnen doorgaan en een samenvatting rapporteren, zolang je een expliciete keuze biedt zoals strict mode vs partial import.
Text-encoding issues creëren hardnekkige tickets. UTF-8 is de juiste default, maar echte CSV's bevatten vaak een BOM, typografische aanhalingstekens of non-breaking spaces gekopieerd uit spreadsheets. Handel dit consistent af en rapporteer wat je detecteerde zodat gebruikers hun exportinstellingen kunnen aanpassen.
Tot slot: het wijzigen van foutcodes tussen releases breekt clients en automatiseringen. Verbeter de tekst als je wilt, maar houd codes en betekenissen stabiel. Versie ze alleen als het echt noodzakelijk is.
Valkuilen om vroeg tegen te houden:\n
Voorbeeld: een klant exporteert een CSV uit Excel, die een BOM toevoegt en datums formatteert als 03/04/2026. Je importer raadt MM/DD, maar de klant verwacht DD/MM. Als je foutrapport de gedetecteerde format, het exacte veld en een voorgestelde oplossing bevat, kan de gebruiker het corrigeren zonder heen-en-weer contact.
De meeste importproblemen zijn kleine mismatches tussen wat gebruikers denken dat het bestand betekent en wat jouw systeem accepteert. Beschouw dit als een release-gate.
Een praktische test: gebruik één opzettelijk rommelig bestand. Bijvoorbeeld: een CSV waar de header twee keer voorkomt (twee “email”-kolommen), een boolean-veld gebruikt “Y” en een datum is “03/04/05”. Je importer moet niet gaan raden. Hij moet een gedocumenteerde mapping toepassen of afwijzen met een specifieke fout.
Twee checks die teams vaak overslaan:\n
Eerst: verifieer dat je importer fouten rapporteert met genoeg locatie-details om het bronbestand te repareren. “Invalid date” is niet actiegericht. “Rij 42, kolom start_date: verwacht YYYY-MM-DD, kreeg 03/04/05” wel.
Tweede: draai hetzelfde ongeldige bestand twee keer en vergelijk de resultaten. Als de foutvolgorde verandert, codes veranderen of rijnummers verschuiven, verliest de gebruiker vertrouwen. Deterministisch gedrag is saai — en dat is precies de bedoeling.
Een veelvoorkomende echte import is orders van klanten afkomstig uit een spreadsheet-export. Iemand exporteert een CSV uit een oud systeem, bewerkt het in Excel en uploadt het. De meeste tickets ontstaan wanneer de importer stilletjes data “herstelt” of wanneer het foutbericht niet zegt wat er aangepast moet worden.
Stel een bestand orders.csv met kolommen: order_id,customer_email,order_date,currency,total_amount.
Hier zijn drie realistische slechte rijen (zoals de gebruiker ze zou zien):
order_id,customer_email,order_date,currency,total_amount
A-1001,[email protected],2026-01-05,USD,129.99
A-1002,not-an-email,01/06/2026,USD,49.00
,[email protected],2026-01-07,US, -10
Rij 2 heeft een ongeldige email en een ambigu datumformaat. Rij 3 mist order_id, heeft een niet-ondersteunde valutacode (US in plaats van USD) en een negatief bedrag.
Als je API fouten teruggeeft, houd de vorm consistent en specifiek. Hier is een voorbeeldrespons die gedeeltelijk succes ondersteunt:
{
"correlation_id": "imp_20260109_7f3a9d",
"import_id": "ord_01HZZ...",
"status": "partial_success",
"summary": {
"total_rows": 3,
"imported_rows": 1,
"failed_rows": 2
},
"errors": [
{
"row_number": 2,
"field": "customer_email",
"code": "invalid_email",
"message": "Email must contain a valid domain.",
"value": "not-an-email"
},
{
"row_number": 2,
"field": "order_date",
"code": "invalid_date_format",
"message": "Use ISO-8601 (YYYY-MM-DD).",
"value": "01/06/2026"
},
{
"row_number": 3,
"field": "order_id",
"code": "required",
"message": "order_id is required.",
"value": ""
},
{
"row_number": 3,
"field": "currency",
"code": "unsupported_currency",
"message": "Allowed values: USD, EUR, GBP.",
"value": "US"
},
{
"row_number": 3,
"field": "total_amount",
"code": "must_be_positive",
"message": "total_amount must be greater than 0.",
"value": " -10"
}
],
"retry": {
"mode": "upload_failed_only",
"failed_row_numbers": [2, 3]
}
}
Deels succes is belangrijk omdat gebruikers niet het hele bestand opnieuw moeten uploaden. Een eenvoudige retry-flow is: fix alleen de mislukte rijen, exporteer een kleine CSV met rijen 2 en 3 en upload opnieuw. Je importer zou dit idempotent moeten behandelen wanneer order_id aanwezig is, zodat een “retry” dezelfde records bijwerkt in plaats van duplicaten aan te maken.
Voor support is correlation_id de snelste weg naar diagnose. Een supportagent kan dat nummer vragen, de import-run in logs vinden en bevestigen of de parser extra kolommen, een verkeerd scheidingsteken of onverwachte encoding zag.
Volgende stappen om dit herhaalbaar te maken:
De meeste fouten komen door rommelige echte data, niet door “slechte code”. CSV-problemen gaan meestal over vorm (headers, scheidingsteken, quoteringen, encoding), terwijl JSON-problemen vaker over betekenis gaan (types, null vs leeg, onverwachte nesting). Behandel beide als onbetrouwbare input en valideer tegen een expliciet contract.
Definieer van tevoren drie uitkomsten:\n\n- Geaccepteerd: alles is geïmporteerd.\n- Afgewezen: niets wordt geïmporteerd omdat het bestand onbetrouwbaar is (verkeerde headers, onleesbare JSON, verkeerde encoding).\n- Deels geaccepteerd: geldige records worden geïmporteerd; ongeldige worden overgeslagen met duidelijke redenen.\n\nKies een standaard (veel producten kiezen voor deels geaccepteerd) en maak dat consistent in UI en API.
Leg een importcontract vast voordat je gaat valideren:\n\n- Geaccepteerde formaten (CSV-scheidingsteken, header verplicht of niet, UTF-8/BOM-afhandeling; JSON array vs object vs JSON Lines)\n- Veldregels (verplicht/optioneel/standaardwaarden)\n- Normalisatie (trimmen, hoofdletterregels, datumformaten)\n- Definitie en afhandeling van duplicaten\n- Waar validatie plaatsvindt (client, server of beide)\n\nDat voorkomt verrassingen als iets “vandaag nog werkte”.
Stel één ondubbelzinnig formaat per veld vast (bijvoorbeeld datums als YYYY-MM-DD). Als je varianten accepteert, maak dat expliciet en voorspelbaar (bijvoorbeeld true/false/1/0, maar niet elke spreadsheet-variant). Vermijd het raden van ambigüe datums zoals 01/02/03; eis ISO-formaten of geef een duidelijke foutmelding.
Bepaal:\n\n- Wat telt als duplicaat (email, external_id, of een composiet)\n- Detectiebereik (binnen het bestand, tegen bestaande records, of beide)\n- Actie (houd eerste, houd laatste, merge, of afwijzen)\n\nCombineer dit met idempotentie als gebruikers opnieuw kunnen uploaden, zodat dezelfde upload geen duplicaten creëert.
Gebruik lagen in plaats van één enorme validate():\n\n- Normaliseer input naar een canonieke vorm\n- Veldregels (type, bereik, enum)\n- Cross-field regels (start < end, totalen kloppen)\n- Bestandsregels (verplichte headers, duplicaatkeys, ondersteunde versie)\n\nKleine benoemde regels zijn makkelijker te testen en veiliger aan te passen.
Retourneer een stabiele foutvorm met:\n\n- code (stabiele identifier)\n- message (gebruiksvriendelijke tekst)\n- path/field (kolomnaam of JSON-pointer)\n- / (voor CSV)\n- ( vs )\n\nMaak het actiegericht door waar mogelijk te vermelden wat je verwachtte en wat je daadwerkelijk zag.
Geef altijd een consistente samenvatting, ook bij fouten:\n\n- aantallen: created, , , en \n- (success, rejected, completed_with_errors)\n- timestamps (, )\n- een voor support/diagnostiek\n\nBij grote bestanden: geef een klein en een manier om het volledige rapport later op te halen.
Ondersteun retries expliciet:\n\n- Accepteer een idempotencyKey (of gebruik een file-hash)\n- Retourneer de bestaande importId als dezelfde request opnieuw komt\n- Definieer upsert-matchregels (bijvoorbeeld: email is de unieke sleutel)\n\nZonder dit kunnen normale gebruikersretries records dubbel aanmaken.
Begin met een paar bekende goede seed-bestanden en genereer veel kleine mutaties (1 wijziging per test):\n\n- encoding (UTF-8 BOM, ongeldige bytes)\n- structuur (missende headers, extra kolommen, verkeerd scheidingsteken)\n- quotingen/newlines (onafgesloten quotes, ingesloten newlines)\n- type-edges (grote getallen, leeg vs null, NaN/Infinity in JSON)\n- groottebeperkingen (zeer lange velden, diepe nesting)\n\nEen fuzz-test “slaagt” als de importer nooit crasht of hangt en deterministische, actiegerichte fouten teruggeeft.
rowlineseverityerrorwarningupdatedskippedfailedtotalRows/totalRecordsstatusstartedAtfinishedAtcorrelationIderrorsSample