1

Given a type that is an array of objects, how do I create a new type that is an object, where the keys are values of a specific property in the array objects, and the values of the object are the type of one of the object generics.

With JS code, it would look like this:

const obj = arr.reduce((acc, element) => ({
  ...acc,
  [element.name]: element.bar
}))

So far I have this:

type ArrayToObject<Arr extends Array<CustomType<any>> = []> = {
  [K in keyof Arr]: ExtractCustomTypeGeneric<Arr[K]>
}

type ExtractCustomTypeGeneric<A> = A extends CustomType<infer T> ? T : never;

type CustomType<T> = {
  name: string,
  foo: number,
  bar: T
}

This gives me an array object (since K in keyof Arr are index numbers), whereas I would want the property keys to be name. Unfortunately something like [Arr[K]['name'] for K in keyof Arr] isn't something that works.

1 Answer 1

3

You can get a union of all the keys in the array using Arr[number]['name'], then you can use Extract to extract from the union of all possible elements in the array (Arr[number]) just the items that have the currently mapped name:

type ArrayToObject<Arr extends Array<CustomType<any>> = []> = {
  [K in Arr[number]['name']]: ExtractCustomTypeGeneric<Extract<Arr[number], { name: K }>>
}

type ExtractCustomTypeGeneric<A> = A extends CustomType<infer T> ? T : never;

type CustomType<T, K = string> = {
  name: K,
  bar: T
}

function createItems<T extends Array<CustomType<any, K>>, K extends string>(...a: T) : T{
    return a;
}

let a = createItems({ name: "A", bar: ""}, { name: "B", bar: 0})

type R = ArrayToObject<typeof a>
//Same as 
type R = {
    A: string;
    B: number;
}

Play

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

3 Comments

Wow. :-) Suppose I wanted to do this without a function? E.g., my starting point was const fields = [{ name: "A", bar: ""}, { name: "B", bar: 0}]; and I wanted to get the type { A: string; B: number; }? I tried but failed to make it work without the function, embarrassingly. :-) (Context: To use Amazon's RDS efficiently I need to have a fields-like object so I know what property name to use to get the value of a column. So I'd prefer not to have to maintain both runtime and compile-time type information for my business objects...)
@T.J.Crowder as const on the constant initalizer will almost get you there, but that will preserve the actual literal type instead of widening to string or number ({ A: ""; B: 0;}). Functions are still the best way to have fine grained control over inference.
Thanks! Yeah, that was indeed the stumbling block for me, that I ended up with "" and 0 rather than string and number. That's too bad. I wanted to avoid having to pass both a type and a schema object to the function. But I'm stuck with either let users: User[] = executeQuery("select ...", UserSchema); or let users = executeQuery<User>("select ...", UserSchema);. I guess since the latter doesn't require the type gymnastics above, I'll use it. So close! :-)

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.