Typescript 将未知类型转换为接口类型

2024-01-08

我想写一个函数asA它接受一个类型的参数unknown并将其作为特定的接口类型返回A,或者如果参数与接口类型不匹配则抛出错误A.

该解决方案应该是稳健的。 IE。如果向我的界面类型添加一个新字段A,编译器应该抱怨我的函数缺少对新字段的检查,直到我修复它。

下面是此类函数的示例asA,但它不起作用。编译器说:

元素隐式具有“any”类型,因为类型为“a”的表达式 不能用于索引类型“{}”。类型上不存在属性“a” “{}”。(7053)

interface A {
    a: string
}

function asA(data:unknown): A {
    if (typeof data === 'object' && data !== null) {
        if ('a' in data && typeof data['a'] === 'string') {
            return data;
        }
    }
    throw new Error('data is not an A');

}

let data:unknown = JSON.parse('{"a": "yes"}');
let a = asA(data);

我怎样才能写一个函数asA如上所述?

我很喜欢使用类型转换,例如(data as any)['a'],只要添加新字段时不出现静默失败A.


您可以使用现有的解决方案,例如typescript-is https://www.npmjs.com/package/typescript-is,尽管这可能需要您切换到ttypescript(允许插件的编译器的自定义构建)

如果您需要自定义解决方案,我们可以使用纯 TS 构建一个解决方案。首先是要求:

  • 验证属性是否属于特定类型
  • 确保新字段经过验证。

最后一个要求可以通过拥有一个具有相同键的对象来满足A,所有必需的键和值是属性的类型。这样一个对象的类型是Record<keyof A, Types>。然后这个对象可以用作验证的源,我们可以获取每个键并验证它的指定类型:

interface A {
  a: string
}

type Types = "string" | "number" | "boolean";
function asA(data: unknown): A {
  const keyValidators: Record<keyof A, Types> = {
    a: "string"
  }
  if (typeof data === 'object' && data !== null) {
    let maybeA = data as A
    for (const key of Object.keys(keyValidators) as Array<keyof A>) {
      if (typeof maybeA[key] !== keyValidators[key]) {
        throw new Error('data is not an A');
      }
    }
    return maybeA;
  }
  throw new Error('data is not an A');

}

let data: unknown = JSON.parse('{"a": "yes"}');
let a = asA(data);

我们可以更进一步,创建一个可以验证任何对象类型的通用工厂函数,我们还可以允许一些额外的事情,例如指定函数或允许可选属性:

interface A {
  a: string
  opt?: string
  // b: number // error if you add b
}

function asOptional<T>(as: (s: unknown, errMsg?: string) => T) {
  return function (s: unknown, errMsg?: string): T | undefined {
    if (s === undefined) return s;
    return as(s);
  }
}

function asString(s: unknown, errMsg: string = ""): string {
  if (typeof s === "string") return s as string
  throw new Error(`${errMsg} '${s} is not a string`)
}

function asNumber(s: unknown, errMsg?: string): number {
  if (typeof s === "number") return s as number;
  throw new Error(`${errMsg} '${s} is not a string`)
}

type KeyValidators<T> = {
  [P in keyof T]-?: (s: unknown, errMsg?: string) => T[P]
}

function asFactory<T extends object>(keyValidators:KeyValidators<T>) {
  return function (data: unknown, errMsg: string = ""): T {
    console.log(data);
    if (typeof data === 'object' && data !== null) {
      let maybeT = data as T
      for (const key of Object.keys(keyValidators) as Array<keyof T>) {
        keyValidators[key](maybeT[key], errMsg + key + ":");
      }
      return maybeT;
    }
    throw new Error(errMsg + 'data is not an A');
  }
}

let data: unknown = JSON.parse('{"a": "yes"}');
const asA = asFactory<A>({
  a: asString,
  opt: asOptional(asString)
  /// b: asNumber
})
let a = asA(data);

interface B {
  a: A
}

const asB = asFactory<B>({
  a: asA
})

let data2: unknown = JSON.parse('{ "a": {"a": "yes"} }');
let b = asB(data2);
let berr = asB(data);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Typescript 将未知类型转换为接口类型 的相关文章