Search code examples
typescripttypescript-typingstypescript-types

Indexing with keys of optional parameters


export interface IHtmlProperties {
    website?: string
    favIcon?: string;
}


function getHtml(htmlTemplate: string, properties: IHtmlProperties) {
        const options : IHtmlProperties = {
                    website: properties.website,
                    favIcon: properties.favIcon
                };

        let html = htmlTemplate;

        Object.keys(options).forEach(function(key) {
            html = html.replace('{{' + key + '}}', options[key]);//<==== Error
        });
        return html;
}

Accessing options[key] throwing error as below

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'IHtmlProperties'.
  No index signature with a parameter of type 'string' was found on type 'IHtmlProperties'.

If IHtmlProperties doesn't have optional params, I can easily write options[key as keyof IHtmlProperties].

But here IHtmlProperties has some optional parameters. Any idea how to solve this?

Update with fix The earlier problem is because of replace having the value of undefined. So checking for value validity fixed the error

        let html = htmlTemplate;

        (Object.keys(options)).forEach(function(key) {
            const value = options[key as keyof IHtmlProperties];
            if(value) {
                html = html.replace('{{' + key + '}}', value);
            }
        });
        return html;

The accepted answer helped me in figuring it out! Thanks for that!


Solution

  • Please see this answer, it is related in terms of using Object.keys. So, you already know that Object.keys returns array of strings. It means that key in forEach(function(key) is not related to type of options. In other words, you can use only website and favIcon as an index for options whereas type of key is much wider, it is a string.

    Type string is a super type of "website" | "favIcon" (keyof IHtmlProperties), hence it is not allowed to use. Imagine you can use any string as a key: options["any string"]. This expression returns undefined because "any string" can't be used to index options object.

    Let's define our function:

    export interface IHtmlProperties {
        website?: string
        favIcon?: string;
    }
    
    
    function getHtml(htmlTemplate: string, properties: IHtmlProperties) {
        const options: IHtmlProperties = {
            website: properties.website,
            favIcon: properties.favIcon
        };
    
        let html = htmlTemplate;
    
        return (Object.keys(options) as Array<keyof IHtmlProperties>).reduce((acc, key) => {
            const value = options[key];
    
            return value ? acc.replace('{{' + key + '}}', value) : acc
        }, html);
    }
    

    Playground As you might have noticed, key has a correct type. Since expected type of second argument of replace is a string we are not allowed to pass undefined.

    Constant value has string | undefined type, thats why it is not allowed as an argument. We need to make sure that it is a string, this is why I have added ternary operator.


    Version with forEach:

    export interface IHtmlProperties {
        website?: string
        favIcon?: string;
    }
    
    
    function getHtml(htmlTemplate: string, properties: IHtmlProperties) {
        const options: IHtmlProperties = {
            website: properties.website,
            favIcon: properties.favIcon
        };
    
        let html = htmlTemplate;
    
        (Object.keys(options) as Array<keyof IHtmlProperties>)
            .forEach((key) => {
                const value = options[key];
                if (value) {
                    html.replace(`{{${key}}}`, value)
                }
            });
    
        return html;
    }