Defining object shape variants in typescript

Viewed 680

I am trying to define an object that can either contain data or an error.

export type ActionResult = {
  data: any;
} | {
  error: any;
};

function test():ActionResult {
    return {
        data: 3
    }
}

When trying to access result of the function I get:

const v = test();
v.data = 23; // Property 'data' does not exist on type 'ActionResult'.  Property 'data' does not exist on type '{ error: any; }'

What's the correct way of accessing either 'data' or 'error'?

2 Answers

Here's how TypeScript understands your code:

const v = test();
// v: {data: any} | {error: any}
v.data = 23;
// It is possible that 'v' is of type '{error: any}'
// In this case, an error might happen at runtime
// This error must be prevented right now - time to throw a TS error!

One way to make sure that this won't happen is to use a type guard to restrict the union type to a type that won't throw a TS error:

const v = test();
// v: {data: any} | {error: any}
if ('data' in v) {
  // v: {data: any}
  v.data = 23;
  // no error!
}

You can also tell TypeScript that you're sure that v will have data by casting v when defining it:

const v = (test() as {data: any});
v.data = 23;

Check this code on typescript playground

Here's the solution with a discriminated union (I've added a common property dataType to each union member):

export type ActionResult = {
  dataType: "value",  
  data: any
} | {
  dataType: "error",  
  error: any
};

function test():ActionResult {
    return {
        dataType: "value",
        data: 3
    }
}

const v = test();
if ("value" === v.dataType){
   v.data = 23;
}
Related