使用 Claude Code 提高数据导入/导出正确性:定义验证规则、统一错误格式,并为 CSV/JSON 导入添加模糊测试,以减少边缘案例的支持工单。

导入失败很少是因为代码“有错”。更多时候是因为真实数据杂乱、不一致,且由从未见过你假设的人生成。
CSV 的问题通常出在形状和格式上;JSON 的问题更常是语义和类型。两者都可能以看似小的问题破坏结果,却导致用户困惑。
这些问题会反复出现在支持工单中:
"00123" 变成 123,true/false 变成 "yes"/"no")正确性不仅仅是“是否导入”。你必须决定哪些结果是允许的,因为用户对“无声错误”比对“明显失败”更敏感。
大多数团队可以就三种结果达成一致:
边缘情况之所以变成返工,是因为人们无法快速判断问题出在何处或如何修复。一个常见场景:客户上传了 5,000 行的 CSV,导入器提示“Invalid format”,他们尝试三次并随机修改。最后变成多个工单,且你方有人需要本地复现该文件。
制定能缩短这个循环的目标:减少重试、加快修复、结果可预测。在写规则之前,决定“部分接受”是什么意思(是否允许)、如何报告逐行问题,以及用户下一步应该做什么(编辑文件、字段映射、或导出已修正版本)。如果你使用像 Koder.ai (koder.ai) 这样的工具快速生成验证器和测试,导入合同仍然是确保产品演进时行为一致的关键。
在写任何验证规则之前,先决定对你产品而言什么算“有效输入”。大多数导入错误源于用户上传的数据与系统默认假设不匹配。
先从格式开始,并明确说明。“CSV” 可能意味着逗号或分号分隔、有无表头、UTF-8 或“Excel 导出的那种”。对于 JSON,决定你接受单个对象、记录数组,还是 JSON Lines(每行一个 JSON 对象)。如果接受嵌套 JSON,要定义你会读取哪些路径、忽略哪些路径。
然后锁定字段合同。对每个字段决定它是必填、可选,还是可选且有默认值。默认值是合同的一部分,不是实现细节。如果 country 缺失,你是默认空、选一个具体国家,还是拒绝该行?
解析行为是“宽容”导入带来长期痛点的地方。提前决定是否严格处理去除空格、大小写规范化以及接受 "yes"/"true"/"1" 等变体。只要这种宽容是可预测并有文档支持,就是可以接受的。
重复项也是一个合同决策,会影响正确性与信任。定义什么算重复(相同 email、相同 external_id 或字段组合)、在何处检测(仅文件内、与现有数据比对或两者)以及发生时如何处理(保留第一条、保留最后一条、合并或拒绝)。
一个可以粘到规范里的合同检查清单:
举例:导入“customers”。如果 email 是唯一键,决定 " [email protected] " 是否等同于 "[email protected]",当 external_id 存在时是否允许缺失 email,以及文件内的重复是否应该被拒绝,即使数据库中没有匹配。一旦合同确定,无论在 UI 还是 API 中,一致的行为都更容易实现,无论你是不是在 Koder.ai 中实现它。
混乱的导入往往从一个巨大的 validate() 函数开始。更清晰的做法是分层规则、命名清晰且函数小巧。这样修改更易审查,测试也更容易编写。
从字段级规则开始:检查单个值能否独立通过(类型、范围、长度、允许值、正则)。让它们乏味且可预测。示例:email 匹配基础邮箱模式,age 是 0 到 120 的整数,status 是 active|paused|deleted 之一。
只有在必要时再添加跨字段规则。这类检查依赖多个字段,漏洞常隐藏在这里。经典例子:startDate 必须早于 endDate,或 total 等于 subtotal + tax - discount。把这些规则写成能指向具体字段的形式,而不是单纯返回“记录无效”。
把记录级规则与文件级规则分开。记录级规则检查一行(CSV)或一个对象(JSON)。文件级规则检查整个上传:必需表头是否存在、唯一键是否在行之间重复、列数是否符合预期、或文件是否声明了受支持的版本。
归一化应当明确,而不是“魔法”。决定在验证前你会做哪些归一化,并记录下来。常见例子包括去除空格、Unicode 归一化(使视觉相同的字符比较一致)以及把电话号码格式化为一致的存储格式。
一个保持可读性的结构:
对规则做版本控制。在文件或 API 请求中放一个 schemaVersion(或导入“profile”)。当你改变“有效”的定义时,仍可用旧版本重新导入早期导出的数据。这个选择能避免很多“昨天还能用”的工单。
一个好的导入器以可操作的方式失败。模糊的错误会导致随机重试和可避免的支持工作。清晰的错误格式既帮助用户快速修复文件,也帮助你改进验证而不破坏客户端。
先定义一个稳定的错误对象结构并在 CSV 与 JSON 间保持一致。你可以用 Claude Code 来建议一个模式和若干现实示例,然后把它作为导入合同的一部分固定下来。
把每个错误当作一个小记录,字段不要随意变动。message 可以演进,但 code 和 location 应保持稳定。
code:简短且稳定的标识符,例如 REQUIRED_MISSING 或 INVALID_DATEmessage:面向用户的友好句子path:问题所在(JSON 指针如 /customer/email,或列名如 email)row 或 line:对于 CSV,包含 1 基础的行号(可选地包含原始行)severity:至少有 error 和 warning让错误具有可操作性。包含你期望的值与实际值,并在可能时给出一个能通过的示例。例如:预期 YYYY-MM-DD,实际上是 03/12/24。
即使返回扁平的错误列表,也要包含足够的信息以便按行和按字段分组。许多 UI 想显示“第 12 行有 3 个问题”并高亮对应列。支持团队也喜欢按模式分组(比如每一行都缺少 country)。
一个紧凑响应示例:
{
"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"
}
]
}
计划国际化但不要改变错误代码。保持 code 与语言无关且持久,把 message 作为可替换文本。如果以后添加 messageKey 或翻译文本,老客户端仍然可以依赖相同的代码来过滤、分组和做分析。
为了避免“神秘导入”,API 响应应回答两个问题:发生了什么,以及用户下一步该做什么。
即便有错误,也要返回一致的摘要,便于 UI 与支持工具统一处理每次导入。
包含:
created、updated、skipped、failed 计数totalRows(或 JSON 的 totalRecords)mode(例如:createOnly、upsert 或 updateOnly)startedAt 与 finishedAt 时间戳correlationId这个 correlationId 非常值得。有了它,当有人报告“没有导入”时,你可以定位到确切的运行与错误报告,而不用猜测。
不要在响应中塞入 10,000 条行错误。返回一个小样本(比如 20 条)来展示模式,并提供单独获取完整报告的方式。
每条错误要尽量具体且稳定:
示例响应形状(部分成功):
{
"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"
}
}
注意 next 字段。即便是最小的成功载荷,也应帮助产品推进:显示审查界面、提供重试或打开已导入的集合。
人们会重试,网络会失败。如果同一文件被导入两次,你需要可预测的结果。
明确幂等性:接受 idempotencyKey(或计算文件哈希),若请求重复则返回已有的 importId。若模式是 upsert,定义匹配规则(例如以 email 为唯一键)。如果是 create-only,那么对重复项应返回“skipped”而不是再次“created”。
如果整个请求无效(认证失败、错误的内容类型、无法读取的文件),应快速失败并返回 status: "rejected" 及简短错误列表。如果文件语法有效但存在行级问题,应把它当作已完成的任务并返回 failed > 0,这样用户可以修复并重新上传而不会丢失摘要信息。
一个有用的习惯:让模型以结构化格式输出合同,而不是散文。“有帮助的段落”往往会跳过诸如去除空格规则、默认值以及空单元格是否表示“缺失”或“空”的细节。
使用一个会强制生成表格的提示,便于人工快速审阅并把它转成代码。要求为每个字段提供规则、通过/失败示例,以及对任何模糊点的显式说明(例如空字符串与 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).
在第一稿之后,通过要求为每条规则给出一个正例和一个反例来收紧范围。这会促使模型覆盖诸如空字符串、仅空白值、缺失列、null 与 "null"、超大整数、科学计数法、重复 ID 和额外 JSON 字段等棘手角落情形。
举个具体场景:从 CSV 导入“customers”:email 必填,phone 可选,signup_date 缺失则默认当天。若你同时说“signup_date 必填”,模型应该指出矛盾。它还应提出诸如 import_customers_missing_email_returns_row_error 的测试名并指定错误码与返回的消息形状。
在实现之前再多做一轮:让模型把规则以清单形式重述并指出默认值、必填字段与归一化可能冲突之处。这个复核步骤能抓住大量会成为工单的行为。
模糊测试能阻止“奇怪的文件”变成支持工单。先从少量已知良好的 CSV/JSON 文件开始,然后生成成千上万略有破损的变体,确保导入器以安全且清晰的方式响应。
从一小组真实使用场景的有效示例开始:最小有效文件、典型文件和大型文件。对于 JSON,包含单个对象、多对象和嵌套结构(如果你支持的话)。
然后添加一个自动变异器,每次只改动一处。通过记录随机种子保持变异可复现,这样你可以回放失败用例。
值得变异的维度:
别只检验语法,也要做语义模糊:互换相似字段(email vs username)、极端日期、重复 ID、负数数量或违反枚举的值。
只有当通过准则严格时,模糊测试才有价值。你的导入器绝不应崩溃或挂起,错误应确定且可操作。
一组实用的通过规则:
在每次变更时把这些测试放到 CI 中。发现失败就保存精确的文件作为夹具,并添加回归测试以防再次出现。
如果你用 Claude Code 完成这项工作,让它生成匹配合同的种子夹具、变异计划与预期错误输出。你仍然选择规则,但它能快速拓宽测试面,尤其在 CSV 引号和 JSON 边角情形上非常有用。
大多数导入工单来自于不明确的规则和无助的反馈。
一个常见陷阱是未记录的“尽力而为”解析。如果导入器静默去除空格、同时接受逗号和分号或猜测日期格式,用户会基于这些猜测建立流程。随后一次小改动或不同的文件生成器就会破坏流程。选择一种行为、记录并测试它。
另一个常见问题是通用错误信息。“Invalid CSV” 或 “Bad request” 会迫使用户猜测问题。他们会重复上传相同文件五次,支持最终还是要文件本身。错误应指向行、字段、明确原因和稳定的错误码。
把整个文件因一行错误而拒绝也常常令人头疼。有时这是正确的(例如金融导入在部分数据缺失时危险),但很多业务型导入可以继续并报告摘要,只要你提供类似严格模式 vs 部分导入的显式选择。
文本编码问题会产生难缠的工单。UTF-8 是正确的默认,但真实的 CSV 常带 BOM、花体引号或从电子表格复制来的不间断空格。要一致处理这些情况并在报告中说明检测到的编码,方便用户修正导出设置。
最后,发布中更改错误码会破坏客户端与自动化流程。你可以改进文字描述,但要保持 code 与含义稳定。只有在确实必要时才为错误码做版本化。
值得提前防范的陷阱:
举例:客户用 Excel 导出 CSV,Excel 加入了 BOM 并把日期格式化为 03/04/2026。你的导入器猜测为 MM/DD,但客户期望 DD/MM。如果错误报告包含检测到的格式、确切字段和建议的修复,用户便能无需来回沟通自行纠正。
大多数导入问题是用户对文件含义的期望与系统接受范围的小不一致。把这当作发布门禁。
一个实用测试:准备一个故意混乱的文件。例如:表头出现两次(两个 email 列)、布尔字段使用 Y、日期为 03/04/05。导入器不应猜测,而应应用已记录的映射规则或以具体错误拒绝。
团队经常跳过的两项检查:
第一,确保导入器报告的错误包含足够的位置信息以修复源文件。“Invalid date” 无助于修复;“第 42 行,列 start_date:期望 YYYY-MM-DD,实际为 03/04/05” 是可操作的。
第二,重复运行同一无效文件并比较结果。如果错误顺序变化、代码变化或行号漂移,用户会失去信任。确定性行为看起来无聊,但正是它的价值所在。
一个常见的实际导入是订单数据从旧系统导出为电子表格,再由别人用 Excel 编辑并上传。大多数工单发生在导入器静默“修复”数据,或错误信息没有说明如何修改时。
假设有一个名为 orders.csv 的文件,列为:order_id,customer_email,order_date,currency,total_amount。
下面是三行真实而有问题的行(用户会以这种形式看到它们):
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
第 2 行的 email 无效且日期格式有歧义。第 3 行缺少 order_id,货币代码不受支持(US 而不是 USD),并且金额为负数。
如果你的 API 返回错误,请保持响应形状一致且具体。下面是支持部分成功的示例响应:
{
"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]
}
}
部分成功很重要,因为用户不应被迫重新上传整个文件。一个简单的重试流程:修复失败的行,只导出包含第 2 行和第 3 行的小 CSV 并重新上传。当 order_id 存在时,你的导入器应将该重试视为幂等操作,从而更新相同记录而不是创建重复项。
对于支持人员,correlation_id 是最快的诊断路径。支持人员只需提供该值,就能在日志中找到该次导入运行并确认解析器是否看到额外列、错误的分隔符或意外的编码。
接下来的重复化步骤:
大多数失败来自真实世界的数据混乱,而不是“代码错误”。CSV 问题通常与形状有关(表头、分隔符、引用、编码),而 JSON 问题通常与含义有关(类型、null 与空值、意外的嵌套)。把两者都当作不可信输入,并针对明确的合同进行验证。
预先定义三种结果:
选择一个默认行为(很多产品选部分接受)并在 UI 与 API 中保持一致。
在写验证逻辑前先把 import contract 写清楚:
这能避免“昨天还能用”之类的问题。
为每个字段默认一种不含歧义的格式(例如日期统一为 YYYY-MM-DD)。如果接受多种变体,必须显式且可预测(例如接受 true/false/1/0,但不要接受所有电子表格的猜测)。避免猜测像 01/02/03 这种不确定的日期,要么要求 ISO,要么拒绝并返回清晰错误。
你需要决定:
如果用户可以重试导入,请结合幂等性设计,确保重复上传不会造成重复创建。
用分层方式而不是一个巨大的 validate():
小型、命名清晰的规则更易测试与审查。
返回稳定的错误结构,包含:
始终返回一致的摘要,即便存在错误:
支持重试以避免重复创建:
idempotencyKey(或使用文件哈希)importId否则用户的常规重试可能会导致重复记录。
从少量已知良好种子文件开始,然后生成许多微小的变异(每次只变一处):
模糊测试“通过”的定义是:导入器不崩溃、不挂起,返回确定且可操作的错误。把失败的文件保存为回归用例。
code(稳定的标识符)message(便于展示给用户)path/field(列名或 JSON 指针)row/line(CSV 时)severity(error 或 warning)尽可能可操作:说明预期值与实际值,便于用户修正。
created、updated、skipped、failed,以及 totalRows/totalRecordsstatus(success、rejected、completed_with_errors)startedAt、finishedAtcorrelationId对于大文件,返回一个 errorsSample 并提供获取完整报告的方式。