假设一个schema对象验证一个data对象.

有没有什么黑魔法可以让我们在schema型守卫之后缩小data型属性的范围?

片段胜过千言万语:

function validate(data: Data, schema: Schema) {}

interface Data {
  [key: string]: number | string | undefined;
}

interface Schema {
  properties: {
    [key: string]: { type: "number" | "string" } | undefined;
  };
}

// Just for demo purposes: we don't know its exact shape until run time.
const schema: Schema = {
  properties: {
    firstName: {
      type: "string",
    },
  },
};

// Idem.
const data: Data = {
  firstName: "John",
};

validate(data, schema); // What kind of magic could happen here...

if (schema.properties.firstName?.type === "string") {
  const firstName: string = data.firstName; // ...so that there's no error here?
}

Playground

我正在考虑将Schema转换为一个具有自定义getter函数的类,该函数将依赖asserts关键字来相应地键入data属性.(1) 我不确定这是否可行,(2)可能有更好的方法.

知道吗?

推荐答案

不幸的是,TypeScript的类型系统不足以表示schemadata的属性之间的任意相关性.它需要microsoft/TypeScript#30581中描述的correlated unions,以及每种可能的对象类型的"无限"并集,如microsoft/TypeScript#14466中所要求的existential types...但是TypeScript并不直接支持这两种类型.

我想说的是,您需要重构成将schemadata封装在一个对象中的东西,它公开了一些方法来获得discriminated union对模式类型和属性值.可能是这样:

function validate(data: Data, schema: Schema) {
  // do validation, and then
  return function prop(k: string) {
    return { type: schema.properties[k]?.type, data: data[k] } as
      { type: "number", data: number } |
      { type: "string", data: string } |
      { type: undefined, data: undefined }
  }
}

因此,当你调用validate(data, schema)时,你会得到一个验证函数:

const vf = validate(data, schema);

这个函数可以为任何给定的属性调用:

const firstNameProp = vf("firstname");

然后是类型/数据值的判别并集:

/* const firstNameProp: {
    type: "number";
    data: number;
} | {
    type: "string";
    data: string;
} | {
    type: undefined;
    data: undefined;
} */

可以按照您想要的方式进行判断:

if (firstNameProp.type === "string") {
  const firstName: string = firstNameProp.data;
} else {
  firstNameProp.data // number | undefined now
}

它不是great,但在类型系统中至少要表示possible.

Playground link to code

Typescript相关问答推荐

为什么typescript要把这个空合并转换成三元?

如何通过TypeScript中的工厂函数将区分的联合映射到具体的类型(如类)?

隐式键入脚本键映射使用

Vue SFC中的多个脚本块共享导入的符号

类型,其中一条记录为字符串,其他记录为指定的

为什么&;(交集)运算符会删除不相交的属性?

为什么在leetcode问题的测试用例中,即使我确实得到了比预期更好的解决方案,这段代码也失败了?

防止重复使用 Select 器重新渲染

如何将对象数组中的键映射到另一种对象类型的键?

S,为什么我的T扩展未定义的条件在属性的上下文中不起作用?

TaskListProps[]类型不可分配给TaskListProps类型

如何创建一个将嵌套类属性的点表示法生成为字符串文字的类型?

仅当类型为联合类型时映射属性类型

我的类型谓词函数不能像我预期的那样工作.需要帮助了解原因

Vite+Chrome扩展 list v3-;不能在模块外使用import语句;对于inpage脚本

可以';t使用t正确地索引对象;联合;索引类型

带有过滤键的 Typescript 映射类型

广义泛型类型不加载抽象类型上下文

为什么 typescript 在对象合并期间无法判断无效键?

为什么在 useState 中设置 React 组件是一种不好的做法?