Harshal Patil
Harshal Patil

Reputation: 21030

How to write a function in TypeScript that can query deep object and return the type of the nested key when called?

I have the following predefined object:

export const catalog = {
  category1: {
    newDomain: 'Hello'
  },
  category2: {
    otherStuff: 30
  }
};

Now I need to achieve two things:

It means I need a function validate such that:

const value1: string = validate('category1', 'newDomain');
const value2: number = validate('category2', 'otherStuff');

Any other combination for validate function should fail.

Upvotes: 0

Views: 189

Answers (1)

jsejcksn
jsejcksn

Reputation: 33931

Edit: I forgot to answer the first part (getting the union of nested keys):

TS Playground

type Values<T> = T[keyof T];

type NestedKey<T extends Record<PropertyKey, Record<PropertyKey, unknown>>> =
  Values<T> extends infer T1 ? (T1 extends T1 ? keyof T1 : never) : never;

const catalog = {
  category1: { newDomain: 'Hello' },
  category2: { otherStuff: 30 },
};

type NestedCatalogKey = NestedKey<typeof catalog>; // "newDomain" | "otherStuff"


First, write a function to do it for any object that has object values, then curry it:

TS Playground

function validate <
  R,
  K0 extends PropertyKey,
  K1 extends PropertyKey,
  T extends Record<K0, Record<K1, R>>,
>(l0Key: K0, l1Key: K1, obj: T): R {
  return obj[l0Key][l1Key];
}

const catalog = {
  category1: { newDomain: 'Hello' },
  category2: { otherStuff: 30 },
};

function validateCatalog <
  K0 extends keyof typeof catalog,
  K1 extends keyof typeof catalog[K0],
>(l0Key: K0, l1Key: K1): typeof catalog[K0][K1] {
  return validate(l0Key, l1Key, catalog);
}

const value1 = validateCatalog('category1', 'newDomain'); // string
const value2 = validateCatalog('category2', 'otherStuff'); // number

const value3 = validateCatalog('category1', 'otherStuff'); /*
                                            ~~~~~~~~~~~~
Argument of type '"otherStuff"' is not assignable to parameter of type '"newDomain"'.(2345) */

const value4 = validateCatalog('category2', 'newDomain'); /*
                                            ~~~~~~~~~~~
Argument of type '"newDomain"' is not assignable to parameter of type '"otherStuff"'.(2345) */

console.log({ value1, value2 }); // { value1: "Hello", value2: 30 }

Upvotes: 1

Related Questions