#Only have properties in interface based on generic value

8 messages · Page 1 of 1 (latest)

green osprey
#

I am currently trying to map an API response where some properties only exist if the parameters were passed.

To do this, I tried this bit of code:

interface FooDetails {
  id: string;
}
interface FooImage {
  url: string;
}

interface Foo<Params extends 'details' | 'image'> {
    someProp: string;
    details: Params extends 'details' ? FooDetails : undefined;
    image: Params extends 'image' ? FooImage : undefined;
}

This technically works as it will only suggest undefined when attempting to access Foo<'details'>#image.
Can I rewrite this to throw a typescript error instead?

main oar
#

Yes you can use intersections of objects to create a type that doesn't have the property

acoustic ospreyBOT
#
type Expand<T> = {
  [K in keyof T]: T[K];
} & {};

interface FooDetails {
  id: string;
}
interface FooImage {
  url: string;
}

type Foo<Params extends 'details' | 'image'> = {
    someProp: string;
}
& (
    Params extends 'details' ? { details: FooDetails } : {}
) & (
    Params extends 'image' ? { image: FooImage } : {}
)

type T = Foo<'details'>
//   ^? - type T = {
//       someProp: string;
//   } & {
//       details: FooDetails;
//   }
type U = Expand<T>
//   ^? - type U = {
//       someProp: string;
//       details: FooDetails;
//   }


const v: T = {
    someProp: 'foo',
    details: {
        id: '1'
    },
    image: {
//  ^^^^^
// Object literal may only specify known properties, and 'image' does not exist in type 'T'.
        url: 'url'
    }
}
green osprey
#

was about to ask if that requires it to be a type, but that explains it. Thanks!

main oar
#

Probably it needs to be a type. But I don't use interfaces unless I need declaration merging, so I'm not an expert on using interfaces.

green osprey
#

well, it's not required to be an interface in my case, so it doesn't matter. Thanks for your help

#

!resolved

acoustic ospreyBOT
#
interface WithDetails {
    details: {
        id: string;
    }
}
interface WithImage {
    image: {
        url: string;
    }
}

type Foo<Params extends 'details' | 'image'> = {
    someProp: string;
} 
&  Params extends 'details' ? WithDetails : {}
&  Params extends 'image' ? WithImage : {}
 
type T = Foo<'details'>
//   ^? - type T = WithDetails

const v: T = {
    someProp: 'foo',
//  ^^^^^^^^
// Object literal may only specify known properties, and 'someProp' does not exist in type 'WithDetails'.
    details: {
        id: '1'
    },
    image: {
        url: 'url'
    }
}