1

I have the following interfaces:

interface Movie {
  id: number;
  title: string;
}

interface Show {
  title: string;
  ids: {
    trakt: number;
    imdb: string;
    tmdb?: number;
  };
}

interface Props {
  data: Movie | Show;
  inCountdown: boolean;
  mediaType: "movie" | "tv"
}

I know that if mediaType is equal to "movie" then data is always going to be of a custom type called Movie. I want my code to know this without casting or using as when using data.

I do not want to do something like this, if possible:

let docId = "";
if (mediaType === "movie") {
  docId = (data as Movie).id.toString();
}

How can I shape this interface so I can use

let mediaType = "movie"
let data = {id: 1, title: "Dune"}
let docId = "";

if (mediaType === "movie") {
  docId = data.id.toString();
}
if (mediaType === "tv") {
  docId = data.show.ids.trakt.toString();
}

without errors or warnings like Property 'show' does not exist on type '{ id: number; title: string; }'?

Here's a working example.

8
  • This is silly. You can't avoid your second code block - if you can, then I certainly don't know how and I'm very curious as to how if I'm wrong! Commented Aug 3, 2021 at 2:35
  • Well, okay, you could - if you wrote a map and addressed it with each element, but that seems like more work than the second code block. Ok I'm done now. I'll wait for answers. Commented Aug 3, 2021 at 2:36
  • You can probably do this by making Props a discriminated union, but right now the code here isn't quite a minimal reproducible example suitable for dropping into a standalone IDE like the TypeScript Playground (link). Could you edit the example code so that it demonstrates what you're seeing without unrelated errors (e.g., mediaType, data, TMDB, IGDB, and Trakt are not defined; could you either provide typings for them or change them?) Commented Aug 3, 2021 at 2:44
  • beat me to it, was just making an example Commented Aug 3, 2021 at 2:45
  • @jcalz Added to the example code. Commented Aug 3, 2021 at 2:55

2 Answers 2

1

You can just make a union to make sure that illegal state in unrepresentable. In other words - you should make sum type instead of product type.

interface Movie {
  id: number;
  title: string;
}

interface Show {
  title: string;
  ids: {
    trakt: number;
    imdb: string;
    tmdb?: number;
  };
}


interface MovieProps {
  data: Movie;
  inCountdown: boolean;
  mediaType: "movie"
}

interface ShowProps {
  data: Show;
  inCountdown: boolean;
  mediaType: "tv"
}

type Props = MovieProps | ShowProps

const Component=(props:Props)=>{
  if(props.mediaType==='tv'){
    props.data // Show
  } else{
    props.data // Movie
  }
}

Playground

Sign up to request clarification or add additional context in comments.

Comments

0

The conditional type concept in Typescript is different than Narrowing the type. Here, we need to narrow down the type which we want.

In your example, you also need to check whether any requied key name is present in the data object. This means that the type of data object will get narrowed down to the required type.

You could check keys(or property name) in an object using the in operator. See the working example below.

let mediaType = "movie"
let data = {id: 1, title: "Dune"}
let docId = "";


function fn(myData: Props['data']){
  if (mediaType === "movie" && 'id' in myData) {
    docId = myData.id.toString(); // this way you can get rid of typecasting
  }
  
  if(mediaType === "tv" && 'ids' in myData){  
    docId = myData.ids.trakt.toString();
  }
}

fn(data); 

Playground

Demo:

interface Movie {
  id: number;
  title: string;
}

interface Show {
  title: string;
  ids: {
    trakt: number;
    imdb: string;
    tmdb?: number;
  };
}

interface Props {
  data: Movie | Show;
  inCountdown: boolean;
  mediaType: "movie" | "tv"
}

let mediaType = "movie"
let data = {id: 1, title: "Dune"}
let docId = "";


function fn(data1: Props['data']){
  if (mediaType === "movie" && 'id' in data1) {
    docId = data1.id.toString();
  }
  
  if(mediaType === "tv" && 'ids' in data1){  
    docId = data1.ids.trakt.toString();
  }
}

fn(data);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.