简体   繁体   中英

Typescript optional type does not allow recursive partial utility type to work

I wanted to use the popular RecursivePartial utility type to make all fields, objects, and arrays of objects be optional but not invalid. However when a field is already marked as optional, this breaks. It suddenly does not allow partial types. Is there any way to fix this such that eventA works like eventB ?

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

class MyEvent {
  date!: Date;
  name!: string;
}

interface MyUser {
  name: string;
  eventA?: MyEvent;
  eventB: MyEvent;
}

type PartialUser = RecursivePartial<MyUser>;

const a: PartialUser = {
  eventA: { // It wants all fields defined
    name: '',
  },
  eventB: { // But this one is OK
    name: '',
  },
};

Playground


An attempt at a solution because I still have issues with a field like users?: User[]

type RecursivePartial<T> = {
  [P in keyof T]?:
  T[P] extends (infer U)[] | undefined ? RecursivePartial<U>[] :
  T[P] extends object | undefined ? RecursivePartial<T[P]> :
  Partial<T[P]>;
};

It works but I'm trying to understand the consequences of | undefined| undefined . It seems that it will let me get away with assigning a number to a Date for example.. Yikes


Attempt 3: I think this works. Just have to do the hard work and add more stronger typechecking

type RecursivePartial<T> = {
  [P in keyof T]?:
  T[P] extends Date | undefined ? Date :
  T[P] extends (infer U)[] | undefined ? RecursivePartial<U>[] :
  T[P] extends object | undefined ? RecursivePartial<T[P]> :
  Partial<T[P]>;
};

The issue here is that the type of the eventA field is actually MyEvent | undefined MyEvent | undefined . Since it includes the union with undefined , it will no longer match to the extends object clause and thus is mapped back to itself ( T[P] in the final case).

One way to fix this is to reform your RecursivePartial so that each type conditional happens on the overall type rather than on the keys of the type. This way the type can distribute itself over each part of a union separately.

Eg:

type RecursivePartial<T> =
  T extends Date ? T :                                       // Leave Date objects alone
  // Also of note: possibly add cases for Set, Map, other JS builtins...
  T extends (infer U)[] ? RecursivePartial<U>[] :            // Recurse into array types!
  T extends {} ? {[P in keyof T]?: RecursivePartial<T[P]>} : // Recurse into object types!
  T;                                                         // Leave other types as-is.

This seems to produce the inference you are looking for:

const a: PartialUser = {
  eventA: {   // No Error!
    name: '',
  },
  eventB: {
    name: '',
  },
};

Let me know if this still doesn't work for your actual use case.


Typescript Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM