Home > Software engineering >  Convert list of properties to property tuple
Convert list of properties to property tuple

Time:08-11

This question is similar to this one, but I think it lacks a lot of the main caveats and concerns raising issues there because I'm not making any assumptions that the properties of a given interface are a closed or ordered set.

Suppose I have an interface and couple of functions like this:

interface Person {
    givenNames: string,
    familyNames: string,
    charactersToDisplayNames: number,
    age: number,
    weight: number,
    fastest5KTime: number,
    //... could be plenty of others
}
function writeFields<
    T extends object,
    F extends (keyof T)[]
>(
    dest : T, 
    fieldsToWrite : F,
    valuesToWrite : WhatTypeGoesHere<T, F>, //Can be defined above
) {/*...*/}
function setNames(dest: Person, givenNames: string, familyNames: string) {
    writeFields(
        dest, 
        ['givenNames', 'familyNames', 'charactersToDisplayNames'],
        //In this case, the 3rd param should be
        //a tuple of type [string, string, number]
        [givenNames, familyNames, givenNames.length   1   familyNames.length]
    )
}
//other functions set other fields, or do so with parameters, etc...
//the desire is to avoid any non-type refactoring esp. to writeFields(). 

How should the type noted above as WhatTypeGoesHere be defined so that the third parameter has proper type-checking?

CodePudding user response:

You can give the valuesToWrite property a mapped tuple type where you take every element type in the fieldsToWrite type (I'm changing this from F to K) and index into T with it.

Like this:

function writeFields<
  T extends object,
  K extends readonly (keyof T)[]
>(
  dest: T,
  fieldsToWrite: readonly [...K],
  valuesToWrite: { [I in keyof K]: T[K[I]] },
) {/*...*/ }

I'm giving fieldsToWrite a variadic tuple type of the form [...K] instead of just K to give the compiler a hint that it should infer a tuple type and not an unordered array type from the fieldsToWrite argument.

Also, it's not super important whether or not the tuple types are mutable or readonly tuple types but readonly ones are less restrictive (e.g., string[] is assignable to readonly string[] but not vice versa) so I used readonly.

Let's test it out:

writeFields(
  dest,
  ['givenNames', 'familyNames', 'charactersToDisplayNames'],
  [givenNames, familyNames, givenNames.length   1   familyNames.length]
) // okay
// T is Person, K is ["givenNames", "familyNames", "charactersToDisplayNames"]

writeFields(
  dest,
  ['age', 'familyNames'],
  ['oops', 'okay'] // error!
  //~~~~~ <-- Type 'string' is not assignable to type 'number'.(2322)
);

Looks good. The compiler knows which values belong at which index when you call writeFields() and will complain if you pass in something bad.

Playground link to code

  • Related