[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6Q37B7SYTPJC58KA7A8WAXE":3,"skill-same:01M6Q37B7SYTPJC58KA7A8WAXE":84},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":59,"repositoryHtmlUrl":60,"repositoryHomepageUrl":6,"ownerHtmlUrl":61,"ownerBlogUrl":6,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M6Q37B7SYTPJC58KA7A8WAXE","data-validation",null,"Validate data with schemas across languages and formats. Use when defining JSON Schema, using Zod (TypeScript) or Pydantic (Python), validating API request\u002Fresponse shapes, checking CSV\u002FJSON data integrity, or setting up data contracts between services.","Kernel8901\u002Fai-agent-skills-classification","Kernel8901","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F72647553?v=4","01M6KV388J2H5PNC8D8P940BJD","01M6KV38CS5B3AY63RKZB410YM",7,"MIT","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Fdata-validation\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"validation","data validation","data","python3","darwin","with","data integrity","across","up","schema","use","zod","python","api","Pydantic","services","csv","validate data","win32","linux","2026-11-10 10:12:30",false,"---\nname: data-validation\ndescription: Validate data with schemas across languages and formats. Use when defining JSON Schema, using Zod (TypeScript) or Pydantic (Python), validating API request\u002Fresponse shapes, checking CSV\u002FJSON data integrity, or setting up data contracts between services.\nmetadata: {\"clawdbot\":{\"emoji\":\"✅\",\"requires\":{\"anyBins\":[\"node\",\"python3\",\"jq\"]},\"os\":[\"linux\",\"darwin\",\"win32\"]}}\n---\n\n# Data Validation\n\nSchema-based data validation across languages and formats. Covers JSON Schema, Zod (TypeScript), Pydantic (Python), API boundary validation, data contracts, and integrity checking.\n\n## When to Use\n\n- Defining the shape of API request\u002Fresponse bodies\n- Validating user input before processing\n- Setting up data contracts between services\n- Checking CSV\u002FJSON file integrity before import\n- Migrating data (did the ETL preserve everything?)\n- Generating types or documentation from schemas\n\n## JSON Schema\n\n### Basic schema\n\n```json\n{\n  \"$schema\": \"https:\u002F\u002Fjson-schema.org\u002Fdraft\u002F2020-12\u002Fschema\",\n  \"type\": \"object\",\n  \"required\": [\"name\", \"email\", \"age\"],\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"maxLength\": 100\n    },\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"age\": {\n      \"type\": \"integer\",\n      \"minimum\": 0,\n      \"maximum\": 150\n    },\n    \"role\": {\n      \"type\": \"string\",\n      \"enum\": [\"user\", \"admin\", \"moderator\"],\n      \"default\": \"user\"\n    },\n    \"tags\": {\n      \"type\": \"array\",\n      \"items\": { \"type\": \"string\" },\n      \"uniqueItems\": true,\n      \"maxItems\": 10\n    },\n    \"address\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"street\": { \"type\": \"string\" },\n        \"city\": { \"type\": \"string\" },\n        \"zip\": { \"type\": \"string\", \"pattern\": \"^\\\\d{5}(-\\\\d{4})?$\" }\n      },\n      \"required\": [\"street\", \"city\"]\n    }\n  },\n  \"additionalProperties\": false\n}\n```\n\n### Common patterns\n\n```json\n\u002F\u002F Nullable field\n{ \"type\": [\"string\", \"null\"] }\n\n\u002F\u002F Union type (string or number)\n{ \"oneOf\": [{ \"type\": \"string\" }, { \"type\": \"number\" }] }\n\n\u002F\u002F Conditional: if role is admin, require permissions\n{\n  \"if\": { \"properties\": { \"role\": { \"const\": \"admin\" } } },\n  \"then\": { \"required\": [\"permissions\"] }\n}\n\n\u002F\u002F Pattern properties (dynamic keys)\n{\n  \"type\": \"object\",\n  \"patternProperties\": {\n    \"^env_\": { \"type\": \"string\" }\n  }\n}\n\n\u002F\u002F Reusable definitions\n{\n  \"$defs\": {\n    \"address\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"street\": { \"type\": \"string\" },\n        \"city\": { \"type\": \"string\" }\n      }\n    }\n  },\n  \"properties\": {\n    \"home\": { \"$ref\": \"#\u002F$defs\u002Faddress\" },\n    \"work\": { \"$ref\": \"#\u002F$defs\u002Faddress\" }\n  }\n}\n```\n\n### Validate with command line\n\n```bash\n# Using ajv-cli (Node.js)\nnpx ajv-cli validate -s schema.json -d data.json\n\n# Using jsonschema (Python)\npip install jsonschema\npython3 -c \"\nimport json, jsonschema\nschema = json.load(open('schema.json'))\ndata = json.load(open('data.json'))\njsonschema.validate(data, schema)\nprint('Valid')\n\"\n\n# Validate multiple files\nfor f in data\u002F*.json; do\n  npx ajv-cli validate -s schema.json -d \"$f\" 2>&1 || echo \"INVALID: $f\"\ndone\n```\n\n## Zod (TypeScript)\n\n### Basic schemas\n\n```typescript\nimport { z } from 'zod';\n\n\u002F\u002F Primitives\nconst nameSchema = z.string().min(1).max(100);\nconst ageSchema = z.number().int().min(0).max(150);\nconst emailSchema = z.string().email();\nconst urlSchema = z.string().url();\n\n\u002F\u002F Objects\nconst userSchema = z.object({\n  name: z.string().min(1),\n  email: z.string().email(),\n  age: z.number().int().min(0),\n  role: z.enum(['user', 'admin', 'moderator']).default('user'),\n  tags: z.array(z.string()).max(10).default([]),\n  createdAt: z.string().datetime(),\n});\n\n\u002F\u002F Infer TypeScript type from schema\ntype User = z.infer\u003Ctypeof userSchema>;\n\u002F\u002F { name: string; email: string; age: number; role: \"user\" | \"admin\" | \"moderator\"; ... }\n\n\u002F\u002F Validate\nconst result = userSchema.safeParse(data);\nif (result.success) {\n  console.log(result.data); \u002F\u002F typed as User\n} else {\n  console.log(result.error.issues); \u002F\u002F validation errors\n}\n\n\u002F\u002F Parse (throws on invalid)\nconst user = userSchema.parse(data);\n```\n\n### Advanced patterns\n\n```typescript\n\u002F\u002F Optional and nullable\nconst schema = z.object({\n  name: z.string(),\n  nickname: z.string().optional(),       \u002F\u002F string | undefined\n  middleName: z.string().nullable(),     \u002F\u002F string | null\n  suffix: z.string().nullish(),          \u002F\u002F string | null | undefined\n});\n\n\u002F\u002F Transforms (validate then transform)\nconst dateSchema = z.string().datetime().transform(s => new Date(s));\nconst trimmed = z.string().trim().toLowerCase();\nconst parsed = z.string().transform(s => parseInt(s, 10)).pipe(z.number().int());\n\n\u002F\u002F Discriminated unions (tagged unions)\nconst eventSchema = z.discriminatedUnion('type', [\n  z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),\n  z.object({ type: z.literal('keypress'), key: z.string() }),\n  z.object({ type: z.literal('scroll'), delta: z.number() }),\n]);\n\n\u002F\u002F Recursive types\nconst categorySchema: z.ZodType\u003CCategory> = z.object({\n  name: z.string(),\n  children: z.lazy(() => z.array(categorySchema)).default([]),\n});\n\n\u002F\u002F Refinements (custom validation)\nconst passwordSchema = z.string()\n  .min(8)\n  .refine(s => \u002F[A-Z]\u002F.test(s), 'Must contain uppercase')\n  .refine(s => \u002F[0-9]\u002F.test(s), 'Must contain digit')\n  .refine(s => \u002F[^a-zA-Z0-9]\u002F.test(s), 'Must contain special character');\n\n\u002F\u002F Extend\u002Fmerge objects\nconst baseUser = z.object({ name: z.string(), email: z.string() });\nconst adminUser = baseUser.extend({ permissions: z.array(z.string()) });\n\n\u002F\u002F Pick\u002Fomit\nconst createUser = userSchema.omit({ createdAt: true });\nconst userSummary = userSchema.pick({ name: true, email: true });\n\n\u002F\u002F Passthrough (allow extra fields)\nconst flexible = userSchema.passthrough();\n\n\u002F\u002F Strip unknown fields\nconst strict = userSchema.strict(); \u002F\u002F Error on extra fields\n```\n\n### API validation with Zod\n\n```typescript\n\u002F\u002F Express middleware\nimport { z } from 'zod';\n\nconst createUserBody = z.object({\n  name: z.string().min(1),\n  email: z.string().email(),\n  password: z.string().min(8),\n});\n\napp.post('\u002Fapi\u002Fusers', (req, res) => {\n  const result = createUserBody.safeParse(req.body);\n  if (!result.success) {\n    return res.status(400).json({ errors: result.error.issues });\n  }\n  const { name, email, password } = result.data;\n  \u002F\u002F ... create user\n});\n\n\u002F\u002F Query parameter validation\nconst listParams = z.object({\n  page: z.coerce.number().int().min(1).default(1),\n  limit: z.coerce.number().int().min(1).max(100).default(20),\n  sort: z.enum(['newest', 'oldest', 'name']).default('newest'),\n  q: z.string().optional(),\n});\n\napp.get('\u002Fapi\u002Fusers', (req, res) => {\n  const params = listParams.parse(req.query);\n  \u002F\u002F params.page is a number, params.sort is typed\n});\n```\n\n## Pydantic (Python)\n\n### Basic models\n\n```python\nfrom pydantic import BaseModel, Field, EmailStr, field_validator\nfrom typing import Optional\nfrom datetime import datetime\nfrom enum import Enum\n\nclass Role(str, Enum):\n    USER = \"user\"\n    ADMIN = \"admin\"\n    MODERATOR = \"moderator\"\n\nclass Address(BaseModel):\n    street: str\n    city: str\n    zip_code: str = Field(pattern=r\"^\\d{5}(-\\d{4})?$\")\n\nclass User(BaseModel):\n    name: str = Field(min_length=1, max_length=100)\n    email: EmailStr\n    age: int = Field(ge=0, le=150)\n    role: Role = Role.USER\n    tags: list[str] = Field(default_factory=list, max_length=10)\n    address: Optional[Address] = None\n    created_at: datetime = Field(default_factory=datetime.now)\n\n    @field_validator(\"name\")\n    @classmethod\n    def name_must_not_be_empty(cls, v: str) -> str:\n        if not v.strip():\n            raise ValueError(\"name cannot be blank\")\n        return v.strip()\n\n# Validate\nuser = User(name=\"Alice\", email=\"alice@example.com\", age=30)\nprint(user.model_dump())      # dict\nprint(user.model_dump_json())  # JSON string\n\n# Validation errors\ntry:\n    User(name=\"\", email=\"bad\", age=-1)\nexcept Exception as e:\n    print(e)  # Detailed validation errors\n```\n\n### Advanced patterns\n\n```python\nfrom pydantic import BaseModel, model_validator, ConfigDict\nfrom typing import Literal, Union, Annotated\n\n# Discriminated union\nclass ClickEvent(BaseModel):\n    type: Literal[\"click\"]\n    x: int\n    y: int\n\nclass KeypressEvent(BaseModel):\n    type: Literal[\"keypress\"]\n    key: str\n\nEvent = Annotated[Union[ClickEvent, KeypressEvent], Field(discriminator=\"type\")]\n\n# Model-level validation (cross-field)\nclass DateRange(BaseModel):\n    start: datetime\n    end: datetime\n\n    @model_validator(mode=\"after\")\n    def end_after_start(self):\n        if self.end \u003C= self.start:\n            raise ValueError(\"end must be after start\")\n        return self\n\n# Strict mode (no type coercion)\nclass StrictUser(BaseModel):\n    model_config = ConfigDict(strict=True)\n    age: int  # \"30\" will be rejected, must be int 30\n\n# Alias (accept different field names in input)\nclass APIResponse(BaseModel):\n    user_name: str = Field(alias=\"userName\")\n    created_at: datetime = Field(alias=\"createdAt\")\n\n    model_config = ConfigDict(populate_by_name=True)\n\n# Computed fields\nfrom pydantic import computed_field\n\nclass Order(BaseModel):\n    items: list[dict]\n    tax_rate: float = 0.08\n\n    @computed_field\n    @property\n    def total(self) -> float:\n        subtotal = sum(i.get(\"price\", 0) * i.get(\"qty\", 1) for i in self.items)\n        return round(subtotal * (1 + self.tax_rate), 2)\n\n# Generate JSON Schema\nprint(User.model_json_schema())\n```\n\n### FastAPI integration\n\n```python\nfrom fastapi import FastAPI, Query\nfrom pydantic import BaseModel\n\napp = FastAPI()\n\nclass CreateUser(BaseModel):\n    name: str = Field(min_length=1)\n    email: EmailStr\n    password: str = Field(min_length=8)\n\nclass UserResponse(BaseModel):\n    id: int\n    name: str\n    email: str\n\n@app.post(\"\u002Fapi\u002Fusers\", response_model=UserResponse)\nasync def create_user(body: CreateUser):\n    # body is already validated\n    return {\"id\": 1, \"name\": body.name, \"email\": body.email}\n\n@app.get(\"\u002Fapi\u002Fusers\")\nasync def list_users(\n    page: int = Query(default=1, ge=1),\n    limit: int = Query(default=20, ge=1, le=100),\n    q: str | None = Query(default=None),\n):\n    # All params validated and typed\n    pass\n```\n\n## Data Integrity Checks\n\n### CSV validation\n\n```bash\n#!\u002Fbin\u002Fbash\n# validate-csv.sh — Check CSV structure and data quality\nFILE=\"${1:?Usage: validate-csv.sh \u003Cfile.csv>}\"\n\necho \"=== CSV Validation: $FILE ===\"\n\n# Row count\nROWS=$(wc -l \u003C \"$FILE\")\necho \"Rows: $ROWS (including header)\"\n\n# Column count consistency\nHEADER_COLS=$(head -1 \"$FILE\" | awk -F',' '{print NF}')\necho \"Columns (header): $HEADER_COLS\"\n\nBAD_ROWS=$(awk -F',' -v expected=\"$HEADER_COLS\" 'NR>1 && NF!=expected {count++} END {print count+0}' \"$FILE\")\nif [ \"$BAD_ROWS\" -gt 0 ]; then\n    echo \"ERROR: $BAD_ROWS rows have wrong column count\"\n    awk -F',' -v expected=\"$HEADER_COLS\" 'NR>1 && NF!=expected {print \"  Line \"NR\": \"NF\" columns (expected \"expected\")\"}' \"$FILE\" | head -5\nelse\n    echo \"Column count: consistent\"\nfi\n\n# Empty fields\nEMPTY=$(awk -F',' '{for(i=1;i\u003C=NF;i++) if($i==\"\") count++} END {print count}' \"$FILE\")\necho \"Empty fields: $EMPTY\"\n\n# Duplicate rows\nDUPES=$(($(sort \"$FILE\" | uniq -d | wc -l)))\necho \"Duplicate rows: $DUPES\"\n\necho \"=== Done ===\"\n```\n\n### JSON validation\n\n```bash\n# Check if file is valid JSON\njq empty data.json && echo \"Valid JSON\" || echo \"Invalid JSON\"\n\n# Validate structure of each object in an array\njq -e '\n  .[] |\n  select(\n    (.name | type) != \"string\" or\n    (.email | type) != \"string\" or\n    (.age | type) != \"number\" or\n    .age \u003C 0\n  )\n' data.json && echo \"INVALID records found\" || echo \"All records valid\"\n\n# Check for required fields\njq -e '.[] | select(.id == null or .name == null)' data.json\n\n# Check for unique IDs\njq '[.[].id] | length != (. | unique | length)' data.json\n# true = duplicates exist\n\n# Compare record counts between source and target\nSRC=$(jq length source.json)\nTGT=$(jq length target.json)\necho \"Source: $SRC, Target: $TGT, Match: $([ \"$SRC\" = \"$TGT\" ] && echo yes || echo NO)\"\n```\n\n### Migration validation\n\n```python\n#!\u002Fusr\u002Fbin\u002Fenv python3\n\"\"\"Validate that a data migration preserved all records.\"\"\"\nimport json\nimport sys\n\ndef validate_migration(source_path, target_path, key_field=\"id\"):\n    with open(source_path) as f:\n        source = {r[key_field]: r for r in json.load(f)}\n    with open(target_path) as f:\n        target = {r[key_field]: r for r in json.load(f)}\n\n    missing = set(source) - set(target)\n    extra = set(target) - set(source)\n    changed = []\n\n    for key in set(source) & set(target):\n        if source[key] != target[key]:\n            changed.append(key)\n\n    print(f\"Source records: {len(source)}\")\n    print(f\"Target records: {len(target)}\")\n    print(f\"Missing in target: {len(missing)}\")\n    print(f\"Extra in target: {len(extra)}\")\n    print(f\"Changed: {len(changed)}\")\n\n    if missing:\n        print(f\"\\nMissing IDs (first 10): {list(missing)[:10]}\")\n    if extra:\n        print(f\"\\nExtra IDs (first 10): {list(extra)[:10]}\")\n    if changed:\n        print(f\"\\nChanged IDs (first 5): {changed[:5]}\")\n        for key in changed[:3]:\n            print(f\"\\n  {key}:\")\n            for field in set(source[key]) | set(target[key]):\n                s = source[key].get(field)\n                t = target[key].get(field)\n                if s != t:\n                    print(f\"    {field}: {s!r} → {t!r}\")\n\n    return len(missing) == 0 and len(extra) == 0\n\nif __name__ == \"__main__\":\n    ok = validate_migration(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else \"id\")\n    sys.exit(0 if ok else 1)\n```\n\n## Tips\n\n- Validate at system boundaries (API endpoints, file imports, message queues), not deep inside business logic. Trust internal data.\n- Zod and Pydantic both generate JSON Schema from their definitions. Use this for documentation, OpenAPI specs, and cross-language contracts.\n- `additionalProperties: false` in JSON Schema catches typos in field names. Use it for strict APIs.\n- Pydantic v2 is significantly faster than v1. Use `model_config = ConfigDict(strict=True)` when you want no implicit type coercion.\n- Zod's `.safeParse()` returns a result object; `.parse()` throws. Use `safeParse` in API handlers to return structured errors.\n- For CSV validation, always check column count consistency first — most downstream errors trace back to misaligned columns.\n- Data migration validation should compare record counts, check for missing\u002Fextra records, and sample-check field values. Counting alone isn't enough.\n","# Data Validation\n\nSchema-based data validation across languages and formats. Covers JSON Schema, Zod (TypeScript), Pydantic (Python), API boundary validation, data contracts, and integrity checking.\n\n## When to Use\n\n- Defining the shape of API request\u002Fresponse bodies\n- Validating user input before processing\n- Setting up data contracts between services\n- Checking CSV\u002FJSON file integrity before import\n- Migrating data (did the ETL preserve everything?)\n- Generating types or documentation from schemas\n\n## JSON Schema\n\n### Basic schema\n\n```json\n{\n  \"$schema\": \"https:\u002F\u002Fjson-schema.org\u002Fdraft\u002F2020-12\u002Fschema\",\n  \"type\": \"object\",\n  \"required\": [\"name\", \"email\", \"age\"],\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"maxLength\": 100\n    },\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"age\": {\n      \"type\": \"integer\",\n      \"minimum\": 0,\n      \"maximum\": 150\n    },\n    \"role\": {\n      \"type\": \"string\",\n      \"enum\": [\"user\", \"admin\", \"moderator\"],\n      \"default\": \"user\"\n    },\n    \"tags\": {\n      \"type\": \"array\",\n      \"items\": { \"type\": \"string\" },\n      \"uniqueItems\": true,\n      \"maxItems\": 10\n    },\n    \"address\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"street\": { \"type\": \"string\" },\n        \"city\": { \"type\": \"string\" },\n        \"zip\": { \"type\": \"string\", \"pattern\": \"^\\\\d{5}(-\\\\d{4})?$\" }\n      },\n      \"required\": [\"street\", \"city\"]\n    }\n  },\n  \"additionalProperties\": false\n}\n```\n\n### Common patterns\n\n```json\n\u002F\u002F Nullable field\n{ \"type\": [\"string\", \"null\"] }\n\n\u002F\u002F Union type (string or number)\n{ \"oneOf\": [{ \"type\": \"string\" }, { \"type\": \"number\" }] }\n\n\u002F\u002F Conditional: if role is admin, require permissions\n{\n  \"if\": { \"properties\": { \"role\": { \"const\": \"admin\" } } },\n  \"then\": { \"required\": [\"permissions\"] }\n}\n\n\u002F\u002F Pattern properties (dynamic keys)\n{\n  \"type\": \"object\",\n  \"patternProperties\": {\n    \"^env_\": { \"type\": \"string\" }\n  }\n}\n\n\u002F\u002F Reusable definitions\n{\n  \"$defs\": {\n    \"address\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"street\": { \"type\": \"string\" },\n        \"city\": { \"type\": \"string\" }\n      }\n    }\n  },\n  \"properties\": {\n    \"home\": { \"$ref\": \"#\u002F$defs\u002Faddress\" },\n    \"work\": { \"$ref\": \"#\u002F$defs\u002Faddress\" }\n  }\n}\n```\n\n### Validate with command line\n\n```bash\n# Using ajv-cli (Node.js)\nnpx ajv-cli validate -s schema.json -d data.json\n\n# Using jsonschema (Python)\npip install jsonschema\npython3 -c \"\nimport json, jsonschema\nschema = json.load(open('schema.json'))\ndata = json.load(open('data.json'))\njsonschema.validate(data, schema)\nprint('Valid')\n\"\n\n# Validate multiple files\nfor f in data\u002F*.json; do\n  npx ajv-cli validate -s schema.json -d \"$f\" 2>&1 || echo \"INVALID: $f\"\ndone\n```\n\n## Zod (TypeScript)\n\n### Basic schemas\n\n```typescript\nimport { z } from 'zod';\n\n\u002F\u002F Primitives\nconst nameSchema = z.string().min(1).max(100);\nconst ageSchema = z.number().int().min(0).max(150);\nconst emailSchema = z.string().email();\nconst urlSchema = z.string().url();\n\n\u002F\u002F Objects\nconst userSchema = z.object({\n  name: z.string().min(1),\n  email: z.string().email(),\n  age: z.number().int().min(0),\n  role: z.enum(['user', 'admin', 'moderator']).default('user'),\n  tags: z.array(z.string()).max(10).default([]),\n  createdAt: z.string().datetime(),\n});\n\n\u002F\u002F Infer TypeScript type from schema\ntype User = z.infer\u003Ctypeof userSchema>;\n\u002F\u002F { name: string; email: string; age: number; role: \"user\" | \"admin\" | \"moderator\"; ... }\n\n\u002F\u002F Validate\nconst result = userSchema.safeParse(data);\nif (result.success) {\n  console.log(result.data); \u002F\u002F typed as User\n} else {\n  console.log(result.error.issues); \u002F\u002F validation errors\n}\n\n\u002F\u002F Parse (throws on invalid)\nconst user = userSchema.parse(data);\n```\n\n### Advanced patterns\n\n```typescript\n\u002F\u002F Optional and nullable\nconst schema = z.object({\n  name: z.string(),\n  nickname: z.string().optional(),       \u002F\u002F string | undefined\n  middleName: z.string().nullable(),     \u002F\u002F string | null\n  suffix: z.string().nullish(),          \u002F\u002F string | null | undefined\n});\n\n\u002F\u002F Transforms (validate then transform)\nconst dateSchema = z.string().datetime().transform(s => new Date(s));\nconst trimmed = z.string().trim().toLowerCase();\nconst parsed = z.string().transform(s => parseInt(s, 10)).pipe(z.number().int());\n\n\u002F\u002F Discriminated unions (tagged unions)\nconst eventSchema = z.discriminatedUnion('type', [\n  z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),\n  z.object({ type: z.literal('keypress'), key: z.string() }),\n  z.object({ type: z.literal('scroll'), delta: z.number() }),\n]);\n\n\u002F\u002F Recursive types\nconst categorySchema: z.ZodType\u003CCategory> = z.object({\n  name: z.string(),\n  children: z.lazy(() => z.array(categorySchema)).default([]),\n});\n\n\u002F\u002F Refinements (custom validation)\nconst passwordSchema = z.string()\n  .min(8)\n  .refine(s => \u002F[A-Z]\u002F.test(s), 'Must contain uppercase')\n  .refine(s => \u002F[0-9]\u002F.test(s), 'Must contain digit')\n  .refine(s => \u002F[^a-zA-Z0-9]\u002F.test(s), 'Must contain special character');\n\n\u002F\u002F Extend\u002Fmerge objects\nconst baseUser = z.object({ name: z.string(), email: z.string() });\nconst adminUser = baseUser.extend({ permissions: z.array(z.string()) });\n\n\u002F\u002F Pick\u002Fomit\nconst createUser = userSchema.omit({ createdAt: true });\nconst userSummary = userSchema.pick({ name: true, email: true });\n\n\u002F\u002F Passthrough (allow extra fields)\nconst flexible = userSchema.passthrough();\n\n\u002F\u002F Strip unknown fields\nconst strict = userSchema.strict(); \u002F\u002F Error on extra fields\n```\n\n### API validation with Zod\n\n```typescript\n\u002F\u002F Express middleware\nimport { z } from 'zod';\n\nconst createUserBody = z.object({\n  name: z.string().min(1),\n  email: z.string().email(),\n  password: z.string().min(8),\n});\n\napp.post('\u002Fapi\u002Fusers', (req, res) => {\n  const result = createUserBody.safeParse(req.body);\n  if (!result.success) {\n    return res.status(400).json({ errors: result.error.issues });\n  }\n  const { name, email, password } = result.data;\n  \u002F\u002F ... create user\n});\n\n\u002F\u002F Query parameter validation\nconst listParams = z.object({\n  page: z.coerce.number().int().min(1).default(1),\n  limit: z.coerce.number().int().min(1).max(100).default(20),\n  sort: z.enum(['newest', 'oldest', 'name']).default('newest'),\n  q: z.string().optional(),\n});\n\napp.get('\u002Fapi\u002Fusers', (req, res) => {\n  const params = listParams.parse(req.query);\n  \u002F\u002F params.page is a number, params.sort is typed\n});\n```\n\n## Pydantic (Python)\n\n### Basic models\n\n```python\nfrom pydantic import BaseModel, Field, EmailStr, field_validator\nfrom typing import Optional\nfrom datetime import datetime\nfrom enum import Enum\n\nclass Role(str, Enum):\n    USER = \"user\"\n    ADMIN = \"admin\"\n    MODERATOR = \"moderator\"\n\nclass Address(BaseModel):\n    street: str\n    city: str\n    zip_code: str = Field(pattern=r\"^\\d{5}(-\\d{4})?$\")\n\nclass User(BaseModel):\n    name: str = Field(min_length=1, max_length=100)\n    email: EmailStr\n    age: int = Field(ge=0, le=150)\n    role: Role = Role.USER\n    tags: list[str] = Field(default_factory=list, max_length=10)\n    address: Optional[Address] = None\n    created_at: datetime = Field(default_factory=datetime.now)\n\n    @field_validator(\"name\")\n    @classmethod\n    def name_must_not_be_empty(cls, v: str) -> str:\n        if not v.strip():\n            raise ValueError(\"name cannot be blank\")\n        return v.strip()\n\n# Validate\nuser = User(name=\"Alice\", email=\"alice@example.com\", age=30)\nprint(user.model_dump())      # dict\nprint(user.model_dump_json())  # JSON string\n\n# Validation errors\ntry:\n    User(name=\"\", email=\"bad\", age=-1)\nexcept Exception as e:\n    print(e)  # Detailed validation errors\n```\n\n### Advanced patterns\n\n```python\nfrom pydantic import BaseModel, model_validator, ConfigDict\nfrom typing import Literal, Union, Annotated\n\n# Discriminated union\nclass ClickEvent(BaseModel):\n    type: Literal[\"click\"]\n    x: int\n    y: int\n\nclass KeypressEvent(BaseModel):\n    type: Literal[\"keypress\"]\n    key: str\n\nEvent = Annotated[Union[ClickEvent, KeypressEvent], Field(discriminator=\"type\")]\n\n# Model-level validation (cross-field)\nclass DateRange(BaseModel):\n    start: datetime\n    end: datetime\n\n    @model_validator(mode=\"after\")\n    def end_after_start(self):\n        if self.end \u003C= self.start:\n            raise ValueError(\"end must be after start\")\n        return self\n\n# Strict mode (no type coercion)\nclass StrictUser(BaseModel):\n    model_config = ConfigDict(strict=True)\n    age: int  # \"30\" will be rejected, must be int 30\n\n# Alias (accept different field names in input)\nclass APIResponse(BaseModel):\n    user_name: str = Field(alias=\"userName\")\n    created_at: datetime = Field(alias=\"createdAt\")\n\n    model_config = ConfigDict(populate_by_name=True)\n\n# Computed fields\nfrom pydantic import computed_field\n\nclass Order(BaseModel):\n    items: list[dict]\n    tax_rate: float = 0.08\n\n    @computed_field\n    @property\n    def total(self) -> float:\n        subtotal = sum(i.get(\"price\", 0) * i.get(\"qty\", 1) for i in self.items)\n        return round(subtotal * (1 + self.tax_rate), 2)\n\n# Generate JSON Schema\nprint(User.model_json_schema())\n```\n\n### FastAPI integration\n\n```python\nfrom fastapi import FastAPI, Query\nfrom pydantic import BaseModel\n\napp = FastAPI()\n\nclass CreateUser(BaseModel):\n    name: str = Field(min_length=1)\n    email: EmailStr\n    password: str = Field(min_length=8)\n\nclass UserResponse(BaseModel):\n    id: int\n    name: str\n    email: str\n\n@app.post(\"\u002Fapi\u002Fusers\", response_model=UserResponse)\nasync def create_user(body: CreateUser):\n    # body is already validated\n    return {\"id\": 1, \"name\": body.name, \"email\": body.email}\n\n@app.get(\"\u002Fapi\u002Fusers\")\nasync def list_users(\n    page: int = Query(default=1, ge=1),\n    limit: int = Query(default=20, ge=1, le=100),\n    q: str | None = Query(default=None),\n):\n    # All params validated and typed\n    pass\n```\n\n## Data Integrity Checks\n\n### CSV validation\n\n```bash\n#!\u002Fbin\u002Fbash\n# validate-csv.sh — Check CSV structure and data quality\nFILE=\"${1:?Usage: validate-csv.sh \u003Cfile.csv>}\"\n\necho \"=== CSV Validation: $FILE ===\"\n\n# Row count\nROWS=$(wc -l \u003C \"$FILE\")\necho \"Rows: $ROWS (including header)\"\n\n# Column count consistency\nHEADER_COLS=$(head -1 \"$FILE\" | awk -F',' '{print NF}')\necho \"Columns (header): $HEADER_COLS\"\n\nBAD_ROWS=$(awk -F',' -v expected=\"$HEADER_COLS\" 'NR>1 && NF!=expected {count++} END {print count+0}' \"$FILE\")\nif [ \"$BAD_ROWS\" -gt 0 ]; then\n    echo \"ERROR: $BAD_ROWS rows have wrong column count\"\n    awk -F',' -v expected=\"$HEADER_COLS\" 'NR>1 && NF!=expected {print \"  Line \"NR\": \"NF\" columns (expected \"expected\")\"}' \"$FILE\" | head -5\nelse\n    echo \"Column count: consistent\"\nfi\n\n# Empty fields\nEMPTY=$(awk -F',' '{for(i=1;i\u003C=NF;i++) if($i==\"\") count++} END {print count}' \"$FILE\")\necho \"Empty fields: $EMPTY\"\n\n# Duplicate rows\nDUPES=$(($(sort \"$FILE\" | uniq -d | wc -l)))\necho \"Duplicate rows: $DUPES\"\n\necho \"=== Done ===\"\n```\n\n### JSON validation\n\n```bash\n# Check if file is valid JSON\njq empty data.json && echo \"Valid JSON\" || echo \"Invalid JSON\"\n\n# Validate structure of each object in an array\njq -e '\n  .[] |\n  select(\n    (.name | type) != \"string\" or\n    (.email | type) != \"string\" or\n    (.age | type) != \"number\" or\n    .age \u003C 0\n  )\n' data.json && echo \"INVALID records found\" || echo \"All records valid\"\n\n# Check for required fields\njq -e '.[] | select(.id == null or .name == null)' data.json\n\n# Check for unique IDs\njq '[.[].id] | length != (. | unique | length)' data.json\n# true = duplicates exist\n\n# Compare record counts between source and target\nSRC=$(jq length source.json)\nTGT=$(jq length target.json)\necho \"Source: $SRC, Target: $TGT, Match: $([ \"$SRC\" = \"$TGT\" ] && echo yes || echo NO)\"\n```\n\n### Migration validation\n\n```python\n#!\u002Fusr\u002Fbin\u002Fenv python3\n\"\"\"Validate that a data migration preserved all records.\"\"\"\nimport json\nimport sys\n\ndef validate_migration(source_path, target_path, key_field=\"id\"):\n    with open(source_path) as f:\n        source = {r[key_field]: r for r in json.load(f)}\n    with open(target_path) as f:\n        target = {r[key_field]: r for r in json.load(f)}\n\n    missing = set(source) - set(target)\n    extra = set(target) - set(source)\n    changed = []\n\n    for key in set(source) & set(target):\n        if source[key] != target[key]:\n            changed.append(key)\n\n    print(f\"Source records: {len(source)}\")\n    print(f\"Target records: {len(target)}\")\n    print(f\"Missing in target: {len(missing)}\")\n    print(f\"Extra in target: {len(extra)}\")\n    print(f\"Changed: {len(changed)}\")\n\n    if missing:\n        print(f\"\\nMissing IDs (first 10): {list(missing)[:10]}\")\n    if extra:\n        print(f\"\\nExtra IDs (first 10): {list(extra)[:10]}\")\n    if changed:\n        print(f\"\\nChanged IDs (first 5): {changed[:5]}\")\n        for key in changed[:3]:\n            print(f\"\\n  {key}:\")\n            for field in set(source[key]) | set(target[key]):\n                s = source[key].get(field)\n                t = target[key].get(field)\n                if s != t:\n                    print(f\"    {field}: {s!r} → {t!r}\")\n\n    return len(missing) == 0 and len(extra) == 0\n\nif __name__ == \"__main__\":\n    ok = validate_migration(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else \"id\")\n    sys.exit(0 if ok else 1)\n```\n\n## Tips\n\n- Validate at system boundaries (API endpoints, file imports, message queues), not deep inside business logic. Trust internal data.\n- Zod and Pydantic both generate JSON Schema from their definitions. Use this for documentation, OpenAPI specs, and cross-language contracts.\n- `additionalProperties: false` in JSON Schema catches typos in field names. Use it for strict APIs.\n- Pydantic v2 is significantly faster than v1. Use `model_config = ConfigDict(strict=True)` when you want no implicit type coercion.\n- Zod's `.safeParse()` returns a result object; `.parse()` throws. Use `safeParse` in API handlers to return structured errors.\n- For CSV validation, always check column count consistency first — most downstream errors trace back to misaligned columns.\n- Data migration validation should compare record counts, check for missing\u002Fextra records, and sample-check field values. Counting alone isn't enough.\n",{"name":5,"metadata":48,"description":7},{"clawdbot":49},{"os":50,"emoji":51,"requires":52},[42,27,41],"✅",{"anyBins":53},[54,26,55],"node","jq","main","f4cb2d49f7667e79ef7804f50396d5aaffdf1f56","网页与前端开发\u002Fdata-validation",1,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification","https:\u002F\u002Fgithub.com\u002FKernel8901","a little little little dobe in life but science geek","2026-04-05 16:43:56",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add Kernel8901\u002Fai-agent-skills-classification --skill data-validation",{"tool":70,"command":71},"bunx","bunx skills add Kernel8901\u002Fai-agent-skills-classification --skill data-validation",{"tool":73,"command":74},"pnpm","pnpm dlx skills add Kernel8901\u002Fai-agent-skills-classification --skill data-validation","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Fdata-validation\u002FSKILL.md",true,[78],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"_meta.json","REFERENCE","参考文档",294,"https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F网页与前端开发\u002Fdata-validation\u002F_meta.json",[85,108,140,151,161,190],{"skillNo":86,"name":87,"nameZh":6,"description":88,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":91,"publishedAt":43},"01M6Q5R6WZ06RRB9RQTW54B34B","zyfai","Earn yield on any Ethereum wallet on Base, Arbitrum, and Plasma. Use when a user wants passive DeFi yield on their funds. Deploys a non-custodial subaccount (Safe) linked to their EOA, enables automated yield optimization, and lets them deposit\u002Fwithdraw anytime.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzyfai-sdk\u002FSKILL.md",[],[87,92,93,94,95,96,33,97,98,99,100,101,102,103,104,105,106,107],"safe","ethereum","defi","Optimization","user","Wallet","base","deposit","withdraw","yield","arbitrum","non-custodial","passive","defi yield","earn","yield optimization",{"skillNo":109,"name":110,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":124,"publishedAt":43},"01M6Q5R337B5JSNER4P4YS03TN","zerion-api","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api\u002FSKILL.md",[113,118,121],{"categoryNo":114,"categoryName":115,"taxonomyType":116,"taxonomyTypeDesc":117},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":119,"categoryName":120,"taxonomyType":116,"taxonomyTypeDesc":117},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":122,"categoryName":123,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000030","开发工程师",[36,125,126,127,128,129,130,94,131,97,132,133,134,135,136,30,137,138,139],"zerion api","zerion","MCP","mcp server","server","blockchain","transactions","crypto wallet","crypto","evm","solana","query","positions","token","prices",{"skillNo":141,"name":142,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":148,"publishedAt":43},"01M6Q5QZFYTDAZMZ27BN5VZXAR","zerion-api-skill","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api-skill\u002FSKILL.md",[145,146,147],{"categoryNo":114,"categoryName":115,"taxonomyType":116,"taxonomyTypeDesc":117},{"categoryNo":119,"categoryName":120,"taxonomyType":116,"taxonomyTypeDesc":117},{"categoryNo":122,"categoryName":123,"taxonomyType":20,"taxonomyTypeDesc":21},[36,149,150,125,126,127,128,129,130,94,131,97,133,134,135,136,30,137,138,139],"api skill","zerion api skill",{"skillNo":152,"name":153,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":154,"categories":155,"keywords":159,"publishedAt":43},"01M6Q5QW17QW98RA3FE45E4RD8","zerion-api-mcp","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fzerion-api-mcp\u002FSKILL.md",[156,157,158],{"categoryNo":114,"categoryName":115,"taxonomyType":116,"taxonomyTypeDesc":117},{"categoryNo":119,"categoryName":120,"taxonomyType":116,"taxonomyTypeDesc":117},{"categoryNo":122,"categoryName":123,"taxonomyType":20,"taxonomyTypeDesc":21},[127,36,160,125,126,128,129,94,131,97,132,133,134,135,136,30,137,138,139],"zerion api mcp",{"skillNo":162,"name":163,"nameZh":6,"description":164,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":165,"categories":166,"keywords":170,"publishedAt":43},"01M6Q5QQYZB7A501BDEQWKGSJT","yahoo-finance-forex","Fetches real-time FOREX news and market data from Yahoo Finance for major currency pairs (EUR\u002FUSD, GBP\u002FUSD, USD\u002FJPY, etc.). Analyzes sentiment and provides trading context.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fyahoo-finance-forex\u002FSKILL.md",[167],{"categoryNo":168,"categoryName":169,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000040","财务",[171,172,173,174,175,176,177,178,179,26,180,181,182,183,184,185,186,187,188,189],"finance","yahoo finance","forex","yahoo","yahoo finance forex","github","https","sentiment","real-time","install","Market","usd","news","time","currency","gbp","pip","market data","for",{"skillNo":191,"name":192,"nameZh":6,"description":193,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":194,"categories":195,"keywords":197,"publishedAt":43},"01M6Q5QM6VS84WZVM7BKXXPF32","yahoo-finance","This skill should be used when the user asks to \"get stock prices\", \"check stock quotes\", \"look up earnings\", \"get financial data\", \"find trending stocks\", or needs stock market data from Yahoo Finance.","https:\u002F\u002Fgithub.com\u002FKernel8901\u002Fai-agent-skills-classification\u002Fblob\u002Fmain\u002F金融\u002Fyahoo-finance-cli\u002FSKILL.md",[196],{"categoryNo":168,"categoryName":169,"taxonomyType":20,"taxonomyTypeDesc":21},[171,172,174,198,199,200,201,202,31,181,203,204,205,206,207,208,209,210,139,188],"cli","homebrew","find","check","brew","local","get","stock","link","trending","sf","quotes","ln"]