Home > Blockchain >  Typescript type that allows omitting required object property
Typescript type that allows omitting required object property

Time:09-29

I don't know if what I'm trying to do is possible but here's the idea:

Let's assume we have a generic type:

type Foo<T> = {
  bar: T
}

Is there any T type which would let me omit the property completely, without making it optional in the type Foo (so without ?: or T | undefined, etc)?

So this would pass compiler checks (where I'm looking for what _ is)?

const x: Foo<_> = {}

Use case:

I'm trying to create a generic HttpRequest type, like so:

type HttpRequest<Body, Query, Path, Headers> = {
  body: Body
  query: Query
  path: Path
  header: Headers
}

And I'd like to be able to use it with any combination of the fields present, and I don't want to make all fields optional. Meaning I'd like to be able to do this:

const x: HttpRequest<{ test: number }, _, { foo: string }, _> = {
  body: { test: 1 },
  path: { foo: "bar" },
}

I used _ as the magic type I'm looking for that lets me ommit the field.

Now I know I could do this:

type HasBody<B> = {
  body: B
}

type HasPath<P> = {
  path: P
}

// ....

And then combine these types:

type CustomRequest = HasBody<{ test: number }> & HasPath<{ foo: string }>

const x: CustomRequest = {
  body: { test: 1 },
  path: { foo: "bar" },
}

But it would save me a lot of time if the above mentioned type existed.

CodePudding user response:

Type safety dictates that if a property might not exist on a type, then it must be optional. This allows you to use stricter tsconfig options and linters which will find type misuses in your code.

The correct and proper way to do what you need is via union types, which you are already aware of, as you mentioned:

type HasBody<T> = {
    body: T
}

type HasPath<T> = {
    path: T
}

type CustomRequest<T> = HasBody<T> | HasPath<T>;

This is 100% type safe, because typescript will require you to identify and cast to one of the specialised types before you can use the properties. You could identify them via decorators, or perhaps with an enum property common to all types.

I know that creating a type for each possibility might be time consuming, but type safety is why we use typescript, right? If was okay to assign an empty object to this, then the type would be meaningless and you might as well just use any.

CodePudding user response:

You're looking to conditionally omit properties from an object, based on whether or not the property is of some "special" type (which you're calling _). There's no built-in functionality to do this, but you can build something that works this way:

type _ = never;
type Omit_<T> = { [K in keyof T as T[K] extends _ ? never : K]: T[K] }

You can define _ to be any property type you don't want to support being part of the object. I've chosen the never type here, but you could use undefined, or a nonce class type with a private member to simulate nominal typing, or anything really that won't interfere with "valid" property types.

Then Omit_<T> will take a type T and omit all the properties whose value is of the _ type. It does this using key remapping in mapped types.

You can verify that it works:

type Z = Omit_<{ a: 1, b: 2, c: _, d: 4 }>
/* type Z = {
    a: 1;
    b: 2;
    d: 4;
} */

Armed with that, you can define Foo

type Foo<T> = Omit_<{
    bar: T
}>;

const x: Foo<_> = {}

and HttpRequest

type HttpRequest<Body, Query, Path, Headers> = Omit_<{
    body: Body
    query: Query
    path: Path
    header: Headers
}>

const y: HttpRequest<{ test: number }, _, { foo: string }, _> = {
    body: { test: 1 },
    path: { foo: "bar" },
}

Looks good. There are probably caveats here, especially around type inference, so you should fully test against your use cases before adopting something like this.


Playground link to code

  • Related