I'm looking to extend the default indexed anonymous types in Typescript and can't seem to find the right syntax to get the type of the value of an indexed object, if this is at all possible?
Here is the problem:
EDIT:
I've reworked the examples for this to better explain the problem.
// How do we pass the value for T as a dictionary through to the iterator?.
interface Object {
where<T = { [key: string]: any}, K = keyof T>(this: T, iterator: (v: any /* this should be valueof T, not any */, key?: K) => boolean | void): T;
// This works, but you have to specify the type when calling it, this is exactly what I'm trying to avoid.
whereAsArray<S, T = { [key: string]: S }, K = keyof T>(this: T, iterator: (v: S /* this should be valueof T, not S*/, key?: K) => boolean | void): S[] /* this should be valueof T[], not S */;
}
// This problem is agnostic of the implementation. Included here only so that the code runs.
(function Setup() {
if (typeof Object.prototype.where != 'function') { Object.defineProperty(Object.prototype, 'where', { value: function (iterator: (v: any, key?: string) => any) { const keys: string[] = Object.keys(this); const result: any = {}; let i: number; for (i = 0; i < keys.length; i++) { const key = keys[i]; if (this.hasOwnProperty(key)) { const res = iterator(this[key], key); if (res === true) { result[key] = (this[key]); } } } return result; }, writable: true, configurable: true, enumerable: false }); }
if (typeof Object.prototype.whereAsArray != 'function') { Object.defineProperty(Object.prototype, 'whereAsArray', { value: function (iterator: (v: any, key?: string) => any) { const keys: string[] = Object.keys(this); const result: any[] = []; let i: number; for (i = 0; i < keys.length; i++) { const key = keys[i]; if (this.hasOwnProperty(key)) { const res = iterator(this[key], key); if (res === true) { result.push(this[key]); } } } return result; }, writable: true, configurable: true, enumerable: false }); }
})();
(function Test() {
// Typescript knows the type of x, it is explicitly typed here as an object keyed on string, storing only numbers; a dictionary of string=>number.
// Typescript enforces this everywhere you use this dictionary, as shown below:
const x: { [key: string]: number } = {};
x["foo"] = 1;
x["bar"] = 2;
// The code can currently be used like this, and works.. But, if you hover the 'i' variable, you will see that the type is any,
// because I can't figure out how to extract the type of the "value of" T in the interface?
const results = x.where((i, _k) => i !== 1);
console.log(results);
// This works. But we have to specify <number> for TS to figure this out. Question is, is it possible to have TS figure this out from the signature?
// Having to type the function calls should not be necessary.
const results2 = x.whereAsArray<number>((i, _k) => i === 1);
console.log(results2);
})();
Playground link:
I'm posting this as a separate answer because you've changed the question. The typings for this are actually pretty simple.
type ValueOf<T> = T[keyof T];
interface Object {
where<T>(this: T, iterator: (v: ValueOf<T>, key: string) => boolean | void): T; // should become Partial<T> if we are not dealing with a dictionary
whereAsArray<T>(this: T, iterator: (v: ValueOf<T>, key: string) => boolean | void): ValueOf<T>[];
}
We do not need or want a generic for the key because these functions are for all keys, not a specific key.
Note that when defining the iterator
callback, the additional arguments should be listed as required. When you call the where
method with a callback, you don't need to include all of the arguments. But if you make them optional in the callback definition, you won't be able to actually use them in the body of the callback because they might be undefined
.
I've already explained many potentially issues with defining these methods on the Object.prototype
, but I do want to bring one to your attention. Our where
returns the same type T
as the original object. That is not inherently a problem, but it doesn't match your implementation because arrays are returned as objects rather than arrays. Typescript however expects an array to be returned, leading to runtime errors.
const z = [1, 2, 3];
const filtered: number[] = z.where(v => v > 1);
// the type is number[] but it's actually an object
console.log(filtered);
// so calling array methods on it seems fine to TS, but is a runtime error
console.log(filtered.map(n => n - 1));