dericcain
dericcain

Reputation: 2290

TypeScript advanced type object key

I have a class Form that has this signature:

interface IFormSchema {
  name: string;
  // Removed irrelevant fields...
}

const schema: IFormSchema[] = [{ name: 'firstName' }];

// Form Interface
interface IForm {
  fields: IFields;
  // Removed irrelevant fields...
}

// Here is the IFields interface used in IForm
interface IFields {
  // The key here is what I want typed **************
  [key: string]: IField;
}

const form: IForm = new Form(schema: IFormSchema[]);

The schema array is iterated and each object is converted to a Field with the IField interface:


interface IField {
  form: IForm;
  name: string;
  // Removed irrelevant fields...
}

Now, when I new up the Form and then I access the Form.fields, and can access the field by its name like so form.fields.firstName, I want firstName to be typed so that if I try and access form.fields.wrongFieldName, TypeScript will throw an error.

How would I do this? Any help would be greatly appreciated.

Upvotes: 0

Views: 2298

Answers (3)

kaya3
kaya3

Reputation: 51034

This is possible, but you need to use generic types and type inference. If you have an explicit type annotation like : IForm then there's nothing to allow one IForm to have a firstName field while another IForm lacks that field.

type IFormSchema<K extends string> = { name: K }
type IFields<K extends string> = Record<K, string>

class Form<K extends string> {
  public fields: IFields<K>;
  constructor(schema: readonly IFormSchema<K>[]) {
    // initialise this.fields here
    throw new Error('Not implemented');
  }
}

Example usage:

// no type annotation, and must use `as const` to infer string literal types
const schema = [{name: 'firstName'}, {name: 'lastName'}] as const;
// no type annotation; allow the type argument K to be inferred
const form = new Form(schema);

let ok1: string = form.fields.firstName;
let ok2: string = form.fields.lastName;
let error: string = form.fields.address; // property 'address' does not exist

Playground Link

Upvotes: 3

DesTroy
DesTroy

Reputation: 410

It Isn't Possible. Unless you define the fields in a separate Record Type to define the [key: string] as an Enum, or in a RecordsType.

enum FieldsEnum = {
  FIRST_NAME = 'firstName',
  LAST_NAME = 'lastName',
};

type BaseFieldsType = {
 // other fields here 
 // and then the below 
 [key in FieldsEnum]?: IField;
};

interface IFields extends BaseFieldsType {}; 

Upvotes: 0

night_owl
night_owl

Reputation: 926

You just need to specify it in the keys for IFields:

interface IFields {
  firstName: IField;
}

In fact, if you are sure of what keys will be available inside of IFields, you can get rid of the index signature and just use keys.

Upvotes: -1

Related Questions