"Property types" X "are incompatible," but this is not

I tried to create an interface object with properties initialized from another object:

id: data.reference.id

      

The properties are compatible, but the typescript compiler throws an error. I don't understand why and how to avoid this error.

(link to check 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 is in the typescript playground

[update] I added a case where a new reference is created from a variable (reference_KO2 is a variable initialized from a data property - and reference_OK2 is the same variable initialized with a constant)

two behaviors with the same variable of compatible type!

+3


source to share


1 answer


As long as the code looks semantically correct, the compiler will disagree because it Data

doesn't store additional information about the subcategory: it only knows what the field type

is Category

.

So we know that reference_ok

can be assigned to either a ReferenceOne

, either ReferenceTwo

. ReferenceOne

demands to type

be CategoryOne

, while ReferenceTwo

demands to type

be CategoryTwo

. None of them work as they data.reference.type

are generic Category

. We could reproduce the same effect with less code by "boosting" the target:

let r: Reference = {
  id: '1',
  type: 1 as Category,
}

      

There are at least two ways to avoid this situation. One is to override Reference

as an interface, which basically tells the compiler that we don't need to know the category at compile time at this point.

interface Reference {
    id: string,
    type: Category,
}

      



The most attractive solution, however, has to do with generics. We can zoom Data

in to indicate the category type as well:

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 you like.

+2


source







All Articles