i474
i474

Reputation: 664

Interface property depend on other property

Let's say I have the next interface:

interface TestInterface {
  id?: string;
  type?: string;
}

Is it possible to rewrite in a way that when I perform a check that id !== undefined it'll automatically mean that type property is also defined?

Upvotes: 1

Views: 93

Answers (3)

Nenad
Nenad

Reputation: 26687

You can mimic this with union types.

Simple example:

interface INonNullable {
    id: string;
    type: string;
}
interface INullable {
    id?: undefined;
    type?: undefined;
}
type FinalType = INonNullable | INullable;

function testId(x: FinalType) 
{
  if (x.id !== undefined) {
    x.type // string
  }
}

FinalType is optional, you can simply use (x: INonNullable | INullable) everywhere.

Add type-guard function:

You can also make function with type guard to test for your criteria and narrow the type that way:

interface INonNullable {
    id: string;
    type: string;
}
interface INullable {
    id?: undefined;
    type?: undefined;
}
type FinalType = INonNullable | INullable;

function isNonNullable(x: FinalType): x is INonNullable
{
  return x.id !== undefined;
}

let x = {};

if (isNonNullable(x)) {
  x.type // string;
}

You have more info about this in Typescript documentation: User-Defined Type Guards

Reusable Empty<T>:

Another neat option, as mentioned in the comment by Patrick Roberts, to make more re-usable solution using mapped types and generics:

interface INonNullable {
    id: string;
    type: string;
}
type Empty<T> = {
  [K in keyof T]?: undefined;
}
type FinalType<T> = T | Empty<T>;

function testId(x: FinalType<INonNullable>) {
  if (x.id !== undefined) {
    x.type // string
  }
}

Upvotes: 6

JeromeBu
JeromeBu

Reputation: 1159

Here a way to do what you want, using union :

interface IdWithType {
  id: string;
  type: string;
}

interface WithoutId {
  id?: never;
  type?: string;
}

type TestInterface = IdWithType | WithoutId

// You cannot instansiate an object of type TestInterface with an id and with out a type

const yo: TestInterface = { // Type '{ id: string; }' is not assignable to type 'TestInterface'.
  id: "hey"
}


const testFunction = (a: TestInterface) => {
  if (a.id) {
    const b = a.type // string
  }
  const c = a.type // string | undefined
}

Upvotes: 1

aopanasenko
aopanasenko

Reputation: 478

Lets see your example:

interface TestInterface {
  id?: string;
  type?: string;
}

const objectForTest: TestInterface = {
  id: '12345',
  type: 'some type'
}

You can do it in the following ways:

1) objectForTest?.id

If this operator meet undefined, it will return this value without throwing a TypeError.

This is equvalent to:

const testValue = (objectForTest === null || objectForTest === undefined) ?
    undefined 
    :
    objectForTest.id;

2) objectForTest!.id

In this case you say to type checker: "Hey, I promise you that objectForTest is not null or undefined".

Upvotes: 1

Related Questions