Home > Back-end >  Typescript: How to create a new key/value pair type that is exactly in the same shape as one of the
Typescript: How to create a new key/value pair type that is exactly in the same shape as one of the

Time:06-05

I have the following data:

const pets = [
  {
    type: 'cat',
    name: 'penny'
  },
  {
    type: 'dog',
    name: 'Fido'
  },
  {
    type: 'fish',
    name: 'Nemo'
  }
];

A pet should only be considered valid if it's in either one of those 3 exact shapes (i.e. the key AND the value must match). For example this is a valid pet:

  {
    type: 'dog',
    name: 'Fido'
  }

These are not:

  {
    age: 'dog',
    name: 'Fido'
  },

and

  {
    type: 'dog',
    name: 'Sammy'
  },

etc..

My actual object is much larger than this so I don't want to manually define this type. How can it be done dynamically?

I tried this but to no avail:

type Pet = {
  [key in keyof typeof pets]: typeof pets[key];
};

CodePudding user response:

You need to use an as const assertion on pets to tell typescript to infer the most specific types that it can. Other wise pets is just typed as { type: string, name: string }[], which isn't helpful.

const pets = [
  {
    type: 'cat',
    name: 'penny'
  },
  {
    type: 'dog',
    name: 'Fido'
  },
  {
    type: 'fish',
    name: 'Nemo'
  }
] as const;

Now pets is typed as:

const pets: readonly [{
    readonly type: "cat";
    readonly name: "penny";
}, {
    readonly type: "dog";
    readonly name: "Fido";
}, {
    readonly type: "fish";
    readonly name: "Nemo";
}]

Note how there are three specific objects with different specific strings.

Now the Pet type is simply:

type Pet = typeof pets[number]
/*
{
    readonly type: "cat";
    readonly name: "penny";
} | {
    readonly type: "dog";
    readonly name: "Fido";
} | {
    readonly type: "fish";
    readonly name: "Nemo";
}
*/

Indexing an array type by number gets a union of all array member types. In this case, each specific type of pet.

And now this works as you expect:

const fido: Pet = { // works
    type: 'dog',
    name: 'Fido'
}

const bad1: Pet = { // error because Nemo is a fish
    type: 'cat', 
    name: 'Nemo'
}

const bad2: Pet = { // error because penny is a cat
    type: 'dog',
    name: 'penny'
}

See playground

CodePudding user response:

You can also use Record with an enum to fix the types with certain values. I don't know if that is exactly what you want but you can try.

// Define the keys here they will make sure that nothing
// other then these keys exist here.
enum Keys {
  TYPE = "type",
  NAME = "name",
}

// Create a Record type that can only take this key but any string value
type Pet = Record<Keys, string>;

const pets: Pet[] = [
  {
    type: "cat",
    name: "penny",
  },
  {
    type: "dog",
    name: "Fido",
  },
  {
    type: "fish",
    name: "Nemo",
  },
];

// Valid
const pet: Pet = {
  type: "cat",
  name: "fido",
};

// Invalid
const invalidPet: Pet = {
    age: 'dog',
    name: 'Fido'
};

Again note that Keys are what define what you can take as keys, whereas the values are not restricted and they can be anything.

If you want to restrict values too (not a valid case because it doesn't make any sense), you can create an enum for that as well.

  • Related