Put simply, I'd like a variant of keyof which produces the keys at runtime. E.g.,
interface Foo {
prop1: number;
prop2: string;
}
const x = keyof Foo;
In this case x
should be ["prop1", "prop2"]
.
I've seen a few cases where people have tried to use keyof
in somewhat-similar fashions, but they all wanted the actual keys present in the object at runtime. I however very explicitly want to ignore any properties present in any hypothetical runtime Foo objects that are not present in the interface.
Should permit generic parameters as the type.
Perhaps this could be generalized to turning a union of literal types (as keyof Foo
is, being 'prop1' | 'prop2'
) into an array containing each member of the union once (['prop1', 'prop2']
). I have a number of places I would like to see that, particularly for testing (have a test iterate over the array to make sure that each value supposedly acceptable to the union does, in fact, produce an appropriate result).
in what order should these properties go?
I for one don't care; alphabetical, as-declared, officially-undefined, I just want it to be complete.
i think it should be a codefix (refactoring) rather than a runtime facility:
// before
const x = keyof Foo;
^^^^^^^^^
// codefix
______________________________________________
const x = keyof Foo; | |
^^^^^^^^^ < Replace with all names of `Foo` properties |
|______________________________________________|
// after
const x = ["prop1", "prop2"];
It's better than nothing but it definitely doesn't do everything I would have liked. For example, in my testing example, the entire point is ensuring complete coverage of the union for the test case. If a new item is added to the union, that array is still a valid array of items from that union, but it is no longer "complete"鈥攕ome values from the union are not represented in the array.
on the other hand, the fact that there is no way to know the order of names in which they will be listed is disturbing
in a meanwhile there is a hack:
const mySample : Foo {
prop1: 0,
prop2: ''
};
function toKeysBySample<T>(value: T): (keyof T)[] {
return Object.keys(value);
}
const fooPropNames = toKeysBySample(mySample);
although it's not perfect either: https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208
That hack does nothing for the case of a union that isn't derived from keyof
though. Personally, I am only concerned about order in a minority of cases; lacking knowledge of the order in this case wouldn't bother me. The type of the resulting "array" could also be a tuple, which would capture the order.
Should permit generic parameters as the type.
that wouldn't that easy
function keys<T>(): (keyof T)[] {
return keyof T;
}
console.log(keys<{ a: number; }>());
console.log(keys<{ c: bool; }>());
what would it emit? Consider:
T
to pass to Object.keys
T
is generic, cannot emit static value at keyof T
T & G[]
).d.ts
!a better hack that is almost to the point (yet has problems with unions)
interface Foo {
kind: 'a';
value: number;
}
interface Baz {
kind: 'b';
text: string;
}
type Bar = Foo | Baz;
type Names<T> = { [P in keyof T]: P; }
function toKeysBySample<T>(names: Names<T>): (keyof T)[] {
return Object.values(names);
}
toKeysBySample<Bar>({ kind: 'kind', text: 'text' });
@Igorbek has a good point, if we remember that any type driven emit is taboo
As noted by @Igorbek and @aleksey-bykov, type directed emit is something that violate the TS goals of a fully erasable type system.
Moreover, a keyof T
is not guaranteed to be the complete list at runt time you would get from Object.keys
since the TS types are not exact/final.
For both reasons, this is out of scope for the TypeScript project at the time being.
The second reason is the entire reason I wanted this feature. I don't want the complete runtime list. I want strictly and only the ones listed in the interface.
As for the first, I guess I just don't see why that should be a goal at all.
Probably a duplicate or at least a sub-issue of #1549.
This is possible with custom transformers introduced by #13940.
See https://github.com/kimamula/ts-transformer-keys.
What about generating an enum at compile-time?
type A = { x: number, y: string };
enum B = keyof A; // as A exists right now during compilation
for (var enumMember in B) {
console.log("enum member: ", enumMember);
}
which would write:
0
1
x
y
and would have the following JS code:
(function (B) {
B[B["x"] = 0] = "x";
B[B["y"] = 1] = "y";
})(B = exports.B || (exports.B = {}));
As to what order... doesn't matter to me. I just want this to validate a JSON config file against a TypeScript Interface without requiring something like ajv
or portions of tsc
itself like ts-transformer-keys
Most helpful comment
The second reason is the entire reason I wanted this feature. I don't want the complete runtime list. I want strictly and only the ones listed in the interface.
As for the first, I guess I just don't see why that should be a goal at all.