TypeScript type narrowing on objecct properties?

This won't compile:

type QueryParamValue = string | number | boolean | null | undefined;

function encodeParam(x: QueryParamValue) {
    if (x === true) {
        return '1';
    }
    if (x === false) {
        return '0';
    }
    if (x === undefined || x === null) {
        return '';
    }
    return encodeURIComponent(x);
}

interface QueryParams {
    [_: string]: QueryParamValue|QueryParamValue[];
}

export function queryParams(params: QueryParams) {
    return Object.keys(params)
        .map(k => {
            if(params[k] === undefined) {
                return undefined;
            }
            if (Array.isArray(params[k])) {
                return params[k]
                    .map(val => `${encodeParam(k)}[]=${encodeParam(val)}`)
                    .join('&')
            }
            return `${encodeParam(k)}=${encodeParam(params[k])}`
        })
        .filter(Boolean)
        .join('&');
}

codeplay

I figured out if I assign the prop to an intermediate value then it works fine:

export function queryParams(params: QueryParams) {
    return Object.keys(params)
        .map(k => {
            const value = params[k]; // <-- assign to intermediate value
            if(value === undefined) {
                return undefined;
            }
            if (Array.isArray(value)) {
                return value
                    .map(val => `${encodeParam(k)}[]=${encodeParam(val)}`)
                    .join('&')
            }
            return `${encodeParam(k)}=${encodeParam(value)}`
        })
        .filter(Boolean)
        .join('&');
}

But this behaviour seems unintuitive. Is there some way to make the first example compile?

1 answer

  • answered 2019-09-15 21:24 meriton

    No, you need the variable:

    TypeScript can not assume that param[k] will return the same value every time, because

    • somebody might have changed the value in the mean time
    • the property access might actually call a getter that randomly returns a different value