Home > Back-end >  Type for keys in object, where value of that key satisfies constraint
Type for keys in object, where value of that key satisfies constraint

Time:11-16

I have something like this:

Obviously this is an overly minimal example so the question of why I would want to do these types specifically is irrelevant.

type Bird = { kind: "bird" }
type Fish = { kind: "fish" }

const zoo = {
  flamingo: { kind: "bird" },
  hawk: { kind: "bird" },
  chicken: { kind: "bird" },
  guppy: { kind: "fish" },
  blowfish: { kind: "fish" }
}

I want to get string array types that might be this:

type FishInZoo = "guppy" | "blowfish"
type BirdInZoo = "flamingo" | "hawk" | "chicken"

So I'm thinking something like the following, but I don't know what the syntax would be:

type FishInZoo = keyof typeof zoo where { kind: "fish" }

Is this possible?

CodePudding user response:

I believe you're looking for something like:

type GetKeysOfType<
  Type extends Record<string, any>,
  Obj extends Record<string, any>
> = keyof {
    [Key in keyof Obj as Obj[Key] extends Type ? Key : never]: Obj[Key];
}

Do note this will require as const assertion on the object see docs. This allows you to use the string literal in the object and type i.e.

type Bird = { kind: "bird" }
type Fish = { kind: "fish" }

// as opposed to
type Kind = { kind: string };

Using this will require you to specify:

const zoo = {
  flamingo: { kind: "bird" },
  hawk: { kind: "bird" },
  chicken: { kind: "bird" },
  guppy: { kind: "fish" },
  blowfish: { kind: "fish" }
} as const // this as const.

Then you use the type like this:

type BirdsInZoo = GetKeysOfType<Bird, typeof zoo>
type FishesInZoo = GetKeysOfType<Fish, typeof zoo>

You can also enforce that all objects that use GetKeysOfType have to be Readonly i.e. have to use as const to prevent people from forgetting and receiving incorrect values. but that would depend on your use case.

It uses the relatively new key remapping feature released in 4.1.

Playground link

CodePudding user response:

In addition to @zecuria's great answer above, there are a few additional caveats, related to my actual usage of this type.

type GetKeysOfType<
  Type extends Record<string, any>,
  Obj extends Record<string, any>
> = keyof {
  [Key in keyof Obj as Obj[Key] extends Type ? Key : never]: Obj[Key];
};

const initialFilters = {
  sportId: { key: "sportId", kind: "type", types: [] as String[] },
  gameType: { key: "gameType", kind: "type", types: [] as String[] },
  contestTypeId: { key: "contestTypeId", kind: "type", types: [] as String[] },
  entryFees: {
    kind: "range",
    key: "entryFees",
    max: 10000,
    min: 0,
  },
} as const;

type TypeFilterKey = GetKeysOfType<TypeFilter, typeof initialFilters>;

Of importance is the as String[] on the object values in initialFilters.

Without it, types will be of type readonly [], instead of the mutable String[], and something like

filter.types.includes(type)

will fail with

TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.

  • Related