Ilja
Ilja

Reputation: 46479

Only allow subkeys of object as a function argument

Assume we have an object like this:

const MyObject = {
  gloves: {
    gloves1: ...,
    gloves2: ...
  },
  boots: {
    boots1: ...,
    boots2: ...

  }
}

where names like gloves1 are variable. We then create a helper function to get a specific value from MyObject i.e.

function getValue(category: keyof typeof MyObject, name: string) {
 return MyObject[category][name];
}

is there any way to type name dynamically based on what catefory was passed as argument? So if I pass gloves as category name should only be gloves1 or gloves2

Upvotes: 0

Views: 52

Answers (2)

jcalz
jcalz

Reputation: 327644

Since the example object is invalid I will make up my own, but feel free to replace it:

const MyObject = {
  a: { b: "", c: 1, d: true },
  e: { f: false, g: 2, h: "x" }
};

type MyObject = typeof MyObject; // for convenience

The right way to do this is to make getValue() a generic function. Assuming some of the nested properties are of different types, you'd also like getValue() to return the type corresponding to the particular subproperty. If so, then you want two generic parameters: one corresponding to the category, and one corresponding to the name. Here's how you'd type it:

function getValue<K extends keyof MyObject, K2 extends keyof MyObject[K]>(
  category: K, name: K2) {
  return MyObject[category][name];
}

This compiles now without error. K is a key of MyObject. And K2 is constrained to a key of MyObject[K], a lookup type corresponding to the property of MyObject at the K index.

Let's see if it works:

const good1 = getValue("a", "c"); // number
const good2 = getValue("e", "h"); // string
const bad1 = getValue("a", "h"); // error!
// ----------------------> ~~~
// "h" is not assignable to "b" | "c" | "d"

Looks good. Note how the type of good1 and good2 are different, and correspond to the particular types of MyObject.a.c and MyObject.e.h respectively. And you get the desired error with bad1 when you use an invalid subproperty key.

Okay, hope that helps!

Playground link to code

Upvotes: 1

Roberto Zvjerković
Roberto Zvjerković

Reputation: 10127

Your example object is broken:

const MyObject = {
    gloves: {
        gloves1: 1,
        gloves2: 2
    },
    boots: {
        boots1: 3,
        boots2: 4
    }
}

With generics:

function getValue<Category extends keyof typeof MyObject>(category: Category, name: keyof typeof MyObject[Category]) {
    return MyObject[category][name];
}
getValue("boots", "gloves1"); // Argument of type '"gloves1"' is not assignable to parameter of type '"boots1" | "boots2"'.

Upvotes: 0

Related Questions