Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type checking on Array.map that returns object literals

Tags:

typescript

I've created this example in the TypeScript playground:

interface Test{
    a: string
    b: string
}

const object: Test = {
    a: 'b',
    b: 'c',
}

function testIt(): Test[] {
    const data = [{b: '2', c: '3'}]
    const prices: Test[] = data.length ? data.map((item) => {
        return {
            a: item.b,
            b: item.c,
            c: '2',
            d: '3'
        }
    }) : [];
    return prices;
}

Removing either a or b property from the object return statement in the array map method results in a TypeScript error (as expected).

Adding c or d or any other random unknown property does not trigger a TypeScript error. I would suspect that this is only possible if the interface contains [x: string]: any.

So why does Array.map do type checking on missing properties on an interface but not on additional / unknown properties?

like image 292
Kees van Lierop Avatar asked Dec 13 '18 08:12

Kees van Lierop


People also ask

How do you map an array of objects?

The syntax for the map() method is as follows: arr. map(function(element, index, array){ }, this); The callback function() is called on each array element, and the map() method always passes the current element , the index of the current element, and the whole array object to it.

What does map return in TypeScript?

Return Value: This method returns the created array. Below examples illustrate the Array map() method in TypeScript.

How do you map an array of objects to another object?

To convert an array of objects to a Map :Use the Map() constructor to create a new Map . Call the forEach() method on the array. On each iteration, add the key-value pair to the new Map .

How do you type a map in TypeScript?

Use Map type and new keyword to create a map in TypeScript. let myMap = new Map<string, number>(); To create a Map with initial key-value pairs, pass the key-value pairs as an array to the Map constructor.


1 Answers

The problem is that according to basic OOP rules, a derived type (ie one with more properties) should be compatible with the base type (ie. the type with just a and b).

This being said, typescript does warn us when assigning object literals with more properties where a type with fewer properties is expected. This is called excess property checking. This feature only kicks in on DIRECT assignment to something that is o a given type.

The reason this excess property check does not apply in your case is the way type checking is done for map. First the callback return type is inferred based on the object literal, so it is inferred as { a: string, b: string, c: string, d: string }. Then this type is used as the return type for map, so map will return an Array<{ a: string, b: string, c: string, d: string }>. This is then assigned to Test[] which under the first rule is allowed. Nowhere did we assign an object literal to a place where Test was expected.

One way to get an error is to not let typescript infer the result of callback passed to map. We can do this by adding the annotation to the callback. Then we are directly assigning an object literal to a location that expects Test:

const prices = data.length ? data.map((item) : Test => {
    return {
        a: item.b,
        b: item.c,
        c: '2', // error
        d: '3'
    }
}) : [];
like image 107
Titian Cernicova-Dragomir Avatar answered Oct 05 '22 04:10

Titian Cernicova-Dragomir