1

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?

2 Answers 2

4

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

Sign up to request clarification or add additional context in comments.

1 Comment

Wow thank you! That first code fragment is a little hard to follow (not your fault!) but once I realized that you then use that type like type Birds = GetKeysOfType<Bird, typeof zoo> I see that it's the perfect solution! I'm going to edit your answer to make that clear.
0

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'.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.