1

I have a script that works a bit like an assembly line. It starts with a dictionary with an object with only one property and adds one more property after each step.

I started with plain JS and am now converting this to TS.

I have modeled this now as a Typescript interface with one mandatory property and a lot of optional properties:

interface CarBatch {
  [key: string]: {
    part1: string;
    part2?: string[];
    part3?: SteeringWheel;
    part4?: string;
    part5?: string;
    part6?: string;
  }
}

However, Typescript complains a lot about this, as accessing these optional properties always yields "Object is possibly 'undefined'". For example console.log(carBatch['porsche'].part4)

I tried to use a base interface and extends but this was difficult because of the dictionary and because my assembly steps always would start with one kind of type and would need to modify pieces of it to another type in the middle.

What would be the correct way to model this?

3
  • 1
    why don't you use question mark for handing it like this : console.log(carBatch?.['porsche']?.part4) Commented Feb 7, 2023 at 9:35
  • Check out user-defined type guard functions and assertion functions in the TS handbook. I also wrote an answer explaining their usage here. Commented Feb 7, 2023 at 9:35
  • ^ Those concepts can be used to inform the compiler of runtime mutations like you described in your question. However, without a minimal reproducible example, it's hard to know what exactly you're struggling with. Can you update the question to include one (and also provide a link to it in the TS Playground)? Commented Feb 7, 2023 at 9:37

3 Answers 3

0

As per Ali's comment, this could be handled by adding question marks to the console.log:

console.log(carBatch?.['porsche']?.part4)

Sign up to request clarification or add additional context in comments.

Comments

0

Your code seems to loosely follow the builder pattern. As such, you may type it like this:

function addStage<P extends Record<string, unknown>, K extends string, V>(
  previous: P, key: K, value: V
): P & { [key in K]: V } {
  return {
    ...previous,
    [key]: value,
  };
}

// Usage
const stage1 = addStage({}, 'part1', 1);
//    ^?
const stage2 = addStage(stage1, 'part2', 'value');
//    ^?
const stage3 = addStage(stage2, 'part3', [1, 2, 3]);
//    ^?

Typescript playground link

Comments

0

You can put the built-in utility type Pick to good use.

Let's declare a completed Car as a separate interface first:

interface Car {
  part1: string;
  part2: string[];
}

I'm omitting part3 through part6 to keep things short, but rest assured that this approach scales well.

An unfinished car is represented as the type PartialCar, with a generic argument K that defines which keys must already be present:

type PartialCar<K extends keyof Car> = Pick<Car, K>;

Since CarBatch might contain unfinished cars, we make it generic:

interface CarBatch<C> {
  [key: string]: C
}

Note that there's no need to restrict the type C.

Let's see how we could create a new batch of cars that only have part1 assigned:

function startBatch(): CarBatch<PartialCar<'part1'>> {
  return {
    'porsche': {
      part1: 'one'
    }
  }
}

To add a new part part2 to all cars, we accept any unfinished car type C, and return a batch that contains more finished car type C & PartialCar<'part2'>:

function addPart2<C>(batch: CarBatch<C>): CarBatch<C & PartialCar<'part2'>> {
  const outputBatch: CarBatch<C & Pick<Car, 'part2'>> = {};
  for (const key of Object.keys(batch)) {
    outputBatch[key] = {
      ...batch[key],
      part2: ['left', 'right'],
    };
  }
  return outputBatch;
}

This function works regardless of type C, but you can easily restrict it. For example, use C extends PartialCar<'part1'> if the assignment of part2 depends on part1 being present.

A batch of finished cars is of course a CarBatch<Car>:

function printBatch(batch: CarBatch<Car>) {
  console.log(batch);
}

To show that it all works:

let batch = startBatch();
let finishedBatch = addPart2(batch);
printBatch(finishedBatch);

Playground link

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.