2

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?

balanza
  • 1,059
  • 1
  • 12
  • 34

2 Answers2

2

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()
}
Søren D. Ptæus
  • 4,176
  • 1
  • 26
  • 28
1

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"

Ben Croughs
  • 2,566
  • 1
  • 20
  • 30
  • I accept your answer as it's correct, anyway I realised my case is slightly more complicated. I'm gonna open a new question for this. Thanks anyway ;) – balanza Jan 22 '19 at 10:30