TypeScript:验证外部数据
数据验证意味着确保数据具有所需的结构和内容。
使用 TypeScript,当我们收到外部数据时,验证变得相关,例如:
- 从 JSON 文件解析的数据
- 从网络服务接收的数据
在这些情况下,我们希望数据适合我们拥有的静态类型,但我们不能确定。与我们自己创建的数据相比,TypeScript 会不断检查一切是否正确。
这篇博文解释了如何在 TypeScript 中验证外部数据。
JSON 模式
在我们探索 TypeScript 中的数据验证方法之前,我们需要先了解一下JSON 模式,因为有几种方法是基于它的。
JSON 模式背后的思想是用JSON来表达JSON 数据的模式(结构和内容,认为是静态类型)。也就是说,元数据以与数据相同的格式表示。
JSON 模式的用例是:
- 验证 JSON 数据:如果我们有数据的模式定义,我们可以使用工具来检查数据是否正确。数据的一个问题也可以自动修复:我们可以指定可用于添加缺少的属性的默认值。
- 记录 JSON 数据格式:一方面,核心模式定义可以被视为文档。但是 JSON 模式还支持描述、弃用说明、注释、示例等。这些机制称为注解。它们不用于验证,而是用于文档。
- IDE 支持编辑数据:例如,Visual Studio Code 支持 JSON 架构。如果有 JSON 文件的模式,我们将获得几个编辑功能:自动完成、错误突出显示等。值得注意的是,VS Code 对
package.json文件的支持完全基于 JSON 模式。
一个示例 JSON 模式
这个例子是取自该json-schema.org网站:
{
"$id": "https://example.com/geographical-location.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Longitude and Latitude Values",
"description": "A geographical coordinate.",
"required": [ "latitude", "longitude" ],
"type": "object",
"properties": {
"latitude": {
"type": "number",
"minimum": -90,
"maximum": 90
},
"longitude": {
"type": "number",
"minimum": -180,
"maximum": 180
}
}
}
以下 JSON 数据在此架构中有效:
{
"latitude": 48.858093,
"longitude": 2.294694
}
TypeScript 中的数据验证方法
本节简要概述了在 TypeScript 中验证数据的各种方法。对于每种方法,我都会列出一个或多个支持该方法的库。Wrt 库,我不打算全面,因为这个领域的事情变化很快。
不使用 JSON 模式的方法
- 方法:调用构建器方法和函数来生成验证函数(在运行时)和静态类型(在编译时)。图书馆包括:
- 方法:调用构建器方法并仅生成验证函数。采用这种方法的图书馆通常专注于使验证尽可能通用:
- 方法:在编译时将 TypeScript 类型编译为验证代码。图书馆:
使用 JSON 模式的方法
- 方法:将 TypeScript 类型转换为 JSON 模式。图书馆:
- 方法:将 JSON 模式转换为 TypeScript 类型。图书馆:
- 方法:通过 JSON 模式验证 JSON 数据。前两种方法也倾向于这样做。npm 包:
选择图书馆
使用哪种方法和库,取决于我们需要什么:
- 如果我们从 TypeScript 类型开始并希望确保数据(来自配置文件等)适合这些类型,那么支持静态类型的构建器 API 是一个不错的选择。
- 如果我们的起点是 JSON 模式,那么我们应该考虑支持 JSON 模式的库之一。
- 如果我们正在处理更混乱的数据(例如通过表单提交),我们可能需要一种更灵活的方法,其中静态类型的作用较小。
示例:通过库Zod 验证数据
通过 Zod 的构建器 API 定义“模式”
Zod 有一个生成器 API,可以生成类型和验证函数。该 API 的用法如下:
import * as z from 'zod';
const FileEntryInputSchema = z.union([
z.string(),
z.tuple([z.string(), z.string(), z.array(z.string())]),
z.object({
file: z.string(),
author: z.string().optional(),
tags: z.array(z.string()).optional(),
}),
]);
对于较大的模式,将事物分解为多个const声明是有意义的。
Zod 可以从 生成静态类型FileEntryInputSchema,但我决定(冗余!)手动维护静态类型FileEntryInput:
type FileEntryInput =
| string
| [string, string, string[]]
| {file: string, author?: string, tags?: string[]}
;
为什么要裁员?
- 阅读起来更容易。
- 如果我必须这样做,它有助于迁移到不同的验证库或方法。
Zod 生成的类型仍然有用,因为我们可以检查它是否可以分配给FileEntryInput. 这将警告我们与两者不同步相关的大多数问题。
验证数据
以下函数检查参数是否data符合FileEntryInputSchema:
function validateData(data: unknown): FileEntryInput {
return FileEntryInputSchema.parse(data); // may throw an exception
}
validateData(['iceland.txt', 'me', ['vacation', 'family']]); // OK
assert.throws(
() => validateData(['iceland.txt', 'me']));
结果的静态类型FileEntryInputSchema.parse()是 Zod 派生的FileEntryInputSchema。通过使FileEntryInput返回类型validateData(),我们确保前一种类型可分配给后者。
类型保护
FileEntryInputSchema.check() 是一个类型保护:
function func(data: unknown) {
if (FileEntryInputSchema.check(data)) {
// %inferred-type: string
// | [string, string, string[]]
// | { author?: string | undefined; tags?: string[] | undefined; file: string; }
data;
}
}
定义一个支持FileEntryInput而不是 Zod 推断的自定义类型保护是有意义的。
function isValidData(data: unknown): data is FileEntryInput {
return FileEntryInputSchema.check(data);
}
从 Zod 模式派生静态类型
参数化类型z.infer<Schema>可用于从模式派生类型:
// %inferred-type: string
// | [string, string, string[]]
// | { author?: string | undefined; tags?: string[] | undefined; file: string; }
type FileEntryInputStatic = z.infer<typeof FileEntryInputSchema>;
数据的外部与内部表示
在处理外部数据时,区分两种类型通常很有用。
一方面,有描述输入数据的类型。它的结构经过优化,易于创作:
type FileEntryInput =
| string
| [string, string, string[]]
| {file: string, author?: string, tags?: string[]}
;
另一方面,还有程序中使用的类型。其结构经过优化,易于在代码中使用:
type FileEntry = {
file: string,
author: null|string,
tags: string[],
};
在我们使用 Zod 确保输入数据符合 之后FileEntryInput,我们使用转换函数将数据转换为类型的值FileEntry。
结论
我的数据验证库用例是确保数据匹配给定的 TypeScript 类型。因此,我宁愿直接将类型编译为验证函数。到目前为止,只有 Babel 宏可以typecheck.macro做到这一点,它需要 Babel 为我排除了它。我想我也可以使用将 TypeScript 类型编译为具有验证功能的单独模块的工具。但这也有缺点,在可用性方面。
因此,Zod 目前对我来说是一个很好的解决方案,我没有任何遗憾。
对于具有构建器 API 的库,我希望拥有将 TypeScript 类型编译为构建器 API 调用的工具(在线和通过命令行)。这将在两个方面有所帮助:
- 这些工具可用于探索 API 的工作方式。
- 我们可以选择通过工具生成 API 代码。
文章链接:https://www.lilianhua.com/typescript-validate-external-data.html
English (US)
Español (ES)
Português (PT)
Français (CA)
Español (MX)
Español (VE)
Español (CO)
Español (AR)
Português (BR)
Quechua (PE)
Guaraní (PY)
简体中文 (ZH)
繁體中文 (HK)
日本語 (JP)
한국어 (KR)
हिन्दी (HI)
Pilipino (PH)
ไทย (TH)
Tiếng Việt (VN)
Bahasa Melayu (MY)
Bahasa Indonesia (ID)
বাংলা (BD)
اردو (PK)
සිංහල (LK)
ភាសាខ្មែរ (KH)
English (UK)
Français (FR)
Deutsch (DE)
Italiano (IT)
Русский (RU)
Nederlands (NL)
Türkçe (TR)
Polski (PL)
Svenska (SE)
Norsk (NO)
Dansk (DK)
Suomi (FI)
Ελληνικά (GR)
Čeština (CZ)
Magyar (HU)
Română (RO)
Български (BG)
Српски (RS)
Українська (UA)


