0

was wondering if there is any way for typescript to realise the type of the object by deriving it from the base control descendants

interface Config {
  name: string;
}

interface Input extends Config {
  placeholder?: string;
}

interface Select extends Config {
  options: Array<Option>;
}

const fields: Array<Config> = [
  { name: 'a', placeholder: 'a'}, // placeholder does not exist in type 'config'
  { name: 'b', options: []} // options does not exist in type 'config'
]

also attempted something like

interface Config<T> {
  name: string;
  fieldType: T
}

interface Input extends Config<'input'> {
  placeholder?: string;
}

can anyone redirect?

1
  • Can you show your use case? Why do you need to annotate fields at all? You can write const fields = [ {name: 'a', placeholder: 'a'}, {name: 'b', options: []} ] and get an object which will be accepted by anything that requires a value of type Array<Config>. Do you need to guarantee that each element of fields conforms to one of your declared extensions of Config? If so, you'll need a union of those types somewhere in your code, which is what @ThomasThiebaud's answer suggests, but you say you want to avoid that. Commented Oct 29, 2019 at 14:23

2 Answers 2

1

If you want to enforce strong typing for all individual elements in fields, while keeping its Array<Config> type, this would be a good place for type assertions.

const fields: Array<Config> = [
  { name: 'a', placeholder: 'a'} as Input, 
  { name: 'b', options: [] } as Select,
  { name: 'a', placeholder: 42} as Input,  // error: placeholder has number
   {options: []} as Select // error: name is missing
]

Playground

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

3 Comments

Type assertions would allow {name: 'a'} as Input, though, which is probably not ideal.
I think this is it... as obviously I can't really ask typescript to find a matching descendant without specifically declaring it, as @ThomasThiebaud suggested. otherwise it will scan the whole universe. which maybe should be a feature sometime. this one is accepted because it allows dynamic integration as well, without strong typing all of the current available types.
hm not sure, what you mean exactly, as placeholder is an optional property of Input. So I would consider {name: 'a'} as assignable to Input.
1

The easiest way to achieve that is to use

const fields: Array<Select | Input> = [
  { name: 'a', placeholder: 'a'},
  { name: 'b', options: []}
]

It allows you to correctly use the parent type Config like that later on

function displayName(config: Config) {
  console.log(config.name)
}

fields.forEach(config => displayName(config))

2 Comments

tried to avoid this for the case of dozens of descendants... gonna have to declare a type ConfigDescendats = Input | Select | TextArea | AnyOtherExtended Was hoping to be exposed to some kind of a built-in annotation like the Partial<Type>.. maybe InstanceOf<Config>
but thanks.. the loss of hope had me do exactly this while adding fieldType: FIELD_TYPES to Config and a specification of the fieldType in every of the descendants: Input { fieldType: FIELD_TYPES.INPUT, placeholder?: string }; so that typescript now exposes the correct allowed fields based on the fieldType`

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.