简体   繁体   中英

How to define a type and make sure it's part of another type in typescript?

I have a big type:

type BigType = {
  aaa: string,
  bbb?: number,
  ccc: boolean[],
  extra?: {
    [key in string]?: string
  },
  nested1: {
    nested2: {
      nested3: {
        [key in string]?: string
      }
    }
  }
}

And I want to define another type and make sure it's subset of the BigType , so I defined a RecursivePartial type:

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

and

type PartOf<T, X extends RecursivePartial<T>> = X;

Now I can define a small type which is only part of the BigType:

type SmallType = PartOf<BigType, {
  aaa: string;
  extra: { ddd: string };
  nested1: { nested2: {} }
}>

The problem is I can add properties which is not part of BigType too:

type SmallType = PartOf<BigType, {
  aaa: string;
  extra: { ddd: string };
  nested1: { nested2: {} },

  someOtherProperty1: string, // not part of BigType
  someOtherProperty2: string, // not part of BigType
}>

How to fix it?

The issue here is that object types in TypeScript are open and not exact (see #12936 for discussion about exact types) . That is, you can object types A and B where A extends B and A has properties that B doesn't mention. This is actually a crucial part of interface/class hierarchies; without it, you couldn't add properties to subinterfaces/subclasses. Still, there are times when it surprises people (especially because when you use object literal values the compiler performs additional excess property checking which makes it look like object types are exact).

Exact object types can't currently be represented in TypeScript as specific concrete types. Instead, you have to use generics (see this GitHub comment for more information)


Anyway in your case I'd probably proceed by defining a DeepPartial and a DeepNoExcess type alias and using both of them in TypeOf . DeepPartial looks like this:

type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };

This is essentially the same as your RecursivePartial , I think. As of TS3.1, mapped types automatically map over arrays and tuples without needing special casing, and recursive mapped types that encounter primitive types leave them unmapped (see microsoft/TypeScript#12447 ). That means you don't need to do much to get a recursive Partial .

DeepNoExcess has to take both the main type and the candidate type (since it's not possible to represent exact types concretely):

type DeepNoExcess<T, U> = { [K in keyof U]:
    K extends keyof T ? DeepNoExcess<Required<T>[K], U[K]> :
    never };

This walks down through the properties of the candidate type U and makes the property type never if the property key doesn't also exist in T . I had to walk down into Required<T> instead of just T because your optional properties weren't being handled properly ( keyof (SomeType | undefined) tends to be never ).

Then PartOf is defined like this:

type PartOf<T, U extends DeepPartial<T> & DeepNoExcess<T, U>> = U;

This yields the behavior you are hoping for with your two examples:

type GoodSmallType = PartOf<BigType, {
    aaa: string;
    extra: { ddd: string };
    nested1: { nested2: {} }
}>; // okay

type BadSmallType = PartOf<BigType, {
    aaa: string;
    extra: { ddd: string };
    nested1: { nested2: {} },

    someOtherProperty1: string, // not part of BigType
    someOtherProperty2: string, // not part of BigType
}>; // error! Types of property 'someOtherProperty1' are incompatible.

Whether it meets all your use cases is not clear; there are lots of decisions you could make (like Required<T> instead of T ) that will have implications for what types are accepted and what types are not. But hopefully this gives you a way forward at least. Good luck!

Playground link to code

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