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 模式的方法  

使用 JSON 模式的方法  

选择图书馆  

使用哪种方法和库,取决于我们需要什么:

  • 如果我们从 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 代码。

©️李联华的博客网 当前IP地址:3.15.34.131,归属地:俄亥俄州Dublin ,欢迎您的访问!

温馨提示 : 非特殊注明,否则均为李联华的博客原创文章,本站文章未经授权禁止任何形式转载
文章链接:https://www.ooize.com/typescript-validate-external-data.html
订阅
提醒
guest
0 评论
内联反馈
查看所有评论
Loading...