How to deal with TS errors about required fields that are only about to be filled?

Viewed 178

Pretty standard use case would be to create an empty object and fill it with the necessary data. At the end of the filling process, the object is going to contain all of its required properties, but when it gets created it has none and thus TypeScript wont allow me to type this new object with the correct type.

Example:

interface ISomeType {
  x: string;
  y: string;
}

function buildObj(): ISomeType {
  const obj: ISomeType = {}; // TS Error: {} doesn't include x and y
  obj.x = foo();
  obj.y = bar(); // At the end of the filling process, the object is correct

  return obj;
}

How do I deal with this issue? How do I make TypeScript understand that this new object is supposed to be of the specified type, it just doesn't have the required properties yet and will have them at the end?

If I mark obj with the type Partial<ISomeType> then I can't mark the function return type with ISomeType which is my intention.

4 Answers

I would construct parts first and just return object literal:

function buildObj(): ISomeType {
    const x = foo(); // const x: ISomeType['x'] = foo();
    const y = bar();
    return {
        x,
        y
    };
}

There is no reason to create the object before returning it.

Try this:

function buildObj(): ISomeType {
  const obj: Partial<ISomeType> = {}; // TS Error: {} doesn't include x and y
  obj.x = foo();
  obj.y = bar(); // At the end of the filling process, the object is correct

  return obj as ISomeType;
}

You could also populate the object from the start with values of the fields

function buildObj(): ISomeType {
 const obj: ISomeType = {
   obj.x = foo();
   obj.y = bar();
 }

 return obj;
}

You should be able to gather all of the information you need before constructing the object and then create it. This will ensure type safety.

Don't DO

Type Assertion. Even though it will allow you to bypass the error. It will cause you a lot of problem in a long run. Because it practically says that this object is a valid object of this particular type, whereas its not.

<ISomeType>{}

DO

Utility Types. They will allow you to modify source object according to your needs.

Partial

Constructs a type with all properties of T set to optional. This utility will return a type that represents all subsets of a given type.

interface ISomeType {
  x: string;
  y: string;
}

const someType:Partial<ISomeType> = {};

// Behind the scene it does following:
interface ISomeType {
  x?: string;
  y?: string;
}

Pick

Constructs a type by picking the set of properties K from T

const someType:Pick<ISomeType, 'x'> = {
  x: 0
};

Furthermore. I'd recommend you to skim through TypeScript Coding Guidelines. In TypesScript interfaces shall not be prefixed with I, like in C#.

Related