[{"data":1,"prerenderedAt":478},["ShallowReactive",2],{"content:\u002Fposts\u002F2026\u002Fzod-vs-valibot":3,"surround:\u002Fposts\u002F2026\u002Fzod-vs-valibot":467},{"id":4,"title":5,"body":6,"canonical":446,"categories":447,"date":449,"description":450,"draft":451,"extension":452,"image":453,"meta":454,"navigation":455,"path":456,"permalink":446,"readingTime":457,"recommend":446,"references":446,"seo":462,"sitemap":463,"stem":464,"tags":465,"type":446,"updated":449,"__hash__":466},"content\u002Fposts\u002F2026\u002Fzod-vs-valibot.md","Zod 和 Valibot 的异同",{"type":7,"value":8,"toc":428},"minimark",[9,13,18,22,77,82,104,107,111,129,133,139,157,160,163,181,184,187,205,208,215,229,247,251,254,261,272,290,293,313,317,328,337,341,355,358,420,423],[10,11,12],"p",{},"两者的官网如下：",[14,15],"link-card",{"link":16,"title":17},"https:\u002F\u002Fzod.dev\u002F","Zod",[14,19],{"link":20,"title":21},"https:\u002F\u002Fvalibot.dev\u002F","Valibot",[23,24,25,32,38,49,56,61,70],"folding",{},[26,27,29],"template",{"v-slot:title":28},"",[10,30,31],{},"我这里准备了一些示例数据和获取数据的方法。接下来所有的操作都是基于这些数据来操作。",[10,33,34],{},[35,36,37],"strong",{},"数据",[39,40,46],"pre",{"className":41,"code":43,"filename":44,"language":45,"meta":28},[42],"language-json","{\n  \"username\": \"月空人\",\n  \"email\": \"whbbit@prop.show\",\n  \"site\": \"https:\u002F\u002Fwww.prop.show\",\n  \"org\": \"PropShow\",\n  \"reading\": [\n    {\n      \"title\": \"Vue.js 设计与实现\",\n      \"author\": \"霍春阳\",\n    },\n    {\n      \"title\": \"Vue.js 设计与实现\",\n      \"author\": \"霍春阳\",\n    },\n  ],\n}\n","mockdata\u002Fuser.json","json",[47,48,43],"code",{"__ignoreMap":28},[39,50,54],{"className":51,"code":52,"filename":53,"language":45,"meta":28},[42],"{\n  \"LOGIN_CONFIG\": \"{\\\"qqLogin\\\":2,\\\"wechatLogin\\\":true}\",\n}\n","mockdata\u002Fconfig.json",[47,55,52],{"__ignoreMap":28},[10,57,58],{},[35,59,60],{},"获取数据的方法",[39,62,68],{"className":63,"code":65,"filename":66,"language":67,"meta":28},[64],"language-ts","import fs from 'node:fs\u002Fpromises'\n\nexport const userData = await fs.readFile('.\u002Fmockdata\u002Fuser.json', 'utf-8')\n    .then(val => JSON.parse(val))\n","api\u002Fuser.ts","ts",[47,69,65],{"__ignoreMap":28},[39,71,75],{"className":72,"code":73,"filename":74,"language":67,"meta":28},[64],"import fs from 'node:fs\u002Fpromises'\n\nexport const configData = await fs.readFile('.\u002Fmockdata\u002Fconfig.json', 'utf-8')\n    .then(val => JSON.parse(val))\n","api\u002Fconfig.ts",[47,76,73],{"__ignoreMap":28},[78,79,81],"h2",{"id":80},"基础的数据-schema-的写法","基础的数据 Schema 的写法",[83,84,86,95],"tab",{":tabs":85},"[\"Zod\", \"Valibot\"]",[26,87,88],{"v-slot:tab1":28},[39,89,93],{"className":90,"code":91,"filename":92,"language":67,"meta":28},[64],"import * as z from \"zod\";\n\nimport { userData } from \"..\u002Fapi\u002Fuser\";\n\nconst UserSchema = z.object({\n    username: z.string(),\n    email: z.email(),\n    site: z.url(),\n    org: z.string(),\n    reading: z.array(\n        z.object({\n            title: z.string(),\n            author: z.string(),\n        }),\n    ),\n});\n","validation\u002Fzod.ts",[47,94,91],{"__ignoreMap":28},[26,96,97],{"v-slot:tab2":28},[39,98,102],{"className":99,"code":100,"filename":101,"language":67,"meta":28},[64],"import * as v from \"valibot\";\n\nimport { userData } from \"..\u002Fapi\u002Fuser\";\n\nconst UserSchema = v.object({\n    username: v.string(),\n    email: v.pipe(v.string(), v.email()),\n    site: v.pipe(v.string(), v.url()),\n    org: v.string(),\n    reading: v.array(\n        v.object({\n            title: v.string(),\n            author: v.string(),\n        }),\n    ),\n});\n","validation\u002Fvalibot.ts",[47,103,100],{"__ignoreMap":28},[78,105,106],{"id":106},"对数据的的校验",[108,109,110],"h3",{"id":110},"基础校验",[83,112,113,121],{":tabs":85},[26,114,115],{"v-slot:tab1":28},[39,116,119],{"className":117,"code":118,"filename":92,"language":67,"meta":28},[64],"UserSchema.parse(userData)\n",[47,120,118],{"__ignoreMap":28},[26,122,123],{"v-slot:tab2":28},[39,124,127],{"className":125,"code":126,"filename":101,"language":67,"meta":28},[64],"v.parse(UserSchema, userData)\n",[47,128,126],{"__ignoreMap":28},[108,130,132],{"id":131},"safeparse","safeParse",[10,134,135,136,138],{},"这样书写后如果报错会抛出一个错误阻碍后续代码操作，它们都提供了一个 ",[47,137,132],{"code":132}," 的写法",[83,140,141,149],{":tabs":85},[26,142,143],{"v-slot:tab1":28},[39,144,147],{"className":145,"code":146,"filename":92,"language":67,"meta":28},[64],"const result = UserSchema.safeParse(userData);\nif (!result.success) {\n    console.error(result.error);\n}\nelse {\n    console.log(result.data);\n}\n",[47,148,146],{"__ignoreMap":28},[26,150,151],{"v-slot:tab2":28},[39,152,155],{"className":153,"code":154,"filename":101,"language":67,"meta":28},[64],"const result = v.safeParse(UserSchema, userData);\n\nif (!result.success) {\n    console.error(result.issues);\n}\nelse {\n    console.log(result.output);\n}\n",[47,156,154],{"__ignoreMap":28},[108,158,159],{"id":159},"美化错误输出",[10,161,162],{},"这时的错误是不太容易阅读的，它们也都提供了美化错误输出的写法",[83,164,165,173],{":tabs":85},[26,166,167],{"v-slot:tab1":28},[39,168,171],{"className":169,"code":170,"filename":92,"language":67,"meta":28},[64],"const result = UserSchema.safeParse(userData);\nif (!result.success) {\n    console.error(z.flattenError(result.error));\n}\nelse {\n    console.log(result.data);\n}\n",[47,172,170],{"__ignoreMap":28},[26,174,175],{"v-slot:tab2":28},[39,176,179],{"className":177,"code":178,"filename":101,"language":67,"meta":28},[64],"const result = v.safeParse(UserSchema, userData);\n\nif (!result.success) {\n    console.error(v.flatten(result.issues));\n}\nelse {\n    console.log(result.output);\n}\n",[47,180,178],{"__ignoreMap":28},[108,182,183],{"id":183},"自定义数据校验",[10,185,186],{},"它们提供的数据校验都很全了，但是有时我们需要一个自定义的规则，比如我这里需要 username 必须是纯中文的字符，这时我们该如何操作呢？",[83,188,189,197],{":tabs":85},[26,190,191],{"v-slot:tab1":28},[39,192,195],{"className":193,"code":194,"filename":92,"language":67,"meta":28},[64],"const UserSchema = z.object({\n    username: z.string().regex(\u002F^[\\u4E00-\\u9FA5]+$\u002F, { \u002F\u002F [!code ++]\n        error: \"用户名必须是中文\", \u002F\u002F [!code ++]\n    }), \u002F\u002F [!code ++]\n    email: z.email(),\n    site: z.url(),\n    org: z.string(),\n    reading: z.array(\n        z.object({\n            title: z.string(),\n            author: z.string(),\n        }),\n    ),\n});\n",[47,196,194],{"__ignoreMap":28},[26,198,199],{"v-slot:tab2":28},[39,200,203],{"className":201,"code":202,"filename":101,"language":67,"meta":28},[64],"import { dash } from \"radash\";\n\nconst UserSchema = v.object({\n    username: v.pipe( \u002F\u002F [!code ++]\n        v.string(), \u002F\u002F [!code ++]\n        v.regex(\u002F^[\\u4E00-\\u9FA5]+$\u002F, \"用户名必须是中文\"), \u002F\u002F [!code ++]\n    ), \u002F\u002F [!code ++]\n    email: v.pipe(v.string(), v.email()),\n    site: v.pipe(v.string(), v.url()),\n    org:\n    v.string(),\n    reading: v.array(\n        v.object({\n            title: v.string(),\n            author: v.string(),\n        }),\n    ),\n});\n",[47,204,202],{"__ignoreMap":28},[78,206,207],{"id":207},"自动格式化数据",[10,209,210,211,214],{},"我们有时候需要对拿到的数据进行一些操作，我们可以在 Zod 和 Validate 中使用它们的 ",[47,212,213],{"code":213},"transform"," 来做。",[10,216,217,218,221,222,225,226],{},"下面的示例中，我们把 org 字符串转换为使用 ",[47,219,220],{"code":220},"-"," 连接。 ",[47,223,224],{"code":224},"PropShow"," -> ",[47,227,228],{"code":228},"prop-show",[83,230,231,239],{":tabs":85},[26,232,233],{"v-slot:tab1":28},[39,234,237],{"className":235,"code":236,"filename":92,"language":67,"meta":28},[64],"import { dash } from \"radash\";\n\nconst UserSchema = z.object({\n    username: z.string().regex(\u002F^[\\u4E00-\\u9FA5]+$\u002F, {\n        error: \"用户名必须是中文\",\n    }),\n    email: z.email(),\n    site: z.url(),\n    org: z.string().transform(val => dash(val)), \u002F\u002F [!code ++]\n    reading: z.array(\n        z.object({\n            title: z.string(),\n            author: z.string(),\n        }),\n    ),\n});\n",[47,238,236],{"__ignoreMap":28},[26,240,241],{"v-slot:tab2":28},[39,242,245],{"className":243,"code":244,"filename":101,"language":67,"meta":28},[64],"const UserSchema = v.object({\n    username: v.pipe(\n        v.string(),\n        v.regex(\u002F^[\\u4E00-\\u9FA5]+$\u002F, \"用户名必须是中文\"),\n    ),\n    email: v.pipe(v.string(), v.email()),\n    site: v.pipe(v.string(), v.url()),\n    org: v.pipe( \u002F\u002F [!code ++]\n        v.string(), \u002F\u002F [!code ++]\n        v.transform(val => dash(val)), \u002F\u002F [!code ++]\n    ), \u002F\u002F [!code ++]\n    reading: v.array(\n        v.object({\n            title: v.string(),\n            author: v.string(),\n        }),\n    ),\n});\n",[47,246,244],{"__ignoreMap":28},[78,248,250],{"id":249},"对-json-字符串进行转换和校验","对 JSON 字符串进行转换和校验",[10,252,253],{},"我们还会遇到的一个情况就是从数据库中读出一个 JONS 格式的字符串，我们在使用的时候大部分时候都需要将其转换为标准的 JavaScript 对象。我们如何对这个 JSON 格式的字符串进行校验和自动转换为标准的 JavaScript 对象呢？",[10,255,256,257,260],{},"在上面我们准备的数据中，",[47,258,259],{"code":259},"configData"," 就是这样一个数据。它代表我们可以使用的第三方登录平台。",[10,262,263,264,267,268,271],{},"我们要做的事就是校验它是否是正确的包含 ",[47,265,266],{"code":266},"qqLogin"," 和 ",[47,269,270],{"code":270},"wechatLogin"," 的对象",[83,273,274,282],{":tabs":85},[26,275,276],{"v-slot:tab1":28},[39,277,280],{"className":278,"code":279,"filename":92,"language":67,"meta":28},[64],"const LoginConfigSchema = z.object({\n    qqLogin: z.boolean(),\n    wechatLogin: z.boolean(),\n});\n\nconst LoginConfigStringSchema = z.object({\n    LOGIN_CONFIG: z\n        .string()\n        .transform(val => JSON.parse(val))\n        .pipe(LoginConfigSchema),\n});\n\nconst result = LoginConfigStringSchema.safeParse(configData);\n",[47,281,279],{"__ignoreMap":28},[26,283,284],{"v-slot:tab2":28},[39,285,288],{"className":286,"code":287,"filename":101,"language":67,"meta":28},[64],"const LoginConfigSchema = v.object({\n    qqLogin: v.boolean(),\n    wechatLogin: v.boolean(),\n});\n\nconst LoginConfigStringSchema = v.object({\n    LOGIN_CONFIG: v.pipe(\n        v.string(),\n        v.transform(val => JSON.parse(val)),\n        v.pipe(LoginConfigSchema),\n    ),\n});\n\nconst result = v.safeParse(LoginConfigStringSchema, configData);\n",[47,289,287],{"__ignoreMap":28},[10,291,292],{},"或者我们转过来，我们在调用接口的时候，有时候需要我们传递一个标准的 JSON 字符串，我们这时候也可以转过来，将 Object 自动转换为字符串",[83,294,295,304],{":tabs":85},[26,296,297],{"v-slot:tab1":28},[39,298,302],{"className":299,"code":300,"filename":301,"language":67,"meta":28},[64],"const configData = {\n    LOGIN_CONFIG: {\n        qqLogin: true,\n        wechatLogin: true\n    }\n}\nconst LoginConfigStringSchema = z.object({\n    LOGIN_CONFIG: z.object({\n        qqLogin: z.boolean(),\n        wechatLogin: z.boolean(),\n    }).transform(val => JSON.parse(val))\n});\nconst result = v.safeParse(LoginConfigStringSchema, configData);\nconsole.log(result.data)\n\u002F\u002F 输出结果\n\u002F\u002F {\n\u002F\u002F   LOGIN_CONFIG: \"{\\\"qqLogin\\\":true,\\\"wechatLogin\\\":true}\",\n\u002F\u002F }\n","zod.ts",[47,303,300],{"__ignoreMap":28},[26,305,306],{"v-slot:tab2":28},[39,307,311],{"className":308,"code":309,"filename":310,"language":67,"meta":28},[64],"const configData = {\n    LOGIN_CONFIG: {\n        qqLogin: true,\n        wechatLogin: true\n    }\n}\nconst LoginConfigStringSchema = v.object({\n    LOGIN_CONFIG: v.pipe(v.object({\n        qqLogin: v.boolean(),\n        wechatLogin: v.boolean(),\n    }),\n    v.transform(val => JSON.stringify(val)))\n});\nconst result = v.safeParse(LoginConfigStringSchema, configData);\nconsole.log(result.output)\n\u002F\u002F 输出结果\n\u002F\u002F {\n\u002F\u002F   LOGIN_CONFIG: \"{\\\"qqLogin\\\":true,\\\"wechatLogin\\\":true}\",\n\u002F\u002F }\n","valibot.ts",[47,312,309],{"__ignoreMap":28},[78,314,316],{"id":315},"中文错误的支持-i18n","中文错误的支持 —— i18n",[10,318,319,320,323,324,327],{},"在 Zod 中我们可以直接使用 ",[47,321,322],{"code":322},"z.config(z.locales.zhCN())"," 来配置，在 Valibot 中使用 ",[47,325,326],{"code":326},"v.setGlobalConfig({ lang: 'zh-CN' })"," 进行配置。",[329,330,331],"blockquote",{},[10,332,333,334],{},"Valibot 你需要安装并引入 ",[47,335,336],{"code":336},"@valibot\u002Fi18n",[78,338,340],{"id":339},"json-schema","JSON Schema",[10,342,343,344,347,348,351,352],{},"在 Zod 中你可以直接使用 ",[47,345,346],{"code":346},"z.toJSONSchema(LoginConfigStringSchema)","；在 Valibot 中你需要安装 ",[47,349,350],{"code":350},"@valibot\u002Fto-json-schema","，使用方式为 ",[47,353,354],{"code":354},"v.safeParse(LoginConfigStringSchema, configData)",[78,356,357],{"id":357},"整体比较",[359,360,361,375],"table",{},[362,363,364],"thead",{},[365,366,367,371,373],"tr",{},[368,369,370],"th",{},"比较项",[368,372,17],{},[368,374,21],{},[376,377,378,390],"tbody",{},[365,379,380,384,387],{},[381,382,383],"td",{},"写法",[381,385,386],{},"链式操作",[381,388,389],{},"函数组合",[365,391,392,395,400],{},[381,393,394],{},"大小",[381,396,397],{},[47,398,399],{"code":399},"268.3k",[381,401,402,403,406,407,409,410,413,414,409,417],{},"本体大小 ",[47,404,405],{"code":405},"80.4k","， ",[47,408,350],{"code":350}," 大小为 ",[47,411,412],{"code":412},"8.4k",", ",[47,415,416],{"code":416},"@valibot\u002Fi18n\u002Fzh-CN",[47,418,419],{"code":419},"8.2k",[78,421,422],{"id":422},"搭配视频观看",[424,425],"video-embed",{"id":426,"type":427},"BV1EcisBxErT","bilibili",{"title":28,"searchDepth":429,"depth":429,"links":430},4,[431,433,440,441,442,443,444,445],{"id":80,"depth":432,"text":81},2,{"id":106,"depth":432,"text":106,"children":434},[435,437,438,439],{"id":110,"depth":436,"text":110},3,{"id":131,"depth":436,"text":132},{"id":159,"depth":436,"text":159},{"id":183,"depth":436,"text":183},{"id":207,"depth":432,"text":207},{"id":249,"depth":432,"text":250},{"id":315,"depth":432,"text":316},{"id":339,"depth":432,"text":340},{"id":357,"depth":432,"text":357},{"id":422,"depth":432,"text":422},null,[448],"代码","2026-01-06 14:54:01","本文将比较 Zod 和 Valibot 之间的区别，帮助你在选择校验库时做一点参考",false,"md","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002Fzod-vs-valibot.png",{},true,"\u002Fposts\u002F2026\u002Fzod-vs-valibot",{"text":458,"minutes":459,"time":460,"words":461},"6 min read",5.495,329700,1099,{"title":5,"description":450},{"loc":456},"posts\u002F2026\u002Fzod-vs-valibot",[17,21],"ps1kVhLVcyp-39a3ibh4qToL7b23L6YgcaGcEwI2nQw",[468,473],{"title":469,"path":470,"stem":471,"date":472,"type":446,"children":-1},"HTTP 状态码和短语","\u002Fposts\u002F2026\u002Fhttp-status-codes-and-phrases-cheat-sheet","posts\u002F2026\u002Fhttp-status-codes-and-phrases-cheat-sheet","2026-01-04 11:15:21",{"title":474,"path":475,"stem":476,"date":477,"type":446,"children":-1},"使用 oxlint + oxfmt 替换 ESLint + Prettier","\u002Fposts\u002F2026\u002Fmigrate-eslint-and-prettier-to-oxlint-and-oxfmt","posts\u002F2026\u002Fmigrate-eslint-and-prettier-to-oxlint-and-oxfmt","2026-02-10 08:49:04",1789642587655]