Mike76
Mike76

Reputation: 979

TypeScript: Restrict an existing interface by restricting certain properties with constraints

I have the following TypeScript interface that is used as a database entity for an ORM library:

export interface Entity {
  id?: number;
  someColumn: string;
  someOtherValue: number;
  otherColumn: string;
}

Notice the optional id property, which is either the primary key of the entity or undefined. If it is undefined, then this means that the entity does not exist in the underlying database.

However, many functions only accept Entity-objects that have a valid id. Therefore, I would like to introduce a new interface that looks like this (without the "?"):

export interface ValidEntity {
  id: number;
  someColumn: string;
  someOtherValue: number;
  otherColumn: string;
}

Now my problem is that I do not want to duplicate all the properties from the original Entity-interface. How can I "extend" the Entity-interface with a constraint to enforce that id must not be undefined?

Reversing The Question

Another question is the same thing in the opposite direction. Suppose that we already have the ValidEntity interface and want to create an Entity interface that relaxes the id property to allow undefined. How can we accomplish this relaxation without duplicating properties?

Upvotes: 2

Views: 1913

Answers (1)

joshwilsonvu
joshwilsonvu

Reputation: 2679

Although there might be other ways that produce prettier error messages, a quick intersection type will do the trick.

export interface Entity {
  id?: number;
  someColumn: string;
  someOtherValue: number;
  otherColumn: string;
}

type ValidEntity = Entity & { id: number };
// Alternate solution:
// type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
// type ValidEntity = RequiredBy<Entity, "id">

function f(e: ValidEntity) { }

let obj = {
    someColumn: "1",
    someOtherValue: 2,
    otherColumn: "3"
}

f(obj);
// Property 'id' is missing in type '{ someColumn: string; someOtherValue: 
// number; otherColumn: string; }' but required in type '{ id: number; } '.

Going the opposite direction is a little bit more tricky. Based on this answer, you can use utility types as follows:

type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

interface ValidEntity {
  id: number;
  someColumn: string;
  someOtherValue: number;
  otherColumn: string;
}

type Entity = PartialBy<ValidEntity, 'id'>

Upvotes: 2

Related Questions