Typescript: define types without a field

386 Views Asked by At

I'm dealing with a legacy dataset and I'm trying to model some meaningful types in typescript. In this example, assume I have data about work experience from an employee curriculum:

EMPLOY            | START    | END
'my first employ' | 20180901 | 20181001
'my last employ'  | 20180901 | null

If END is null, it means it's the actual employ. Since I have several business rules regarding this domain concept, I'd like to model it with types. Here's my code:

interface ActualExperience {
  actual: true,
  employ: string,
  start: Date,
  end: undefined // <-- no end! 
}

interface PreviousExperience {
  actual: false,
  employ: string,
  start: Date,
  end: Date     // <-- end has a value!
}

type Experience = ActualExperience | PreviousExperience

So far so good. Then I wanna use my types:

// this is OK
const previous: PreviousExperience = {
  actual: false,
  employ: 'my first employ',
  start: new Date(),
  end: new Date()
}

// ERROR! 
const actual: ActualExperience = {
  actual: true,
  employ: 'my last employ',
  start: new Date()
}

Typescript requires me to explicitly define end: undefined in order to map to ActualEmploy:

// this is OK now!
const actual: ActualExperience = {
  actual: true,
  employ: 'my last employ',
  start: new Date(),
  end: undefined
}

This is very unpractical for me, as I have to explicitly add an undefined value to a record only to make my compiler happy.

How can I design such type model?

2

There are 2 best solutions below

1
Ben Croughs On BEST ANSWER

declare your interface as:

interface ActualExperience {
  actual: true,
  employ: string,
  start: Date
}

if in later code call actual.end , javascript will return undefined, no need to define it like that on your interface "ActualExperience"

0
Søren D. Ptæus On

There are two ways you can go about this.

First, if there is no specific need to explicitly have end: undefined in your ActualExperience, you can just drop it.

Second, depending on what you are trying to do, it might make more sense to use an additional interface:

interface BaseExperience {
  actual: boolean,
  employ: string,
  start: Date,
  end?: Date
}

You can then specify that your current interfaces implement BaseExperience

interface ActualExperience extends BaseExperience {
  actual: true,
  employ: string,
  start: Date
}

interface PreviousExperience extends BaseExperience {
  actual: false,
  employ: string,
  start: Date,
  end: Date
}

Finally, you can use your BaseExperience directly

const someExperience: BaseExperience = {
  actual: true,
  employ: 'my last employ',
  start: new Date()
}

and use your ActualExperience the way you want to

const actual: ActualExperience = {
  actual: true,
  employ: 'my last employ',
  start: new Date()
}