是否可以纯粹使用 TypeScript 为对象创建对象键路径字符串自动完成?
Is it possible to create object key path string autocomplete for an object purely with TypeScript?
const val = {
a: {
b: {
c: 1
}
}
};
我有很多不同结构的对象。我有一组函数需要遍历对象中的数据。我想利用 TypeScript 来帮助我确保输入是整个过程中的关键。
像这样都可以:
myFn('a.b.c');
myFn('a', 'b', 'c');
如果用户类型:
myFn('a.x.c');
myFn('a', 'b', 'x');
这些应该作为错误突出显示。
可能吗?
关于第一个例子,用dot
表示法,你可以用这个例子:
type Foo = {
user: {
description: {
name: string;
surname: string;
}
}
}
declare var foo: Foo;
/**
* Common utils
*/
type Primitives = string | number | symbol;
/**
* Obtain a union of all values of object
*/
type Values<T> = T[keyof T]
type Elem = string;
type Acc = Record<string, any>
/**
* Custom user defined typeguard
*/
const hasProperty = <Obj, Prop extends Primitives>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, any> =>
Object.prototype.hasOwnProperty.call(obj, prop);
/**
* Obtain value by object property name
*/
type Predicate<Accumulator extends Acc, El extends Elem> =
El extends keyof Accumulator ? Accumulator[El] : Accumulator
/**
* If first argument is empty string, avoid using dot (.)
* If first argument is non empty string concat two arguments andput dot (.)
* between them
*/
type Concat<Fst, Scd> =
Fst extends string
? Scd extends string
? Fst extends ''
? `${Scd}`
: `${Fst}.${Scd}`
: never
: never
{
type Test = Concat<'hello','bye'> // "hello.bye"
}
/**
* Obtain union of all possible paths
*/
type KeysUnion<T, Cache extends string = ''> =
T extends Primitives ? Cache : {
[P in keyof T]:
| Concat<Cache, P>
| KeysUnion<T[P], Concat<Cache, P>>
}[keyof T]
{
// "user" | "user.description" | "user.description.name" | "user.description.surname"
type Test = KeysUnion<Foo>
}
/**
* Get object value by path
*/
type GetValueByPath<T extends string, Cache extends Acc = {}> =
T extends `${infer Head}.${infer Tail}`
? GetValueByPath<Tail, Predicate<Cache, Head>>
: Predicate<Cache, T>
{
// {
// name: string;
// surname: string;
// }
type Test = GetValueByPath<"user.description", Foo>
}
/**
* Obtain union of all possible values,
* including nested values
*/
type ValuesUnion<T, Cache = T> =
T extends Primitives ? T : Values<{
[P in keyof T]:
| Cache | T[P]
| ValuesUnion<T[P], Cache | T[P]>
}>
/**
* Function overloading
*/
function deepPickFinal<Obj, Keys extends KeysUnion<Obj>>
(obj: ValuesUnion<Obj>, keys: Keys): GetValueByPath<Keys, Obj>
function deepPickFinal<Obj, Keys extends KeysUnion<Obj> & Array<string>>
(obj: ValuesUnion<Obj>, ...keys: Keys) {
return keys
.reduce(
(acc, elem) => hasProperty(acc, elem) ? acc[elem] : acc,
obj
)
}
/**
* Ok
*/
const result = deepPickFinal(foo, 'user.description') // ok { name: string; surname: string; }
const result2 = deepPickFinal(foo, 'user') // ok
您可能已经注意到,return 类型也会被推断出来。
完整的描述和解释,您可以在 my article.
中找到
如果你想使用剩余参数,comma separated
,请参考我的文章
const val = {
a: {
b: {
c: 1
}
}
};
我有很多不同结构的对象。我有一组函数需要遍历对象中的数据。我想利用 TypeScript 来帮助我确保输入是整个过程中的关键。
像这样都可以:
myFn('a.b.c');
myFn('a', 'b', 'c');
如果用户类型:
myFn('a.x.c');
myFn('a', 'b', 'x');
这些应该作为错误突出显示。
可能吗?
关于第一个例子,用dot
表示法,你可以用这个例子:
type Foo = {
user: {
description: {
name: string;
surname: string;
}
}
}
declare var foo: Foo;
/**
* Common utils
*/
type Primitives = string | number | symbol;
/**
* Obtain a union of all values of object
*/
type Values<T> = T[keyof T]
type Elem = string;
type Acc = Record<string, any>
/**
* Custom user defined typeguard
*/
const hasProperty = <Obj, Prop extends Primitives>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, any> =>
Object.prototype.hasOwnProperty.call(obj, prop);
/**
* Obtain value by object property name
*/
type Predicate<Accumulator extends Acc, El extends Elem> =
El extends keyof Accumulator ? Accumulator[El] : Accumulator
/**
* If first argument is empty string, avoid using dot (.)
* If first argument is non empty string concat two arguments andput dot (.)
* between them
*/
type Concat<Fst, Scd> =
Fst extends string
? Scd extends string
? Fst extends ''
? `${Scd}`
: `${Fst}.${Scd}`
: never
: never
{
type Test = Concat<'hello','bye'> // "hello.bye"
}
/**
* Obtain union of all possible paths
*/
type KeysUnion<T, Cache extends string = ''> =
T extends Primitives ? Cache : {
[P in keyof T]:
| Concat<Cache, P>
| KeysUnion<T[P], Concat<Cache, P>>
}[keyof T]
{
// "user" | "user.description" | "user.description.name" | "user.description.surname"
type Test = KeysUnion<Foo>
}
/**
* Get object value by path
*/
type GetValueByPath<T extends string, Cache extends Acc = {}> =
T extends `${infer Head}.${infer Tail}`
? GetValueByPath<Tail, Predicate<Cache, Head>>
: Predicate<Cache, T>
{
// {
// name: string;
// surname: string;
// }
type Test = GetValueByPath<"user.description", Foo>
}
/**
* Obtain union of all possible values,
* including nested values
*/
type ValuesUnion<T, Cache = T> =
T extends Primitives ? T : Values<{
[P in keyof T]:
| Cache | T[P]
| ValuesUnion<T[P], Cache | T[P]>
}>
/**
* Function overloading
*/
function deepPickFinal<Obj, Keys extends KeysUnion<Obj>>
(obj: ValuesUnion<Obj>, keys: Keys): GetValueByPath<Keys, Obj>
function deepPickFinal<Obj, Keys extends KeysUnion<Obj> & Array<string>>
(obj: ValuesUnion<Obj>, ...keys: Keys) {
return keys
.reduce(
(acc, elem) => hasProperty(acc, elem) ? acc[elem] : acc,
obj
)
}
/**
* Ok
*/
const result = deepPickFinal(foo, 'user.description') // ok { name: string; surname: string; }
const result2 = deepPickFinal(foo, 'user') // ok
您可能已经注意到,return 类型也会被推断出来。 完整的描述和解释,您可以在 my article.
中找到如果你想使用剩余参数,comma separated
,请参考我的文章