TS2322 error when trying to add a property to an anonymous object

eye-catchJavaScript/TypeScript

TS2322 error happens when trying to add a property to an anonymous object. Haven’t you faced this case where you need to create an object first with some values and then, add additional property depending on the values.

interface Result {
    subject: string;
    score: number;
    result: "Pass" | "Failure";
}

let result = {
    score: 21,
    subject: "Math",
};

if (result.score > 60) {
    result = {
        ...result,
        // Type '{ result: string; score: number; subject: string; }' is not assignable to type '{ score: number; subject: string; }'.
        // Object literal may only specify known properties, and 'result' does not exist in type '{ score: number; subject: string; }'.ts(2322)
        result: "Pass",
    }
    // Element implicitly has an 'any' type because expression of type '"result"' can't be used to index type '{ score: number; subject: string; }'.
    // Property 'result' does not exist on type '{ score: number; subject: string; }'.ts(7053)
    result["result"] = "Pass";
}

This doesn’t work because of TS2322 error or TS7053 error. For TS7053 error, the object needs to have a key-value definition like the following.

type KeyValue = { [key: string]: string; };

You can learn the detail in the following post.

But the solution is not good because the type doesn’t match the original type “Result”.

Sponsored links

Make only desired properties optional

The solution is to create a type that matches the implementation. In this case, only “result” property needs to be optional.

TypeScript offers some utility types but none of them can make the specified properties optional. Let’s create our own utility type if it doesn’t exist. We use the following utility types.

  • Partial makes all properties optional.
  • Omit removes specified properties
// Type definition
type Optional<T, K extends keyof T> = Partial<T> & Omit<T, K>;
// Usage
type OptionalResult = Optional<Result, "result">;
// subject
// score
// result?  <--- optional

Optional<T, K extends keyof T> requires a type on the first argument. The second argument is the keys of the type.

Partial<T> makes all properties optional.
Omit<T, K> removes the specified properties but other properties are not optional.

When the two definitions are together, we can make only specified properties optional.

Partial<Result>         Omit<Result, "result">      Result
subject?            +   subject             ->      subject
score?              +   score               ->      score
result?             +   -                   ->      result?

Make only desired properties mandatory and others optional

Required type makes all properties mandatory. If we want to make some of the properties mandatory, we need to create our own type.

We can create the type by using the following two types.

  • Partial makes all properties optional.
  • Pick picks specified properties
// Type definition
type Mandatory<T, K extends keyof T> = Partial<T> & Pick<T, K>;
// Usage
type MandatoryResult = Mandatory<Result, "subject" | "score">;
// subject
// score
// result?  <--- optional

The behavior is the same as the previous type.

Partial<Result>         Pick<Result, "subject" | "score">      Result
subject?            +   subject                        ->      subject
score?              +   score                          ->      score
result?             +   -                              ->      result?

Using the our own utility types

By defining a proper type, we can add the additional property in both ways.

let mandatoryResult: MandatoryResult = {
    subject: "Math",
    score: 61
};

let optionalResult: OptionalResult = {
    score: 11,
    subject: "hoge",
};

function addResult(obj: OptionalResult | MandatoryResult) {
    if (obj.score > 60) {
        obj = {
            ...obj,
            result: "Pass",
        };
        console.log(obj);
    } else {
        obj["result"] = "Failure";
        console.log(obj);
    }
}

addResult(optionalResult); 
// { score: 11, subject: 'hoge', result: 'Failure' }
addResult(mandatoryResult);
// { subject: 'Math', score: 61, result: 'Pass' }

Conclusion

If we define the proper type, the code is clearer and more maintainable. If the type is not defined, IDE doesn’t rename the property name and thus, it takes a longer time to fix them.

Use the following utility types to make selected properties optional/mandatory.

type Optional<T, K extends keyof T> = Partial<T> & Omit<T, K>;
type Mandatory<T, K extends keyof T> = Partial<T> & Pick<T, K>;

type OptionalResult = Optional<Result, "result">;
type MandatoryResult = Mandatory<Result, "subject" | "score">;

Comments

Copied title and URL