'How to extend anonymous type in Typescript
When chaining functionality in typescript
with anonymous types for example like this:
let array = [{ seed: 2 }, { seed: 3 }];
array
.map(i => ({ seed: i.seed, square: i.seed * i.seed }))
.forEach(i => console.log(`square for ${i.seed} is ${i.square}`));
I need to define new anonymous type for map function. If I would have multiple steps all producing new properties, I'd end up writing lots of definition code to get all properties carried over.
I could use $.extend
(or Object.assign
), but that way I'll lose intellisense and strong typing.
array
.map(i => $.extend(i, { square: i.seed * i.seed }))
.forEach(i => console.log(`square for ${i.seed} is ${i.square}`));
How can I extend anonymous object without defining all properties again while keeping strong typing?
Solution 1:[1]
I have finally found a solution. This can be achieved by Intersection Types. Those can be used with anonymous types and classes. In the example below, extend function will copy properties from each of objects and return an object of Intersection Type. This will reduce considerable amount of type definition code without losing intellisense and strong typing.
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
let array = [{ seed: 2 }, { seed: 3 }];
array
.map(i => extend(i, { square: i.seed * i.seed }))
.map(i => extend(i, { cube: i.square * i.seed }))
.forEach(i => console.log(`square for ${i.seed} is ${i.square} and cube is ${i.cube}`));
Same in Playground
This is implemented f.e. in core-js and its type definitions return Intersection Type:
assign<T, U>(target: T, source: U): T & U;
Solution 2:[2]
How about:
interface A {
seed: number;
}
interface B extends A {
square: number;
}
let array: A[] = [{ seed: 2 }, { seed: 3 }];
array
.map<B>(a => {
return { seed: a.seed, square: a.seed * a.seed }
})
.forEach(b => console.log("square for ${b.seed} is ${b.square}"));
or (if you want to keep things anonymous):
let array = [{ seed: 2 }, { seed: 3 }];
array
.map<{seed: number, square: number}>(a => {
return { seed: a.seed, square: a.seed * a.seed }
})
.forEach(b => console.log("square for ${b.seed} is ${b.square}"));
(use it in playground)
Solution 3:[3]
To add and easier to understand answer about how one can extend a type with an anonymous type by using intersection types, I made this example that illustrates it:
interface Animal {
name: string
}
// With named types:
interface Bear extends Animal {
honey: boolean
}
function giveHoney(bear: Bear) {
bear.honey = true;
}
const myBear: Bear = { name: 'Bob', honey: false };
giveHoney(myBear);
// With anonymous types (no need to explicitly define the Bear type):
function giveHoney(bear: Animal & { honey: boolean }) {
bear.honey = true;
}
const myBear = { name: 'Bob', honey: false };
giveHoney(myBear);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Jukka Varis |
Solution 2 | Nitzan Tomer |
Solution 3 | cprcrack |