I'm assuming you are using strictNullChecks because that's the only way I can reproduce the behavior described. Since the apartments field of IBuilding is optional, its effective type is undefined | IApartment[], which does not extend Array<Object> because of the undefined. However, even before Normalized is called, the conditional type T[K] extends Object ? number : T[K] is simplified to number, because the compiler assumes that anything that is internally considered a "type variable" (including unsimplified lookup types) is constrained by the empty object type {}, which is assignable to Object. Clearly this assumption is incorrect if T[K] ends up including null or undefined. I filed an issue.
To get the behavior that I assume you want, you can use a distributive conditional type, which will break up any and all unions in the input, including unions involving null and undefined. I think this should be acceptable for your use case.
type NormalizeOne<T> =
T extends number ? number :
T extends string ? string :
T extends number[] ? number[] :
T extends string[] ? string[] :
T extends Function ? never :
T extends Array<Object> ? number[] :
T extends Object ? number :
T; // not reached due to compiler issue
type Normalized<T> = {
[K in keyof T]: NormalizeOne<T[K]>;
};
If you don't want to break up all unions, you could use a non-distributive conditional type and instead just add specific cases for unions with undefined:
type NormalizeOne<T> =
[T] extends [number] ? number :
[T] extends [number | undefined] ? number | undefined :
[T] extends [string] ? string :
[T] extends [string | undefined] ? string | undefined :
[T] extends [number[]] ? number[] :
[T] extends [number[] | undefined] ? number[] | undefined :
[T] extends [string[]] ? string[] :
[T] extends [string[] | undefined] ? string[] | undefined :
[T] extends [Function] ? never :
[T] extends [Array<Object>] ? number[] :
[T] extends [Array<Object> | undefined] ? number[] | undefined :
[T] extends [Object] ? number :
T; // not reached due to compiler issue
(It's probably possible to remove some of the duplication there by defining an auxiliary type alias.)