I tried to create an object of an interface with properties initialized from another object like that:
id: data.reference.id
The properties are compatible, but typescript compiler throws an error. I don't understand why and how to avoid this error.
(a link to test the code can be found after)
// INTERFACES AND TYPES
type CategoryOne = 1
type CategoryTwo = 2
type Category = CategoryOne | CategoryTwo
interface ReferenceOne {
id: string,
type: CategoryOne
}
interface ReferenceTwo {
id: string,
type: CategoryTwo
}
type Reference = ReferenceOne | ReferenceTwo
// DIRECT INIT
let reference_OK: Reference = { // works here
id: '1',
type: 1
}
// INIT FROM DATA
interface Data {
reference: {
id: string,
type: Category
}
}
let data: Data = {
reference: {
id: '1',
type: 1
}
}
let reference_KO: Reference = { // <--- error here
id: data.reference.id, // but compatible property
type: data.reference.type // but compatible property
}
let param: Category = data.reference.type // == 1
let reference_KO2: Reference = { // <--- error here
id: data.reference.id, // but compatible property
type: param // but compatible property
}
param = 1 // == data.reference.type, same variable
let reference_OK2: Reference = { // <--- no error here
id: data.reference.id,
type: param
}
this code in Typescript playground
[update] I added the case when a new reference is created from a variable (reference_KO2 - variable initialized from a property of data - and reference_OK2 - same variable initialized with a constant)
two behaviors with the same variable of a compatible type !
While the code appears to be semantically correct, the compiler will disagree because Data
does not retain additional information about the category underneath: it only knows that the field type
is a Category
.
So, we know that reference_ok
can be assigned either a ReferenceOne
or a ReferenceTwo
. ReferenceOne
requires type
to be a CategoryOne
, whereas ReferenceTwo
requires type
to be a CategoryTwo
. Neither of these work, since data.reference.type
is a generalized Category
. We could reproduce the same effect with less code, by "upcasting" the value on purpose:
let r: Reference = {
id: '1',
type: 1 as Category,
}
There are at least two ways to avoid this situation. One of them is to redefine Reference
as an interface, which basically tells the compiler that we don't need to know the category in compile time at this point.
interface Reference {
id: string,
type: Category,
}
The most appealing solution, however, is with generics. We can augment Data
to also contain the type of the category:
interface Data<C extends Category> {
reference: {
id: string,
type: C
}
}
At this point, this code works:
let data: Data<CategoryOne> = {
reference: {
id: '1',
type: 1
}
}
let reference_KO: Reference = {
id: data.reference.id,
type: data.reference.type
}
The same treatment can be applied to Reference
, if so you wish.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With