Reputation: 494
Would someone know why addChildrenDefaultValue
works but addChildrenDefaultValue2
throws
Type '(Omit<T, "children"> & { children: ObjectWithChildren<TestObject>[]; })[]' is not assignable to type 'ObjectWithChildren<T>[]'.
Type 'Omit<T, "children"> & { children: ObjectWithChildren<TestObject>[]; }' is not assignable to type 'ObjectWithChildren<T>'.
Type 'Omit<T, "children"> & { children: ObjectWithChildren<TestObject>[]; }' is not assignable to type 'T'.
'Omit<T, "children"> & { children: ObjectWithChildren<TestObject>[]; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'TestObject'.(2322)
? Is there a better way to do this? (I'm using extends cause in my use case it extends more types)
interface TestObject{
name: string;
children?: TestObject[] | null;
}
type ObjectWithChildren<T> = T & {children: ObjectWithChildren<T>[]};
function addChildrenDefaultValue(
taxonomy: TestObject[]
): ObjectWithChildren<TestObject>[] {
return taxonomy.map(({children, ...props}) => {
return {...props, children: addChildrenDefaultValue(children ?? [])};
});
}
function addChildrenDefaultValue2<T extends TestObject>(
taxonomy: T[]
): ObjectWithChildren<T>[] {
return taxonomy.map(({children, ...props}) => {
return {...props, children: addChildrenDefaultValue2(children ?? [])};
});
}
Upvotes: 1
Views: 354
Reputation: 2912
The reason it throws an error is two fold.
Because your interface references itself, the type T has children of type TestObject[] and not T[] like you were likely wanting. This makes the types potentially incompatible (T could be many things, but TestObject cannot).
The use of map in that way (with a spread, and then the property redefined) is omitting children, and then re-adding it with a non-optional definition. So the type of the recursive type needs to reflect that.
type TestObject<TChild> = {
name: string;
children?: TChild[] | null;
}
type ObjectWithChildren<T extends TestObject<T>> = Omit<T, "children"> &
{children: ObjectWithChildren<T>[]};
function addChildrenDefaultValue2<T extends TestObject<T>>(
taxonomy: T[]
): ObjectWithChildren<T>[] {
return taxonomy.map(({children, ...props}) => {
return {...props, children: addChildrenDefaultValue2<T>(children ?? [])};
});
}
Upvotes: 1
Reputation: 2201
Consider the following type
type TestObjectExtended = TestObject & {
age: number
}
The type of children
property in TestObjectExtended
is TestObject[] | null | undefined
. So, in the function addChildrenDefaultValue2
, property children
of T
will have same type - TestObject[] | null | undefined
.
Now in the callback/mapper of map
function, children
has type TestObject[] | null | undefined
instead of T[] | null | undefined
. In the recursive call of addChildrenDefaultValue2
, children necessarily does not satisfy the constraint of the function as it is TestObject[]
(if not undefined
/null
) but expected is T[]
.
QED
Upvotes: 1