#Extracting multiple types while iterating via `in`

25 messages · Page 1 of 1 (latest)

simple bridge
#

I'm trying to define a type that yields the type of an object whose keys are provided by a string array and whose types are the types of these keys in provided object type O. However, if a key in the string array is given in the form "column as alias", the key in the yielded object will be the alias, while the type for this key will be the type of the key with name "column" in O.

Here's what I have, but it doesn't compile:

type AliasedObject<O, KA extends string[]> = {
  [K in KA[number] extends `${infer C} as ${infer A}`
    ? A
    : K]: C extends keyof O ? O[C] : never;
};

I realize there are a ton of errors, but this code does convey what I'm trying to do. How do I fix it?

UPDATE: It seems like the following should do the trick, but TS isn't recognizing any keys for objects of type AliasedObject:

type SelectedColumnsObject<O, KA extends string[]> = {
  [K in KA[number]]: K extends `${infer C} as ${infer A}`
    ? C extends keyof O
      ? [A, O[C]]
      : never
    : K extends keyof O
    ? [K, O[K]]
    : never;
};

type AliasedObject<O, KA extends string[]> = {
  [K in KA[number] as SelectedColumnsObject<
    O,
    KA
  >[K][0]]: SelectedColumnsObject<O, KA>[K][1];
};
sharp shoal
#

I think the issue is that the type of SelectedColumnsObject is an array of tuples, so [K][1] cannot dynamically get the type, instead make that dynamic:

type AliasedObject<O, KA extends string[]> = {
  [K in KA[number] as SelectedColumnsObject<
    O,
    KA
  >[K][0]]: SelectedColumnsObject<O, KA>[K] extends [infer A, infer T] ? T : never;
};

this checks if its a tuple, then return the second type (T)

simple bridge
#

That seems to help for extracting the type, but what about extracting the key? TS doesn't like the stuff that follows as in this code:

type AliasedObject<O, KA extends string[]> = {
  [K in KA[number] as SelectedColumnsObject<O, KA>[K] extends [infer C, any]
    ? C
    : never]: SelectedColumnsObject<O, KA>[K] extends [string, infer T]
    ? T
    : never;
};
#

The following compiles, but it allows objects of type AliasedObject to have any keys, all of type unknown:

type AliasedObject<O, KA extends string[]> = {
  [K in KA[number] as SelectedColumnsObject<O, KA>[K] extends [infer C, any]
    ? C & string
    : never]: SelectedColumnsObject<O, KA>[K] extends [string, infer T]
    ? T
    : never;
};
#

I vaguely recall having seen a notation that allows the key portion of a mapped object type to define multiple type parameters, but I cannot find this notation.

sharp shoal
#

not sure, that as seams a bit strange for me though, I think you could do it without 🤔

#

do you maybe mean template literal types?
for example

type Key<T extends string> = `prefix-${T}`;
#

you already use them though

#

anyway try this:

type SelectedColumnsObject<O, KA extends string[]> = {
  [K in KA[number]]: K extends `${infer C} as ${infer A}`
    ? C extends keyof O
      ? [A, O[C]]
      : never
    : K extends keyof O
    ? [K, O[K]]
    : never;
};

type AliasedObject<O, KA extends string[]> = {
  [K in SelectedColumnsObject<O, KA>[number] extends [infer C, any]
    ? C extends string
      ? C
      : never
    : never]: SelectedColumnsObject<O, KA>[number] extends [infer C, infer T]
    ? T
    : never;
};

I added another conditional type to check if the index O of the tuple returned bt SelectedColumnsObject is a string or not:

[K in SelectedColumnsObject<O, KA>[number] extends [infer C, any] ? C extends string ? C : never : never]
#

this example works nicely for me now:

// example
type User = {
  id: number;
  name: string;
  age: number;
  email: string;
};

type SelectedUser = AliasedObject<User, ["id as userId", "name", "age"]>;

const user: SelectedUser = {
  userId: 1,
  name: "John",
  age: 20,
};
#

oh no it doesnt 🤦‍♂️

simple bridge
#

I had to do this to get it to compile: SelectedColumnsObject<O, KA>[string]. But now it reports that no key exists on the object.

#

I found code in Kysely that does something similar, and it takes this approach:

export type Selection<DB, TB extends keyof DB, SE> = {
  [A in ExtractAliasFromSelectExpression<SE>]: SelectType<
    ExtractTypeFromSelectExpression<DB, TB, SE, A>
  >
}
sharp shoal
#

OK I think I got it now:

type SelectedColumnsObject<O, KA extends string[]> = {
  [K in KA[number]]: K extends `${infer C} as ${infer A}`
    ? C extends keyof O
      ? [A, O[C]]
      : never
    : K extends keyof O
    ? [K, O[K]]
    : never;
};

type AliasedObject<O, KA extends string[]> = {
  [K in keyof SelectedColumnsObject<O, KA> as SelectedColumnsObject<O, KA>[K] extends [infer C, any]
    ? C extends string
      ? C
      : never
    : never]: SelectedColumnsObject<O, KA>[K] extends [infer C, infer T]
    ? T
    : never;
};
#

basically replaced the number with keyof in K in SelectedColumnsObject<O, KA>[number]

#

I think I will save this somewhere, its pretty useful 😄

simple bridge
#

Still not working for me, but thank you for your help. There really should be a library that makes it easy to lookup patterns and recipes for making types.

sharp shoal
#

sorry, maybe I misunderstood your issue then. wait one more minute, I want to try another solution that might be more what you were looking for

#
restive lynxBOT
#

@sharp shoal Here's a shortened URL of your playground link! You can remove the full link from your message.

bmkotu#7304

Preview:```ts
type ExtractStringKeys<T> = {
[K in keyof T]: K extends string ? K : never
}[keyof T]

type ValidKeys<O> =
| ExtractStringKeys<O>
| ${ExtractStringKeys<O>} as ${string}

type AliasedObject<O, KA extends ValidKeys<O>[]> = {
[K in KA[number]]: K extends `${infer C} as ${inf
...```

simple bridge
#

Curious. Try adding this function, which doesn't allow user to have any properties:


function actOnUser(user: SelectedUser) {
  user.id;
  user.userId;
  user.age;
}
sharp shoal
#

Oh that is very strange

#

ah yes ok, it looks like TS actually outputs this:

type SelectedUser = {
    userId: number;
} | {
    name: string;
} | {
    age: number;
} | {
    x: Date;
}
#

hold on this should be simply resolved with UnionToIntersection

#

also need Merge

type ExtractStringKeys<T> = {
  [K in keyof T]: K extends string ? K : never;
}[keyof T];

type ValidKeys<O> = ExtractStringKeys<O> | `${ExtractStringKeys<O>} as ${string}`;

export type AliasedObject<O, KA extends ValidKeys<O>[]> = Merge<
  UnionToIntersection<
    {
      [K in KA[number]]: K extends `${infer C} as ${infer A}`
        ? C extends keyof O
          ? { [P in A]: O[C] }
          : never
        : K extends keyof O
        ? { [P in K]: O[K] }
        : never;
    }[KA[number]]
  >
>;

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I
) => void
  ? I
  : never;

type Merge<T> = { [K in keyof T]: T[K] };