Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

keyof to produce a runtime array #13267

Closed
markboyall opened this issue Jan 3, 2017 · 15 comments
Closed

keyof to produce a runtime array #13267

markboyall opened this issue Jan 3, 2017 · 15 comments
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript

Comments

@markboyall
Copy link

markboyall commented Jan 3, 2017

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.

@krryan
Copy link

krryan commented Jan 3, 2017

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).

@zpdDG4gta8XKpMCd
Copy link

in what order should these properties go?

@krryan
Copy link

krryan commented Jan 3, 2017

I for one don't care; alphabetical, as-declared, officially-undefined, I just want it to be complete.

@zpdDG4gta8XKpMCd
Copy link

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"];

@krryan
Copy link

krryan commented Jan 3, 2017

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"—some values from the union are not represented in the array.

@zpdDG4gta8XKpMCd
Copy link

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: #12253 (comment)

@krryan
Copy link

krryan commented Jan 3, 2017

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.

@Igorbek
Copy link
Contributor

Igorbek commented Jan 3, 2017

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:

  • no object of type T to pass to Object.keys
  • T is generic, cannot emit static value at keyof T
  • would need to introduce ambient parameter
    • pass it only if it is used?
    • need to resolve on call-site?
    • generics can be propagated through call stack
    • generics can be constructed from other generics (T & G[])
    • .d.ts !

@zpdDG4gta8XKpMCd
Copy link

zpdDG4gta8XKpMCd commented Jan 3, 2017

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' });

@zpdDG4gta8XKpMCd
Copy link

@Igorbek has a good point, if we remember that any type driven emit is taboo

@mhegazy
Copy link
Contributor

mhegazy commented Jan 3, 2017

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.

@mhegazy mhegazy added Suggestion An idea for TypeScript Out of Scope This idea sits outside of the TypeScript language design constraints labels Jan 3, 2017
@markboyall
Copy link
Author

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.

@saschanaz
Copy link
Contributor

Probably a duplicate or at least a sub-issue of #1549.

@mhegazy mhegazy closed this as completed Apr 24, 2017
@kimamula
Copy link
Contributor

kimamula commented Apr 25, 2017

This is possible with custom transformers introduced by #13940.
See https://github.com/kimamula/ts-transformer-keys.

@gwicksted
Copy link
Contributor

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

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

8 participants