递归补丁类型定义
Recursively patch type definition
我正在尝试编写一个泛型类型,它将类型作为参数(可以是普通对象、数组、基元等)并在它是普通对象时重新映射值类型,或者添加一些由 Configuration
类型描述的配置指令的数组。
我们称该假设修饰符为 Configurable<T>
。 T
可以是任何复杂嵌套的实体。 Book
可以是 T
的值,例如:
type Configuration = {
$test: {
option1: boolean;
option2: string;
}
};
type Book = {
id: string;
title: string;
author: string;
related: Array<string>;
};
type Result = Configurable<Book>;
然后我希望 Configurable<Book>
正确地对以下表达式进行类型检查,其中值可以是实际值或配置对象:
const expr1: Configurable<Book> = {
id: "1",
title: "Harry Potter",
author: "J.K. Rowling",
related: ["2", "3"]
}
const expr2: Configurable<Book> = {
id: "2",
title: "Harry Potter",
author: {
$test: {
option1: true,
option2: "something"
}
},
related: []
}
const expr3: Configurable<Book> = {
id: "3",
title: "Harry Potter",
author: "J.K. Rowling",
related: ["2", {
$test: {
option1: true,
option2: "something"
}
}]
}
const expr4: Configurable<Book> = {
id: "4",
title: true, // ERROR: should be string or Configuration
author: "J.K. Rowling",
related: ["2", "3"]
}
const expr5: Configurable<Book> = {
id: "5",
title: "Harry Potter",
author: "J.K. Rowling",
related: {
$test: {
option1: true,
option2: "something"
}
} // ERROR: should be an array of (string | Configuration)
}
嵌套对象或数组不应该被 Configuration
替换,只有在需要原始值的地方(参见 expr5
)。
这是我尝试过的:
type Configuration = {
$test: {
option1: boolean;
option2: string;
};
};
type Configurable<T> = Record<string, any> extends T
? {
[K in keyof T]: Configurable<T[K]> | Configuration;
}
: T extends Array<infer U>
? Array<Configurable<U>>
: T;
但这会使 expr2
和 expr3
失败。
如果我正确理解您的要求,您可以使用 Configurable
的定义:
type Configurable<T> = T extends object ?
{ [K in keyof T]: Configurable<T[K]> } :
T | Configuration;
The object
type corresponds to any non-primitive, including arrays. If T extends object
is not true, then T
is a primitive and you want to accept T | Configuration
. If T extends object
is true, then you map over its properties with Configurable
. This should automatically do the right thing with arrays and tuples, since mapped tuples and arrays produce tuples and arrays.
让我们试试看:
const expr1: Configurable<Book> = {
id: "1",
title: "Harry Potter",
author: "J.K. Rowling",
related: ["2", "3"]
}
const expr2: Configurable<Book> = {
id: "2",
title: "Harry Potter",
author: {
$test: {
option1: true,
option2: "something"
}
},
related: []
}
const expr3: Configurable<Book> = {
id: "3",
title: "Harry Potter",
author: "J.K. Rowling",
related: ["2", {
$test: {
option1: true,
option2: "something"
}
}]
}
以上示例按要求编译无错误。让我们检查错误:
const expr4: Configurable<Book> = {
id: "4",
title: true, // ERROR: should be string or Configuration
author: "J.K. Rowling",
related: ["2", "3"]
}
const expr5: Configurable<Book> = {
id: "5",
title: "Harry Potter",
author: "J.K. Rowling",
related: {
$test: {
option1: true,
option2: "something"
}
} // ERROR: should be an array of (string | Configuration)
}
确实产生了你想要的错误。看起来不错!
我正在尝试编写一个泛型类型,它将类型作为参数(可以是普通对象、数组、基元等)并在它是普通对象时重新映射值类型,或者添加一些由 Configuration
类型描述的配置指令的数组。
我们称该假设修饰符为 Configurable<T>
。 T
可以是任何复杂嵌套的实体。 Book
可以是 T
的值,例如:
type Configuration = {
$test: {
option1: boolean;
option2: string;
}
};
type Book = {
id: string;
title: string;
author: string;
related: Array<string>;
};
type Result = Configurable<Book>;
然后我希望 Configurable<Book>
正确地对以下表达式进行类型检查,其中值可以是实际值或配置对象:
const expr1: Configurable<Book> = {
id: "1",
title: "Harry Potter",
author: "J.K. Rowling",
related: ["2", "3"]
}
const expr2: Configurable<Book> = {
id: "2",
title: "Harry Potter",
author: {
$test: {
option1: true,
option2: "something"
}
},
related: []
}
const expr3: Configurable<Book> = {
id: "3",
title: "Harry Potter",
author: "J.K. Rowling",
related: ["2", {
$test: {
option1: true,
option2: "something"
}
}]
}
const expr4: Configurable<Book> = {
id: "4",
title: true, // ERROR: should be string or Configuration
author: "J.K. Rowling",
related: ["2", "3"]
}
const expr5: Configurable<Book> = {
id: "5",
title: "Harry Potter",
author: "J.K. Rowling",
related: {
$test: {
option1: true,
option2: "something"
}
} // ERROR: should be an array of (string | Configuration)
}
嵌套对象或数组不应该被 Configuration
替换,只有在需要原始值的地方(参见 expr5
)。
这是我尝试过的:
type Configuration = {
$test: {
option1: boolean;
option2: string;
};
};
type Configurable<T> = Record<string, any> extends T
? {
[K in keyof T]: Configurable<T[K]> | Configuration;
}
: T extends Array<infer U>
? Array<Configurable<U>>
: T;
但这会使 expr2
和 expr3
失败。
如果我正确理解您的要求,您可以使用 Configurable
的定义:
type Configurable<T> = T extends object ?
{ [K in keyof T]: Configurable<T[K]> } :
T | Configuration;
The object
type corresponds to any non-primitive, including arrays. If T extends object
is not true, then T
is a primitive and you want to accept T | Configuration
. If T extends object
is true, then you map over its properties with Configurable
. This should automatically do the right thing with arrays and tuples, since mapped tuples and arrays produce tuples and arrays.
让我们试试看:
const expr1: Configurable<Book> = {
id: "1",
title: "Harry Potter",
author: "J.K. Rowling",
related: ["2", "3"]
}
const expr2: Configurable<Book> = {
id: "2",
title: "Harry Potter",
author: {
$test: {
option1: true,
option2: "something"
}
},
related: []
}
const expr3: Configurable<Book> = {
id: "3",
title: "Harry Potter",
author: "J.K. Rowling",
related: ["2", {
$test: {
option1: true,
option2: "something"
}
}]
}
以上示例按要求编译无错误。让我们检查错误:
const expr4: Configurable<Book> = {
id: "4",
title: true, // ERROR: should be string or Configuration
author: "J.K. Rowling",
related: ["2", "3"]
}
const expr5: Configurable<Book> = {
id: "5",
title: "Harry Potter",
author: "J.K. Rowling",
related: {
$test: {
option1: true,
option2: "something"
}
} // ERROR: should be an array of (string | Configuration)
}
确实产生了你想要的错误。看起来不错!