1

If i write the following type

type myType<T> = {
    [K in keyof T]?: T[K] extends ({[k:string]: any} | undefined) ? T[K] : 0
}

when T[K] is an array of any type, myType behaves as if T[K] extends ({[k:string]:any)|undefined) was true even if any[] is not a {[k:string]:any}. So when i try to use this type like this i get an error:

declare class obj {
    a?: string
    b?: any[] // <-- if this gets changed to a primitive there is no more error
    c?: {
        d: number
    }
}

const a: myType<obj> = {
    a: 0,
    b: 0, // <-- this gives an error
    c: {
        d: 1
    }
}

Playground Link

2
  • 2
    Please include the code for what you do and don't want that type to do in the question itself, not just linked. Three reasons: People shouldn't have to go off-site to help you; some sites are blocked for some users; and links rot, making the question and its answers useless to people in the future. Please put a minimal reproducible example in the question. Commented Jul 10, 2022 at 10:25
  • Okay, I have updated the question as you indicated! Thank you. Commented Jul 11, 2022 at 9:37

2 Answers 2

1

The problem is that Array<any> (number[], string[]...) actually extends the type Object.

That is why any[] checks as {[k:string]: any}.

To solve that, you should validate specifically for array, before validating for {[k:string]: any}.

It would be something like:

type myType<T> = {
  [K in keyof T]?: T[K] extends (Array<any> | undefined) ? 0 : (T[K] extends ({ [k: string]: any } | undefined) ? T[K] : 0)
}

| undefined can be removed if your array property is not optional.

But as you can see you need to nest 2 conditionals.

So instead of checking for a generic object { [k: string]: any }, I would recommend you create another type, so you could validate using your custom type.

Otherwise, you might have more validation problems if you add other properties that are also objects.

Sign up to request clarification or add additional context in comments.

3 Comments

Then how would I validate against anything that is not an object of type { string: any } excluding any other kind of object (like arrays, functions etc)? I find this behaviour a bit strange. I would understand it if I wrote T[K] extends Object | object but` { [k:string]: any }` doesn't look much like an array or a function!
That is because you are thinking of an array of only accessing its elements by the number index. Like myArray[1]. But an Array also has length which you usually access this way: myArray.length. But you can also access it like this: myArray['length']. See now how an array object also extends { [k:string]: any }. Because inside the array object basically there is this {length: number}. This is why I recommend that you create specific types/classes/interfaces for your properties that have dictionary/object structure: { [k:string]: any }.
That's right, now it is clear. Thank you
1

Yes an array extends the type object, because an array is an object.

Here is a narrowed down example :

type Foo = [] extends {} ? 'array' : 'notArray';    
declare const foo:Foo; // 'array' 

A fix would to handle the array type seperatly :

type myType<T> = {
    [K in keyof T]?: T[K] extends ({[k:string]: any} | undefined) ? T extends ([]|undefined) ? 0 : T[K] : 0
}

declare class obj{
    a?: string
    b?: any[] // OK
    c?: {
        d: number
    }
}

Playground

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.