33
loading...
This website collects cookies to deliver better user experience
lodash.get
function.// John
const result = deepPick({ user: { name: 'John' } }, 'user', 'name')
type Foo = {
user: {
description: {
name: string;
surname: string;
}
}
}
type FirstAttempt<T> = {
[P in keyof T]: [P]
}
type SecondAttempt<Obj> = {
[Prop in keyof Obj]:
Obj[Prop] extends PropertyKey
? [Prop]
: SecondAttempt<Obj[Prop]>
}
// { name: ["name"]; surname: ["surname"]; }
type Result = SecondAttempt<Foo>['user']['description']
type ThirdAttempt<Obj, Cache extends Array<PropertyKey> = []> = {
[Prop in keyof Obj]: Obj[Prop] extends PropertyKey
? [...Cache, Prop]
: ThirdAttempt<Obj[Prop], [...Cache, Prop]>
}
// {
// name: ["user", "description", "name"];
// surname: ["user", "description", "surname"];
// }
type Result = ThirdAttempt<Foo>['user']['description']
Cache
as a union of previous and next type.type FourthAttempt<Obj, Cache extends Array<PropertyKey> = []> = {
[Prop in keyof Obj]: Obj[Prop] extends PropertyKey
? [...Cache, Prop]
: FourthAttempt<Obj[Prop], Cache | [...Cache, Prop]>
}
type Result = FourthAttempt<Foo>['user']['description']
type FifthAttempt<Obj, Cache extends Array<PropertyKey> = []> =
Obj extends PropertyKey
? Cache
: {
[Prop in keyof Obj]:
FifthAttempt<Obj[Prop], Cache | [...Cache, Prop]>
}
type Result = FifthAttempt<Foo>
type Values<T>=T[keyof T]
type Values<Obj> = Obj[keyof Obj]
type SixthAttempt0<Obj, Cache extends Array<PropertyKey> = []> =
Obj extends PropertyKey
? Cache
: Values<{
[Prop in keyof Obj]:
SixthAttempt0<Obj[Prop], Cache | [...Cache, Prop]>
}>
type Result = SixthAttempt0<Foo>
type FinalAttempt<Obj, Cache extends Array<PropertyKey> = []> =
Obj extends PropertyKey
? Cache
: {
[Prop in keyof Obj]:
| [...Cache, Prop]
| FinalAttempt<Obj[Prop], [...Cache, Prop]>
}[keyof Obj]
type Result = FinalAttempt<Foo>
type FinalAttempt<Obj, Cache extends Array<PropertyKey> = []> =
Obj extends PropertyKey ? Cache : {
[Prop in keyof Obj]:
| [...Cache, Prop]
| FinalAttempt<Obj[Prop], [...Cache, Prop]>
}[keyof Obj]
declare function deepPick<Obj,>(obj: Obj, ...keys: FinalAttempt<Obj>): void
declare var foo: Foo;
deepPick(foo, 'user'); // ok
deepPick(foo, 'user', 'description') // ok
deepPick(foo, 'description') // expected error
ReturnType
?function deepPick<Obj >(obj: Obj, ...keys: FinalAttempt<Obj>){
return keys.reduce((acc,elem)=>acc[elem], obj) // <-- errors
}
keys
has not any problems with infinity recursion and it is an array of strings. Further more, how would you type reducer predicate? Since, every iteration it returns different type.FinalAttempt
), but this time let's make a union of values. It returns all combinations of Foo
values.type ValuesUnion<Obj, Cache = Obj> =
Obj extends Primitives ? Obj : Values<{
[Prop in keyof Obj]:
| Cache | Obj[Prop]
| ValuesUnion<Obj[Prop], Cache | Obj[Prop]>
}>
hasProperty
typeguard.const hasProperty = <Obj, Prop extends Primitives>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, any> =>
Object.prototype.hasOwnProperty.call(obj, prop);
function deepPick<Obj, Keys extends FinalAttempt<Obj> & Array<string>>
(obj: ValuesUnion<Obj>, ...keys: Keys) {
return keys
.reduce(
(acc, elem) => hasProperty(acc, elem) ? acc[elem] : acc,
obj
)
}
type Elem = string;
type Acc = Record<string, any>
// (acc, elem) => hasProperty(acc, elem) ? acc[elem] : acc
type Predicate<Accumulator extends Acc, El extends Elem> =
El extends keyof Accumulator ? Accumulator[El] : Accumulator
type Reducer<
Keys extends ReadonlyArray<Elem>,
Accumulator extends Acc = {}
> =
/**
* If Keys is empty array, no need to call recursion,
* just return Accumulator
*/
Keys extends []
? Accumulator
/**
* If keys is one element array,
*
*/
: Keys extends [infer H]
? H extends Elem
/**
* take this element and call predicate
*/
? Predicate<Accumulator, H>
: never
/**
* If Keys is an Array of more than one element
*/
: Keys extends readonly [infer H, ...infer Tail]
? Tail extends ReadonlyArray<Elem>
? H extends Elem
/**
* Call recursion with Keys Tail
* and call predicate with first element
*/
? Reducer<Tail, Predicate<Accumulator, H>>
: never
: never
: never;
const reducer = (keys: string[], accumulator: Record<string, any> = {}) => {
const predicate = (obj,prop)=>obj[prop]
if (keys.length === 0) {
return accumulator;
}
if (keys.length === 1) {
const [head] = keys;
return reducer([], predicate(accumulator, head))
}
if(keys.length>1){
const [head, ...tail]=keys;
return reducer(tail, predicate(accumulator, head))
}
}
type Foo = {
user: {
description: {
name: string;
surname: string;
}
}
}
declare var foo: Foo;
/**
* Common utils
*/
type Primitives = string | number | symbol;
type Values<T> = T[keyof T]
type Elem = string;
type Acc = Record<string, any>
// (acc, elem) => hasProperty(acc, elem) ? acc[elem] : acc
type Predicate<Accumulator extends Acc, El extends Elem> =
El extends keyof Accumulator ? Accumulator[El] : Accumulator
type Reducer<
Keys extends ReadonlyArray<Elem>,
Accumulator extends Acc = {}
> =
/**
* If Keys is empty array, no need to call recursion,
* just return Accumulator
*/
Keys extends []
? Accumulator
/**
* If keys is one element array,
*
*/
: Keys extends [infer H]
? H extends Elem
/**
* take this element and call predicate
*/
? Predicate<Accumulator, H>
: never
/**
* If Keys is an Array of more than one element
*/
: Keys extends readonly [infer H, ...infer Tail]
? Tail extends ReadonlyArray<Elem>
? H extends Elem
/**
* Call recursion with Keys Tail
* and call predicate with first element
*/
? Reducer<Tail, Predicate<Accumulator, H>>
: never
: never
: never;
const hasProperty = <Obj, Prop extends Primitives>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, any> =>
Object.prototype.hasOwnProperty.call(obj, prop);
/**
* Fisrt approach
*
*/
type KeysUnion<T, Cache extends Array<Primitives> = []> =
T extends Primitives ? Cache : {
[P in keyof T]:
| [...Cache, P]
| KeysUnion<T[P], [...Cache, P]>
}[keyof T]
type ValuesUnion<T, Cache = T> =
T extends Primitives ? T : Values<{
[P in keyof T]:
| Cache | T[P]
| ValuesUnion<T[P], Cache | T[P]>
}>
function deepPickFinal<Obj, Keys extends KeysUnion<Obj> & ReadonlyArray<string>>
(obj: ValuesUnion<Obj>, ...keys: Keys): Reducer<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') // ok
const result2 = deepPickFinal(foo, 'user', 'description') // ok
const result3 = deepPickFinal(foo, 'user', 'description', 'name') // ok
const result4 = deepPickFinal(foo, 'user', 'description', 'surname') // ok
/**
* Expected errors
*/
const result5 = deepPickFinal(foo, 'surname')
const result6 = deepPickFinal(foo, 'description')
const result7 = deepPickFinal(foo)
validation technique
.type Foo = {
user: {
description: {
name: string;
surname: string;
}
}
}
declare var foo: Foo;
type Primitives = string | number | symbol;
type Util<Obj, Props extends ReadonlyArray<Primitives>> =
Props extends []
? Obj
: Props extends [infer First]
? First extends keyof Obj
? Obj[First]
: never
: Props extends [infer Fst, ...infer Tail]
? Fst extends keyof Obj
? Tail extends string[]
? Util<Obj[Fst], Tail>
: never
: never
: never
// credits https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
type IsNeverType<T> = [T] extends [never] ? true : false;
type IsAllowed<T> = IsNeverType<T> extends true ? false : true;
type Validator<T extends boolean | string> = T extends true ? [] : [never]
type ValuesUnion<T, Cache = T> =
T extends Primitives ? T : {
[P in keyof T]:
| Cache | T[P]
| ValuesUnion<T[P], Cache | T[P]>
}[keyof T]
const hasProperty = <Obj, Prop extends Primitives>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, any> =>
Object.prototype.hasOwnProperty.call(obj, prop);
function pick<
Obj,
Prop extends string,
Props extends ReadonlyArray<Prop>,
Result extends Util<Obj, Props>>
(
obj: ValuesUnion<Obj>,
props: [...Props],
..._: Validator<IsAllowed<Result>>
): Util<Obj, Props>;
function pick<
Obj,
Prop extends string,
Props extends ReadonlyArray<Prop>,
Result extends Util<Obj, Props>>(
obj: ValuesUnion<Obj>,
props: [...Props],
..._: Validator<IsAllowed<Result>>) {
return props.reduce(
(acc, prop) => hasProperty(acc, prop) ? acc[prop] : acc,
obj
)
}
/**
* Ok
*/
const result8 = pick(foo, ['user', 'description']) // ok
const result9 = pick(foo, ['user', 'description', 'name']) // ok
/**
* Expected errors
*/
const result10 = pick(foo, ['description']) // error
const result11 = pick(foo, ['name']) // ok