How to implement TypeScript deep partial mapped type not breaking array properties

With TS 2.8 and conditional types we can simply write:

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends Array<infer U>
    ? Array<DeepPartial<U>>
    : T[P] extends ReadonlyArray<infer U>
      ? ReadonlyArray<DeepPartial<U>>
      : DeepPartial<T[P]>
};

or with [] instead of Array<> that would be:

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends (infer U)[]
    ? DeepPartial<U>[]
    : T[P] extends Readonly<infer U>[]
      ? Readonly<DeepPartial<U>>[]
      : DeepPartial<T[P]>
};

You might want to checkout https://github.com/krzkaczor/ts-essentials package for this and some other useful types.


UPDATE 2018-06-22:

This answer was written a year ago, before the amazing conditional types feature was released in TypeScript 2.8. So this answer is no longer needed. Please see @krzysztof-kaczor's new answer below for the way to get this behavior in TypeScript 2.8 and up.


Okay, here is my best attempt at a crazy but fully general solution (requiring TypeScript 2.4 and up) which might not worth it to you, but if you want to use it, be my guest:

First, we need some type-level boolean logic:

type False = '0'
type True = '1'
type Bool = False | True
type IfElse<Cond extends Bool, Then, Else> = {'0': Else; '1': Then;}[Cond];

All you need to know here is that the type IfElse<True,A,B> evaluates to A and IfElse<False,A,B> evaluates to B.

Now we define a record type Rec<K,V,X>, an object with key K and value type V, where Rec<K,V,True> means the property is required, and Rec<K,V,False> means the property is optional:

type Rec<K extends string, V, Required extends Bool> = IfElse<Required, Record<K, V>, Partial<Record<K, V>>>

At this point we can get to your User and DeepPartialUser types. Let's describe a general UserSchema<R> where every property we care about is either required or optional, depending on whether R is True or False:

type UserSchema<R extends Bool> =
  Rec<'emailAddress', string, R> &
  Rec<'verification', (
    Rec<'verified', boolean, R> &
    Rec<'verificationCode', string, R>
  ), R> &
  Rec<'activeApps', string[], R>

Ugly, right? But we can finally describe both User and DeepPartialUser as:

interface User extends UserSchema<True> { } // required
interface DeepPartialUser extends UserSchema<False> { }  // optional

And see it in action:

var user: User = {
  emailAddress: '[email protected]',
  verification: {
    verified: true,
    verificationCode: 'shazam'
  },
  activeApps: ['netflix','facebook','angrybirds']
} // any missing properties or extra will cause an error

var deepPartialUser: DeepPartialUser = {
  emailAddress: '[email protected]',
  verification: {
    verified: false
  }
} // missing properties are fine, extra will still error

There you go. Hope that helps!