Skip to content

keyof to produce a runtime array #13267

Description

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.

Activity

  1. krryan commented on Jan 3, 2017

    @krryan

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

  2. zpdDG4gta8XKpMCd commented on Jan 3, 2017

    @zpdDG4gta8XKpMCd

    in what order should these properties go?

  3. krryan commented on Jan 3, 2017

    @krryan

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

  4. zpdDG4gta8XKpMCd commented on Jan 3, 2017

    @zpdDG4gta8XKpMCd

    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"];
    
    
  5. krryan commented on Jan 3, 2017

    @krryan

    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.

  6. zpdDG4gta8XKpMCd commented on Jan 3, 2017

    @zpdDG4gta8XKpMCd

    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)

  7. krryan commented on Jan 3, 2017

    @krryan

    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.

  8. Igorbek commented on Jan 3, 2017

    @Igorbek
    Contributor

    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 !
  9. zpdDG4gta8XKpMCd commented on Jan 3, 2017

    @zpdDG4gta8XKpMCd

    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' });
  10. zpdDG4gta8XKpMCd commented on Jan 3, 2017

    @zpdDG4gta8XKpMCd

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

  11. mhegazy commented on Jan 3, 2017

    @mhegazy
    Contributor

    As noted by Igor Oleinikov (@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.

  12. markboyall commented on Jan 4, 2017

    @markboyall
    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.

  13. saschanaz commented on Jan 6, 2017

    @saschanaz
    Contributor

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

  14. kimamula commented on Apr 25, 2017

    @kimamula
    Contributor

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

  15. gwicksted commented on Sep 14, 2017

    @gwicksted
    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

  16. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions