How to ensure an array's values the keys of a typescript interface?











up vote
0
down vote

favorite












I am trying to produce an events property on Klass which contains an array of strings that exactly matches all the keys of a given interface. Like so:



interface Events {
one: (foo: string) => void
two: (bar: number) => void
}

class Klass {
protected readonly events: [keyof Events] = ['one', 'two']
}


However, the above errors out with the following:



[ts]
Type '["one", "two"]' is not assignable to type '["one" | "two"]'.
Types of property 'length' are incompatible.
Type '2' is not assignable to type '1'. [2322]
(property) Klass.events: ["one" | "two"]


What is needed here to ensure that the events property returns an array that contains all of the events?










share|improve this question






















  • protected readonly events: Array<keyof Events> = ['one', 'two']
    – JB Nizet
    Nov 20 at 7:35










  • @JBNizet unfortunately, that makes ['one'] is valid. Requirement is that the array must contain all of the keys, not just any of them.
    – balupton
    Nov 20 at 7:46










  • Ah, sorry. I don't have any idea then. Not sure it's even possible.
    – JB Nizet
    Nov 20 at 7:48















up vote
0
down vote

favorite












I am trying to produce an events property on Klass which contains an array of strings that exactly matches all the keys of a given interface. Like so:



interface Events {
one: (foo: string) => void
two: (bar: number) => void
}

class Klass {
protected readonly events: [keyof Events] = ['one', 'two']
}


However, the above errors out with the following:



[ts]
Type '["one", "two"]' is not assignable to type '["one" | "two"]'.
Types of property 'length' are incompatible.
Type '2' is not assignable to type '1'. [2322]
(property) Klass.events: ["one" | "two"]


What is needed here to ensure that the events property returns an array that contains all of the events?










share|improve this question






















  • protected readonly events: Array<keyof Events> = ['one', 'two']
    – JB Nizet
    Nov 20 at 7:35










  • @JBNizet unfortunately, that makes ['one'] is valid. Requirement is that the array must contain all of the keys, not just any of them.
    – balupton
    Nov 20 at 7:46










  • Ah, sorry. I don't have any idea then. Not sure it's even possible.
    – JB Nizet
    Nov 20 at 7:48













up vote
0
down vote

favorite









up vote
0
down vote

favorite











I am trying to produce an events property on Klass which contains an array of strings that exactly matches all the keys of a given interface. Like so:



interface Events {
one: (foo: string) => void
two: (bar: number) => void
}

class Klass {
protected readonly events: [keyof Events] = ['one', 'two']
}


However, the above errors out with the following:



[ts]
Type '["one", "two"]' is not assignable to type '["one" | "two"]'.
Types of property 'length' are incompatible.
Type '2' is not assignable to type '1'. [2322]
(property) Klass.events: ["one" | "two"]


What is needed here to ensure that the events property returns an array that contains all of the events?










share|improve this question













I am trying to produce an events property on Klass which contains an array of strings that exactly matches all the keys of a given interface. Like so:



interface Events {
one: (foo: string) => void
two: (bar: number) => void
}

class Klass {
protected readonly events: [keyof Events] = ['one', 'two']
}


However, the above errors out with the following:



[ts]
Type '["one", "two"]' is not assignable to type '["one" | "two"]'.
Types of property 'length' are incompatible.
Type '2' is not assignable to type '1'. [2322]
(property) Klass.events: ["one" | "two"]


What is needed here to ensure that the events property returns an array that contains all of the events?







typescript






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 20 at 7:04









balupton

30.9k23104153




30.9k23104153












  • protected readonly events: Array<keyof Events> = ['one', 'two']
    – JB Nizet
    Nov 20 at 7:35










  • @JBNizet unfortunately, that makes ['one'] is valid. Requirement is that the array must contain all of the keys, not just any of them.
    – balupton
    Nov 20 at 7:46










  • Ah, sorry. I don't have any idea then. Not sure it's even possible.
    – JB Nizet
    Nov 20 at 7:48


















  • protected readonly events: Array<keyof Events> = ['one', 'two']
    – JB Nizet
    Nov 20 at 7:35










  • @JBNizet unfortunately, that makes ['one'] is valid. Requirement is that the array must contain all of the keys, not just any of them.
    – balupton
    Nov 20 at 7:46










  • Ah, sorry. I don't have any idea then. Not sure it's even possible.
    – JB Nizet
    Nov 20 at 7:48
















protected readonly events: Array<keyof Events> = ['one', 'two']
– JB Nizet
Nov 20 at 7:35




protected readonly events: Array<keyof Events> = ['one', 'two']
– JB Nizet
Nov 20 at 7:35












@JBNizet unfortunately, that makes ['one'] is valid. Requirement is that the array must contain all of the keys, not just any of them.
– balupton
Nov 20 at 7:46




@JBNizet unfortunately, that makes ['one'] is valid. Requirement is that the array must contain all of the keys, not just any of them.
– balupton
Nov 20 at 7:46












Ah, sorry. I don't have any idea then. Not sure it's even possible.
– JB Nizet
Nov 20 at 7:48




Ah, sorry. I don't have any idea then. Not sure it's even possible.
– JB Nizet
Nov 20 at 7:48












2 Answers
2






active

oldest

votes

















up vote
1
down vote













I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events it will force you to add it to ConcreteEvents. The only potential issue would be if you add other members to ConcreteEvents.



interface Events {
one: (foo: string) => void;
two: (bar: number) => void;
}

class ConcreteEvents implements Events {
// Note! Only add members of Events
one(foo: string) { };
two(bar: number) { };
}

class Klass {
public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
}

const klass = new Klass();
console.log(klass.events);


You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.






share|improve this answer





















  • Brilliant. I'll give it a go tomorrow and will see what I can come up with.
    – balupton
    Nov 20 at 10:30


















up vote
1
down vote













You can almost express this in the type system (assuming TS3.0+) with conditional types, with a few caveats:



type Invalid<T> = ["Needs to be all of", T]
const arrayOfAll = <T>() => <U extends T>(
...array: U & ([T] extends [U[number]] ? unknown : Invalid<T>)
) => array;
const arrayOfAllEventKeys = arrayOfAll<keyof Events>();

const goodEvents = arrayOfAllEventKeys('one', 'two'); // okay, type ['one', 'two']

const extraEvents = arrayOfAllEventKeys('one', 'two', 'three'); // error
// ~~~~~~~
// Argument of type "three" is not assignable to parameter of type "one" | "two"

const missingEvents = arrayOfAllEventKeys('one'); // error
// ~~~~~
// Argument of type "one" is not assignable to
// parameter of type ["Needs to be all of", "one" | "two"]

const redundantEvents = arrayOfAllEventKeys('one', 'two', 'one'); // no error
// doesn't enforce distinctness


Note that goodEvents is inferred to be of type ['one', 'two'], and there is no error. That's what you want. You get errors on extra events and on missing events.



Caveat 1: The error for missing events is a bit cryptic; TypeScript doesn't yet support custom error messages, so I chose something that hopefully is somewhat understandable (Argument of type "one" is not assignable to parameter of type ["Needs to be all of", "one" | "two"]).



Caveat 2: There is no error for redundant events. There's no general way that I can find to require that each parameter to arrayOfAllEventKeys is of a distinct type that doesn't run afoul of some issues with recursive types. It's possible to use overloading or other similar techniques to work for arrays of up to some hardcoded length (say, 10), but I don't know if that would meet your needs. Let me know.



Hope that helps; good luck!






share|improve this answer





















    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53387838%2fhow-to-ensure-an-arrays-values-the-keys-of-a-typescript-interface%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    1
    down vote













    I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events it will force you to add it to ConcreteEvents. The only potential issue would be if you add other members to ConcreteEvents.



    interface Events {
    one: (foo: string) => void;
    two: (bar: number) => void;
    }

    class ConcreteEvents implements Events {
    // Note! Only add members of Events
    one(foo: string) { };
    two(bar: number) { };
    }

    class Klass {
    public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
    }

    const klass = new Klass();
    console.log(klass.events);


    You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.






    share|improve this answer





















    • Brilliant. I'll give it a go tomorrow and will see what I can come up with.
      – balupton
      Nov 20 at 10:30















    up vote
    1
    down vote













    I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events it will force you to add it to ConcreteEvents. The only potential issue would be if you add other members to ConcreteEvents.



    interface Events {
    one: (foo: string) => void;
    two: (bar: number) => void;
    }

    class ConcreteEvents implements Events {
    // Note! Only add members of Events
    one(foo: string) { };
    two(bar: number) { };
    }

    class Klass {
    public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
    }

    const klass = new Klass();
    console.log(klass.events);


    You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.






    share|improve this answer





















    • Brilliant. I'll give it a go tomorrow and will see what I can come up with.
      – balupton
      Nov 20 at 10:30













    up vote
    1
    down vote










    up vote
    1
    down vote









    I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events it will force you to add it to ConcreteEvents. The only potential issue would be if you add other members to ConcreteEvents.



    interface Events {
    one: (foo: string) => void;
    two: (bar: number) => void;
    }

    class ConcreteEvents implements Events {
    // Note! Only add members of Events
    one(foo: string) { };
    two(bar: number) { };
    }

    class Klass {
    public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
    }

    const klass = new Klass();
    console.log(klass.events);


    You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.






    share|improve this answer












    I think you need a concrete object to achieve your goal here. In the example below, the implementation of the interface gives you a guaranteed way to get the right list. If you add a new member to Events it will force you to add it to ConcreteEvents. The only potential issue would be if you add other members to ConcreteEvents.



    interface Events {
    one: (foo: string) => void;
    two: (bar: number) => void;
    }

    class ConcreteEvents implements Events {
    // Note! Only add members of Events
    one(foo: string) { };
    two(bar: number) { };
    }

    class Klass {
    public readonly events = Object.keys(ConcreteEvents.prototype) as any as [keyof Events];
    }

    const klass = new Klass();
    console.log(klass.events);


    You could achieve this in several different ways using a concrete class or an object, so you don't have to follow this example exactly.







    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Nov 20 at 9:06









    Fenton

    150k42283308




    150k42283308












    • Brilliant. I'll give it a go tomorrow and will see what I can come up with.
      – balupton
      Nov 20 at 10:30


















    • Brilliant. I'll give it a go tomorrow and will see what I can come up with.
      – balupton
      Nov 20 at 10:30
















    Brilliant. I'll give it a go tomorrow and will see what I can come up with.
    – balupton
    Nov 20 at 10:30




    Brilliant. I'll give it a go tomorrow and will see what I can come up with.
    – balupton
    Nov 20 at 10:30












    up vote
    1
    down vote













    You can almost express this in the type system (assuming TS3.0+) with conditional types, with a few caveats:



    type Invalid<T> = ["Needs to be all of", T]
    const arrayOfAll = <T>() => <U extends T>(
    ...array: U & ([T] extends [U[number]] ? unknown : Invalid<T>)
    ) => array;
    const arrayOfAllEventKeys = arrayOfAll<keyof Events>();

    const goodEvents = arrayOfAllEventKeys('one', 'two'); // okay, type ['one', 'two']

    const extraEvents = arrayOfAllEventKeys('one', 'two', 'three'); // error
    // ~~~~~~~
    // Argument of type "three" is not assignable to parameter of type "one" | "two"

    const missingEvents = arrayOfAllEventKeys('one'); // error
    // ~~~~~
    // Argument of type "one" is not assignable to
    // parameter of type ["Needs to be all of", "one" | "two"]

    const redundantEvents = arrayOfAllEventKeys('one', 'two', 'one'); // no error
    // doesn't enforce distinctness


    Note that goodEvents is inferred to be of type ['one', 'two'], and there is no error. That's what you want. You get errors on extra events and on missing events.



    Caveat 1: The error for missing events is a bit cryptic; TypeScript doesn't yet support custom error messages, so I chose something that hopefully is somewhat understandable (Argument of type "one" is not assignable to parameter of type ["Needs to be all of", "one" | "two"]).



    Caveat 2: There is no error for redundant events. There's no general way that I can find to require that each parameter to arrayOfAllEventKeys is of a distinct type that doesn't run afoul of some issues with recursive types. It's possible to use overloading or other similar techniques to work for arrays of up to some hardcoded length (say, 10), but I don't know if that would meet your needs. Let me know.



    Hope that helps; good luck!






    share|improve this answer

























      up vote
      1
      down vote













      You can almost express this in the type system (assuming TS3.0+) with conditional types, with a few caveats:



      type Invalid<T> = ["Needs to be all of", T]
      const arrayOfAll = <T>() => <U extends T>(
      ...array: U & ([T] extends [U[number]] ? unknown : Invalid<T>)
      ) => array;
      const arrayOfAllEventKeys = arrayOfAll<keyof Events>();

      const goodEvents = arrayOfAllEventKeys('one', 'two'); // okay, type ['one', 'two']

      const extraEvents = arrayOfAllEventKeys('one', 'two', 'three'); // error
      // ~~~~~~~
      // Argument of type "three" is not assignable to parameter of type "one" | "two"

      const missingEvents = arrayOfAllEventKeys('one'); // error
      // ~~~~~
      // Argument of type "one" is not assignable to
      // parameter of type ["Needs to be all of", "one" | "two"]

      const redundantEvents = arrayOfAllEventKeys('one', 'two', 'one'); // no error
      // doesn't enforce distinctness


      Note that goodEvents is inferred to be of type ['one', 'two'], and there is no error. That's what you want. You get errors on extra events and on missing events.



      Caveat 1: The error for missing events is a bit cryptic; TypeScript doesn't yet support custom error messages, so I chose something that hopefully is somewhat understandable (Argument of type "one" is not assignable to parameter of type ["Needs to be all of", "one" | "two"]).



      Caveat 2: There is no error for redundant events. There's no general way that I can find to require that each parameter to arrayOfAllEventKeys is of a distinct type that doesn't run afoul of some issues with recursive types. It's possible to use overloading or other similar techniques to work for arrays of up to some hardcoded length (say, 10), but I don't know if that would meet your needs. Let me know.



      Hope that helps; good luck!






      share|improve this answer























        up vote
        1
        down vote










        up vote
        1
        down vote









        You can almost express this in the type system (assuming TS3.0+) with conditional types, with a few caveats:



        type Invalid<T> = ["Needs to be all of", T]
        const arrayOfAll = <T>() => <U extends T>(
        ...array: U & ([T] extends [U[number]] ? unknown : Invalid<T>)
        ) => array;
        const arrayOfAllEventKeys = arrayOfAll<keyof Events>();

        const goodEvents = arrayOfAllEventKeys('one', 'two'); // okay, type ['one', 'two']

        const extraEvents = arrayOfAllEventKeys('one', 'two', 'three'); // error
        // ~~~~~~~
        // Argument of type "three" is not assignable to parameter of type "one" | "two"

        const missingEvents = arrayOfAllEventKeys('one'); // error
        // ~~~~~
        // Argument of type "one" is not assignable to
        // parameter of type ["Needs to be all of", "one" | "two"]

        const redundantEvents = arrayOfAllEventKeys('one', 'two', 'one'); // no error
        // doesn't enforce distinctness


        Note that goodEvents is inferred to be of type ['one', 'two'], and there is no error. That's what you want. You get errors on extra events and on missing events.



        Caveat 1: The error for missing events is a bit cryptic; TypeScript doesn't yet support custom error messages, so I chose something that hopefully is somewhat understandable (Argument of type "one" is not assignable to parameter of type ["Needs to be all of", "one" | "two"]).



        Caveat 2: There is no error for redundant events. There's no general way that I can find to require that each parameter to arrayOfAllEventKeys is of a distinct type that doesn't run afoul of some issues with recursive types. It's possible to use overloading or other similar techniques to work for arrays of up to some hardcoded length (say, 10), but I don't know if that would meet your needs. Let me know.



        Hope that helps; good luck!






        share|improve this answer












        You can almost express this in the type system (assuming TS3.0+) with conditional types, with a few caveats:



        type Invalid<T> = ["Needs to be all of", T]
        const arrayOfAll = <T>() => <U extends T>(
        ...array: U & ([T] extends [U[number]] ? unknown : Invalid<T>)
        ) => array;
        const arrayOfAllEventKeys = arrayOfAll<keyof Events>();

        const goodEvents = arrayOfAllEventKeys('one', 'two'); // okay, type ['one', 'two']

        const extraEvents = arrayOfAllEventKeys('one', 'two', 'three'); // error
        // ~~~~~~~
        // Argument of type "three" is not assignable to parameter of type "one" | "two"

        const missingEvents = arrayOfAllEventKeys('one'); // error
        // ~~~~~
        // Argument of type "one" is not assignable to
        // parameter of type ["Needs to be all of", "one" | "two"]

        const redundantEvents = arrayOfAllEventKeys('one', 'two', 'one'); // no error
        // doesn't enforce distinctness


        Note that goodEvents is inferred to be of type ['one', 'two'], and there is no error. That's what you want. You get errors on extra events and on missing events.



        Caveat 1: The error for missing events is a bit cryptic; TypeScript doesn't yet support custom error messages, so I chose something that hopefully is somewhat understandable (Argument of type "one" is not assignable to parameter of type ["Needs to be all of", "one" | "two"]).



        Caveat 2: There is no error for redundant events. There's no general way that I can find to require that each parameter to arrayOfAllEventKeys is of a distinct type that doesn't run afoul of some issues with recursive types. It's possible to use overloading or other similar techniques to work for arrays of up to some hardcoded length (say, 10), but I don't know if that would meet your needs. Let me know.



        Hope that helps; good luck!







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 20 at 14:52









        jcalz

        21k21637




        21k21637






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53387838%2fhow-to-ensure-an-arrays-values-the-keys-of-a-typescript-interface%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            404 Error Contact Form 7 ajax form submitting

            How to know if a Active Directory user can login interactively

            Refactoring coordinates for Minecraft Pi buildings written in Python