0

I want to convert an array of objects, into a set of known properties of a single object. I have working TypeScript code, but I want to make it into a generic function. Below is my working code.

global definition...

export type Pic = SanityDocument & {
    id: Slug,
    caption: string,
    group: string,
    pic: Image,
}

Example code I want to make generic:

const pics : Pic[]  = await sanityClient.fetch(`
        *[_type == "pic"]
    `);

// Example type I want to make generic...
type PicMap = {alpha: Pic, bravo: Pic};

// How do I make `PicMap` be a generic I specify
// as part of a function call?
const picMap : PicMap = pics.reduce((acc, pic : Pic) => {
  acc[pic.id.current as keyof PicMap] = pic;
  return acc;
}, {} as PicMap);

The above code works. I want to modify the above code so that type PicMap becomes generic, so I can re-use this code for other target objects. How is that done? There are three scenarios of what I'm looking for:

  1. A TypeScript function that allows me to generically replace PicMap with something else. However, the Pic type is fixed/required. This is my minimum requirement.
  2. As before, but the Pic type is also generic.
  3. A variant of either option #1 or #2, where the PicMap type can be declared inline rather than requiring an explicit declaration.

Option #1 is what I absolutely need to fulfill this question. Options #2 and/or #3 are icing on the cake.

I am intentionally not using Record<t,k> for two reasons. First, I want the picMap object properties accessible with auto-complete. Second, for me it is ok if the source array has objects that don't exist as exposed properties of the final object. In other words, I am not attempting to limit what properties can be assigned...I am only limiting which properties I can "see" with intellisense. So I don't want a solution like this.

UPDATE

My own attempt at a solution currently looks like this:

New global definition...

type NamedAsset = SanityDocument & {
    id: Slug // has a string property called 'current'
}

And then my generic function...

export function MapAsset<
   V extends object, 
   K extends NamedAsset, 
   T extends K[]> (items:T) : V {
    const acc = items.reduce((acc, asset:K) => {
        acc[asset.id.current as keyof V] = asset;
        return acc;
    }, {} as V);
    return acc;
}

Unfortunately the above has a "Type 'K' is not assignable" error. However, if the above were working then my example usage might look like this:

// Sample type for array contents...
export type Pic = NamedAsset & {
    caption: string,
    group: string,
    pic: Image,
}

// Just some arbitrary way to fill the array.
const pics : Pic[]  = await sanityClient.fetch(`
        *[_type == "pic"]
    `);

// An arbitrary type of arbitrary declared properties.
type PicMap = {alpha: Pic, bravo: Pic};

const picMap : PicMap = MapAsset(pics);
// Or, alternatively...
const picMap = MapAsset<PicMap>(pics);

UPDATE #2

I asked ChatGPT to solve the error I found in my prior example solution. What it gave back does work, but it is brain-melting. It's solution:

export function MapAsset<T extends NamedAsset[]>(items:T):
{
    [K in T[number]['id']['current']]: Extract<T[number], {id : {current:K}}>
}
{
    const acc = items.reduce((acc, asset) => {
        acc[asset.id.current] = asset;
        return acc;
    }, {} as any);
    return acc;
}

Then to use the above, I do this:

type PicMap = {alpha: Pic, bravo: Pic};
const picMap = MapAsset(pics) as PicMap;

Having a function whose result I must cast is not what I had in mind. However, I admit this would be acceptable-ish in the absence of a better answer. Aside from the need to cast the result, I also don't like that the MapAsset function is surprisingly esoteric.

3
  • @jonrsharpe I made an edit simultaneously with you, which caused yours to be overwritten. Accident. However, mostly you just removed the typescript-generics tag. Why? Commented Nov 12, 2023 at 18:50
  • What's SanityDocument and sanityClient? Could you consolidate the code into a single minimal reproducible example without reference to private or third-party code? The goal is that we could copy and paste your plaintext into our own IDEs and see the issue for ourselves. Commented Nov 13, 2023 at 2:56
  • Because it's usless. "Tags are a means of connecting experts with questions they will be able to answer" - generics are part of TypeScript, not a separate area of expertise. Also the "update" titles are not helpful - write the question for the next viewer, if people want to see how the question developed they can read the revision history. Commented Nov 13, 2023 at 10:55

0

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.