我想编写一个函数asA
,它接受类型参数unknown
并将其作为特定接口类型返回A
,或者如果参数与接口类型不匹配则抛出错误A
。
该解决方案应该是稳健的。即,如果向我的接口类型添加一个新字段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);
Run Code Online (Sandbox Code Playgroud)
如何编写asA
如上所述的函数?
我可以使用类型转换,例如(data as any)['a']
,只要将新字段添加到 时不会出现静默失败A
。
Tit*_*mir 11
您可以使用现有的解决方案,例如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);
Run Code Online (Sandbox Code Playgroud)
我们可以更进一步,创建一个可以验证任何对象类型的通用工厂函数,我们还可以允许一些额外的事情,例如指定函数或允许可选属性:
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);
Run Code Online (Sandbox Code Playgroud)
除了像ts-json-validator这样的库之外,您还可以使用“用户定义的类型保护”,但对于许多类型来说,这样做可能会变得有点冗长。
使用类型保护,你可以做这样的事情。请注意,我编写的函数返回 true 或 false,但其返回类型被注释为data is A
。
interface A {
a: string
}
function assertIsA(data: unknown): data is A {
const isA = (typeof data === 'object') && ('a' in (data as any) && typeof (data as any)['a'] === 'string')
if (isA === false)
throw new Error('data is not an A');
return isA
}
let data: unknown = JSON.parse('{"a": "yes"}');
if (assertIsA(data)) { // returns true
console.log(data.a) // within the conditional data is of type A
}
// all of these throw
console.log(assertIsA(null))
console.log(assertIsA(undefined))
console.log(assertIsA({}))
console.log(assertIsA([]))
console.log(assertIsA({b: 'no'}))
console.log(assertIsA('no'))
console.log(assertIsA(12345))
Run Code Online (Sandbox Code Playgroud)
如果你不需要抛出整个事情可以减少到一行:
function assertIsA(data: unknown): data is A {
return (typeof data === 'object') && ('a' in (data as any) && typeof (data as any)['a'] === 'string')
}
Run Code Online (Sandbox Code Playgroud)
或者
const assertIsA = (data: unknown): data is A => (typeof data === 'object') && ('a' in (data as any) && typeof (data as any)['a'] === 'string')
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
33955 次 |
最近记录: |