Skip to Content
Fix "Type is not assignable" on a Nested Object
Intermediate
0 likes
Tutorials

Fix "Type is not assignable" on a Nested Object

Read a long assignability error from the bottom, then fix the usual causes: a wrong value, a wrong type and a literal widened to string. After this, a nested mismatch points to one property.

The message Type '...' is not assignable to type '...' is easy when it fits on one line. On a nested object it grows to a block of indented lines, and the useful part is at the bottom. This page shows how to read it, and fixes the three causes that make up most of these errors.

Step 1: read it from the bottom

type Address = { street: string; zip: number };
type Customer = { name: string; address: Address };

function save(customer: Customer): void {
  console.log(customer.name);
}

const input = {
  name: "Ana",
  address: { street: "12 Main St", zip: "1000" },
};

save(input);
nested.ts(13,6): error TS2345: Argument of type '{ name: string; address: { street: string; zip: string; }; }' is not assignable to parameter of type 'Customer'.
  The types of 'address.zip' are incompatible between these types.
    Type 'string' is not assignable to type 'number'.

The first line says the whole argument does not fit. The second line is the path to the problem: address.zip. The last line is the actual cause: a string where a number was expected. In a deeper object you get one more indented line for each level, so always finish at the bottom.

Terminal running npx tsc on nested.ts, showing the error with the file, line and column, the code TS2345, the indented lines pointing to address and zip, and a squiggle under the input argument

The squiggle sits under input, the argument. The mistake is in the object you built earlier, a few lines up. The compiler reports where the two meet.

Step 2: decide which side is wrong

The fix is either to change the value or to change the type. Ask where the data comes from. If the zip code really arrives as text, because it can start with zero, the type is wrong. If it should be a number, the value is wrong.

type Address = { street: string; zip: string };
type Customer = { name: string; address: Address };

function save(customer: Customer): void {
  console.log(customer.name, customer.address.zip);
}

save({ name: "Ana", address: { street: "12 Main St", zip: "1000" } });
Ana 1000

For postal codes, a string is usually the right type. Phone numbers and ids that start with zero are the same.

Step 3: the literal that turned into a string

This one confuses people, because the value looks right.

I call the type HttpRequest, because Request is already a browser type in the default type libraries and would clash.

type HttpRequest = { method: "GET" | "POST"; url: string };

function send(request: HttpRequest): void {
  console.log(request.method, request.url);
}

const options = { method: "POST", url: "/orders" };
send(options);
literal2.ts(8,6): error TS2345: Argument of type '{ method: string; url: string; }' is not assignable to parameter of type 'HttpRequest'.
  Types of property 'method' are incompatible.
    Type 'string' is not assignable to type '"GET" | "POST"'.

Declared as a plain const object, method has the type string, because a property can be changed later. "POST" is a string, but not every string is "GET" or "POST". Two fixes: put the type on the variable, or freeze the literal with as const.

type HttpRequest = { method: "GET" | "POST"; url: string };

function send(request: HttpRequest): void {
  console.log(request.method, request.url);
}

const typed: HttpRequest = { method: "POST", url: "/orders" };
const frozen = { method: "POST", url: "/orders" } as const;

send(typed);
send(frozen);
POST /orders
POST /orders

The annotation is the better default, because it also checks the object where you write it. as const is fine for a fixed table.

Step 4: the array with one wrong item

type Result = { player: string; scores: number[] };

function save(result: Result): void {
  console.log(result.player);
}

const input = { player: "Ana", scores: [10, 20, "30"] };
save(input);

const direct: Result = { player: "Ana", scores: [10, 20, "30"] };
array.ts(8,6): error TS2345: Argument of type '{ player: string; scores: (string | number)[]; }' is not assignable to parameter of type 'Result'.
  Types of property 'scores' are incompatible.
    Type '(string | number)[]' is not assignable to type 'number[]'.
      Type 'string | number' is not assignable to type 'number'.
        Type 'string' is not assignable to type 'number'.
array.ts(10,58): error TS2322: Type 'string' is not assignable to type 'number'.

When the object is built in a separate variable, the compiler can only say that scores holds strings and numbers, and the message is long. When you write the literal where the type is declared, as on the last line, the error lands on the exact element, "30". If you are staring at a long message, move the literal next to its type for a moment and read the short one.

⚠ Warning: Do not fix a nested mismatch with as Customer at the call. The cast silences the compiler, and the wrong value goes on to the code that trusted the type.

Common mistakes

  • Reading the first line only, and trying to fix the whole object.
  • Changing the type to any on the property that complains.
  • Building the object in a separate variable, then wondering why the literal is widened.

Related course: TypeScript Intermediate.

About

Read a long assignability error from the bottom, then fix the usual causes: a wrong value, a wrong type and a literal widened to string. After this, a nested mismatch points to one property.

Responsible
John G
Last Update
10/12/2026
Reading Time
5 min
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.