typescripttypescript2.4

Typescript keyof extra type condition


I want pass two generic condition for pass array type field name but not accepted second condition.

This my method declaration and this no problem.

firstOrDefault<K extends keyof T>(predicate?: (item: T) => boolean, recursiveItem?: K): T;

Above method declaration is working but I want pass only Array type in recurviseItem field.

I'm trying this method declaration but doesn't work.

firstOrDefault<K extends keyof T & T[]>(predicate?: (item: T) => boolean, recursiveItem?: K): T

How can solve this problem?

Sample Code

let departments : IDepartment[] = [
    {
        name: 'manager',
        subDepartments: [
            {
                name: 'accountant'
            }
        ]
    }
]

// This my method declaration and this code worked but you can pass name and subDepartments field pass for recursiveItem parameter but i want only T[] type field pass so only subDepartments.
let department = departments.firstOrDefault(d => d.name == 'accountant', 'subDepartments')
console.log(department)

interface Array<T> {
    firstOrDefault<K extends keyof T>(predicate?: (item: T) => boolean, recursiveItem?: K): T;
}

Array.prototype.firstOrDefault = function(this, predicate, recursiveItem) {
    if (!predicate)
        return this.length ? this[0] : null;
    for (var i = 0; i < this.length; i++) {
        let item = this[i]
        if (predicate(item))
            return item
        if (recursiveItem) {
            let subItems = item[recursiveItem]
            if (Array.isArray(subItems)) {
                var res = subItems.firstOrDefault(predicate, recursiveItem)
                if (res)
                    return res
            }
        }
    }
    return null;
}

interface IDepartment {
    name?: string,
    subDepartments?: IDepartment[]
}

Solution

  • Try this type definition

    type ArrayProperties<T, I> = { [K in keyof T]: T[K] extends Array<I> ? K : never }[keyof T]
    
    class A {
        arr1: number[];
        arr2: string[];
        str: string;
        num: number;
        func() {}
    }
    
    let allArr: ArrayProperties<A, any>; // "arr1" | "arr2"
    let numArr: ArrayProperties<A, number>; // "arr1"
    

    so firstOrDefault would look like that (I put ArrayProperties<T, T> to restrict recursiveItem only for recursive type i.e. only properties of type IDepartment[] could be used, however you can put ArrayProperties<T, any> if you want to accept any array)

    function firstOrDefault<T>(predicate?: (item: T) => boolean, recursiveItem?: ArrayProperties<T, T>): T { }
    

    With your example

    interface IDepartment {
        name: string;
        subDepartments?: IDepartment[];
    }
    
    let departments : IDepartment[] = [
        {
            name: 'manager',
            subDepartments: [
                {
                    name: 'accountant'
                }
           ]
        }
    ]
    
    let a = firstOrDefault(((d: IDepartment) => d.name === 'accountant'), 'subDepartments'); // OK
    let b = firstOrDefault(((d: IDepartment) => d.name === 'accountant'), 'subDepartment'); // error: [ts] Argument of type '"subDepartment"' is not assignable to parameter of type '"subDepartments"'.