27

I'm creating a function that handles objects from the database. I have two different data structures where the same property has a different name. I can't change that, so I have to handle it in JavaScript.
The objects have other differences, but that's not important to this function.
I want to use the same function for two different types of objects. Here's sample code demonstrating my problem:

interface TypeA {
    itemName: string;
}

interface TypeB {
    itemTitle: string;
}

function getItemName(item: TypeA | TypeB): string {
    let name = '';

    if (item.hasOwnProperty('itemName')) {
        name = item.itemName;
    } else {
        name = item.itemTitle;
    }

    return name;
}

Of course, this code runs. But the IDE marks both the lines name = item.itemName; and name = item.itemTitle; as errors ("Property does not exist on type"), because both types do not have both properties.

So, what's the proper typescript way to do this?

KWeiss
  • 2,954
  • 3
  • 21
  • 37

7 Answers7

27

You need to create a User Defined Type Guard, then you can use an if statement and get the correct typing.

function isTypeA(value: TypeA | TypeB): value is TypeA {
    return value.hasOwnProperty('itemName');
}

Then you can get the typing much cleaner:

function getItemName(item: TypeA | TypeB): string {
    return isTypeA(item) ? item.itemName : item.itemTitle;
}

Check it out here. Item is correctly cast to either TypeA or TypeB.

Daryl
  • 18,592
  • 9
  • 78
  • 145
27

I might be a little bit late, but you could give this a try inside your function:

if ('itemName' in item) {
    name = item.itemName;
} else {
    name = item.itemTitle;
}
theodosis
  • 894
  • 5
  • 15
8

you can make a type assertion if you don't do this too often :

if (item.hasOwnProperty('itemName')) {
    name = (item as TypeA).itemName;
} else {
    name = (item as TypeB).itemTitle;
}

or

if (item.hasOwnProperty('itemName')) {
    name = (<TypeA>item).itemName;
} else {
    name = (<TypeB>item).itemTitle;
}

if you need to make this check more than once or twice, you'd better writing a type guard as @Daryl suggests.

n00dl3
  • 21,213
  • 7
  • 66
  • 76
2
interface TypeA {
  a: string
}
interface TypeB {
  b: string
}

const testFunction = (x: TypeA | TypeB): string => {
  return (x as TypeA).a || (x as TypeB).b;
}

testFunction({ a: 'Hello' }); // 'Hello'
testFunction({ b: 'World' }); // 'World'

Up209d
  • 4,364
  • 4
  • 13
  • 10
1

Intellij accepts this syntax:

function getItemName(item: TypeA): string;
function getItemName(item: TypeB): string;
function getItemName(item): string {
    return item.hasOwnProperty('itemName') ? item.itemName : item.itemTitle;
}

the official way according to the typescript docs is this: https://www.typescriptlang.org/docs/handbook/functions.html

Laurence Mommers
  • 687
  • 5
  • 12
1

I won't complicate things. If you're really sure that your object has either the one or the other property, a name = item['itemName'] || item['itemTitle'] or name = item.hasOwnProperty('itemName') ? item['itemName'] : item['itemTitle'] would be sufficient.

Note that TypeScript usually stops complaining if you access properties using the bracket notation instead of the dot notation. I would suggest adding a comment, though.

SVSchmidt
  • 6,269
  • 2
  • 26
  • 37
0

Use typeguards:

interface TypeA {
    itemName: string;
}

interface TypeB {
    itemTitle: string;
}

function isTypeA(val: any): val is TypeA
{
    return val.hasOwnProperty('itemName');
}

function isTypeB(val: any): val is TypeB
{
    return val.hasOwnProperty('itemTitle');
}

function getItemName(item: TypeA | TypeB): string
{
    let name = '';

    if (isTypeA(item))
    {
        name = item.itemName;
    }
    else
    {
        name = item.itemTitle;
    }

    return name;
}
Amid
  • 21,508
  • 5
  • 57
  • 54