Tomáš Burda
Tomáš Burda

Reputation: 15

Typescript Interface objects property value as key

i try to create correct typings for situations, when trying to map the array of objects to array with the same objects but using the property value as index key..

Code on playground

interface ValueDefinition {
    name: string;
}

function getByName<V extends ValueDefinition>(valuDefinitions: V[]) {
    let out  = {}

    for (const key in valuDefinitions) {
        out[valuDefinitions[key].name] = valuDefinitions[key];
    }
    return out;
}

const obj: ValueDefinition = {
    name: 'super'
};


const objKey = getByName([
    obj
]);

key.super; // Typings for this output

I looking for something like this:

type WithKey<D extends ValueDefinition> = {
    [key: D['name']]: D;
}

Thanks.

Upvotes: 1

Views: 4088

Answers (1)

jcalz
jcalz

Reputation: 330086

You should make your ValueDefinition interface generic in the string literal type of the name property, so you can extract it later:

interface ValueDefinition<K extends string = string> {
    name: K
}

Then you can represent the output of your getByName() function as the following mapped type:

type ValueDefinitionObject<K extends string> = {[P in K]: ValueDefinition<P>}

And revise the signature of getByName to be generic in the set of name literals:

function getByName<K extends string>(valuDefinitions: Array<ValueDefinition<K>>): ValueDefinitionObject<K> {
    let out = {} as ValueDefinitionObject<K>
    for (const key in valuDefinitions) {
        out[valuDefinitions[key].name] = valuDefinitions[key];
    }
    return out;
}

At this point it will work, but you have to be careful to declare your obj so that the type of the value 'super' is inferred as 'super' and not string. This identity function will help:

function asValueDefinition<K extends string>(vd: ValueDefinition<K>): ValueDefinition<K> {
    return vd;
}

Okay, let's try it (with another one I'm adding):

const obj = asValueDefinition({ name: 'super' });
const obj2 = asValueDefinition({ name: 'thing' });

If you inspect them they show up as ValueDefinition<'super'> and ValueDefinition<'thing'>.

const objKey = getByName([obj, obj2]);  

And objKey is typed as ValueDefinitionObject<'super'|'thing'>. Let's use it:

objKey.super; // okay, ValueDefinition<'super'>
objKey.thing; // okay, ValueDefinition<'thing'>
objKey.nope; // error, property 'nope' doesn't exist

Does that work for you? Good luck!

Upvotes: 4

Related Questions