Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript generics: Howto map array entries to object keys

Tags:

TLDR: In my generic function, i want to have myFunction(['width', 'left']) to return the type {width: string, left: string}.

Long version:

I have an Typescript function which has an string array as an input and returns an object with keys of the array as a value:

export interface Dictionary<T> {
  [index: string]: T | undefined;
}
var getStyle = function (  
  element: Element,
  propertyNames: readonly string[]
) { 
  let gCS= window.getComputedStyle(element)
  let result: Dictionary<string> = {};
  propertyNames.forEach((prop)=>{
    result[prop]=gCS.getPropertyValue(prop);
  });
  return result;
};    

The typescript return value is a Object/Dictionary, but without specific properties.

var resultObj = getStyle(document.body, ['width']);
resultObj.width; // should be ok
resultObj.height; // should be not ok

I tried many things. The best thing was that one:

export type RestrictedDictionary1<T, P extends readonly string[]> = {
    [index in keyof P]?: T | undefined
}
declare function getStyle1<P extends readonly string[]>(  
    element: Element,
    propertyNames: P
): RestrictedDictionary1<string, P>;

var resultObj1 = getStyle1(document.body, ['width']);  // Huh? Why an array
resultObj1.width; // should be ok, but both are unvalid for TS
resultObj1.height; // should be not ok, but both are unvalid for TS

Typescript now got an array from that. I have no idea why.

The last try is again an interface, but the [index in P] part does not work

export interface RestrictedDictionary2<T, P extends string[]> {
    [index in P]: T | undefined;  // A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.
}
declare function getStyle2<P extends string[]>(  
    element: Element,
    propertyNames: P
): RestrictedDictionary2<string, P>;

var resultObj2 = getStyle2(document.body, ['width']);
resultObj2.width; // should be ok
resultObj2.height; // should be not ok
like image 465
HolgerJeromin Avatar asked Aug 15 '19 13:08

HolgerJeromin


1 Answers

You are pretty close in your thinking the syntax is a bit off though. If P is string[] then the keyof P are just the members of array, not the values. You could use P[number] to get at the types of the values in the array. Also interfaces can't contain mapped types (the [index in P]: ... syntax), only type aliases can (type definitions). Also there is a predefined mapped type called Record that is exactly what you need no need to define a new one


var getStyle = function<T extends string> (  
  element: Element,
  propertyNames: readonly T[]
): Record<T, string> { 
  let gCS= window.getComputedStyle(element)
  let result = {} as Record<T, string>;
  propertyNames.forEach((prop)=>{
    result[prop]=gCS.getPropertyValue(prop);
  });
  return result;
};   
var resultObj = getStyle(document.body, ['width']);
resultObj.width; // should be ok
resultObj.height; // err

Play

like image 163
Titian Cernicova-Dragomir Avatar answered Oct 12 '22 23:10

Titian Cernicova-Dragomir