Home > Mobile >  Type with optional properties that cannot be explicitly undefined
Type with optional properties that cannot be explicitly undefined

Time:04-05

I'm working on the following type definitions:

type NumericKeys<T> = {
    [K in keyof T]: T[K] extends number ? K : never
}[keyof T]

type Update<T extends Record<string, any>> = {
    $set?: {
        [K in keyof T]?: T[K]
    },
    $inc?: Partial<Pick<T, NumericKeys<T>>>
}

Things are pretty close to the way that I want them except that in the case of both $set and $inc, these types allow a user to provide an explicit undefined value to any of the nested properties. Ideally all the properties in both objects would be optional, but if the user provides them they must match the type of the corresponding property from the original object.

type Item = {
    name: string;
    description: string;
    count: number
}

const example: Update<Item> = {
  $set: {
     // Ideally this would throw a type error because it should be type string
     name: undefined
  },
  $inc: {
     // Ideally this would throw a type error because it should be type number
     count: undefined
  } 
}

CodePudding user response:

TypeScript doesn't consistently distinguish missing from present-but-undefined, as discussed in microsoft/TypeScript#13195 and issues linked therein. My general advice around this is that you probably shouldn't write code where something important hinges on such a distinction.

However, TypeScript 4.4 introduced the --exactOptionalPropertyTypes compiler option which, when enabled, will not let you write undefined to an optional property unless that property explicitly includes undefined. For example:

// --exactOptionalPropertyTypes is enabled
type Foo = { a?: string };
let foo: Foo;
foo = {} // okay
foo = { a: undefined } // error!
//  Type 'undefined' is not assignable to type 'string'
foo = { a: "hello" } // okay

Note that you can still read undefined, since that's what you get if the property is missing:

const fooA = foo.a;
// const fooA: string | undefined

So if you enable --exactOptionalPropertyTypes you can get the behavior you're looking for here. But note that there's a lot of existing code that doesn't make this distinction, and if you have some of this code in your code base then you will start seeing errors there too. This compiler option was intentionally not added to the --strict suite of compiler features because of its potential for breaking a lot of real world code and harming productivity. See microsoft/TypeScript#44421 for a discussion on this; it was deemed "too invasive" to be bundled with --strict.

Playground link to code

  • Related