简体   繁体   中英

Typescript fails to check nested object on function return type

Typescript fails to check on return type if it is a nested object. Consider the code below, Typescript will not send out errors even if the result variable in function A is clearly not matched to the Output type.

interface Input {
  a: string;
  b: number;
}

interface Output {
  c: Omit<Input, 'a'>;
  d: string;
}

function A(input: Input): Output {
  const result = { c: {a: 'aaa', b: 3}, d: 'okay' };

  // Expect to have an error since the result is not matched to Output
  // but there's not errors
  return result;
}

function B(input: Input): Output {
  const result = "{ c: {a: 'aaa', b: 3}, d: 'okay' }";

  // Work as expected
  return result;
}

// But access a property will give us error. 
const test = A({ a: 'a', b: 3 });
test.c.a

Playground here .

Any idea why this happened?

If we explicitly told Typescript that const result: Output =... , it will give us errors. But I'm not sure this is a good practice or not. Is there another way to make it work as expected?

Any idea why this happened?

If we explicitly told Typescript that const result: Output =... , it will give us errors....

TypeScript applies more strict type checking to object literals than it does to other expressions (including variable references). In this case, the "more strict" part when you explicitly provide a type for result is an excess property check .¹ That's a pragmatic check done because typically that's a programmer error, but in the general case excess properties are okay (because the shape of the object still matches the target type, it's just a subtype because it has more properties).

You also get the error if you do the return directly:

function A(input: Input): Output {
    return { c: {a: 'aaa', b: 3}, d: 'okay' };
}

though (subjectively) I generally prefer your original code because it's easier to debug.

It's largely a matter of style, but I would solve the problem by doing this as a general practice when possible:

function A(input: Input) {
//                      ^−−−− No return type annotation
    const result: Output = { c: {a: 'aaa', b: 3}, d: 'okay' };
//                ^−−−−−−−−−− Explicit type

    return result; // <−−−−−− TypeScript infers return type
}

Note that the function's return type is still Output , it's just that TypeScript inferred it from the return rather than our specifying it explicitly in two places.

Playground link


¹ As I write this in April 2021, the TypeScript documentation is going through a major rewrite, and the linked page is listed as "deprecated" with a new to "the new page," but the new page doesn't yet describe excess property checks. Hopefully that's fixed soon.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM