3

Here's what I have, I have various types, and I have a string representation for that type:

type PossibleValueTypes = Color | number | Position;
type PossibleValueTypesAsString = "color" | "number" | "position";

type ValueTypeMap = {
  "color" : Color; 
  "number" : number; 
  "position" : Position; 
}; 

Now, give a given string representation, returning the the value type is easy enough, for example;


function generateRandomValueOfType<T extends PossibleValueTypesAsString>(type: T) : ValueTypeMap[T] {
    if (type === 'color') {
        return {
            r: 1, 
            g: 1, 
            b: 1, 
            a: 1, 
        } as ValueTypeMap[T]
    }
    if (type === "position") {
        return {
            x:1, 
            y: 1
        } as ValueTypeMap[T]
    }

    if (type === "number") {
        return 1 as ValueTypeMap[T]; 
    }

    throw new Error ("We shouldn't have got here"); 
}; 


const a: Position = generateRandomValueOfType("position"); 
const b: number = generateRandomValueOfType("position"); // Expected error!

Now the question is - what if I want to do the opposite, that is - given a value type, determine the string representation.

The best I've got is to do a chained conditional like:

type ReverseValueTypeLookup<T extends PossibleValueTypes>  = T extends Color ? "color" : T extends number ? "number" : T extends Position ? "position": never; 

function hasAValueAcceptingCallback<T extends PossibleValueTypes> (callback: (value: T) => void, valueTypeAsString: ReverseValueTypeLookup<T>)  {
    const value = generateRandomValueOfType(valueTypeAsString); 

  //Argument of type 'ValueTypeMap[ReverseValueTypeLookup<T>]' is not assignable to parameter of type 'T'.
  //'ValueTypeMap[ReverseValueTypeLookup<T>]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'PossibleValueTypes'.
    callback(value);
}






But this doesn't actually work in the example I've given.

Is there a tidier way to achieve both of the thing I'm trying to do here?

TS Playground

dwjohnston
  • 11,163
  • 32
  • 99
  • 194
  • If I translate the [answer](https://stackoverflow.com/a/54520829/2887218) to the other question here I get [this](https://tsplay.dev/wXkyLW), but note that in both this and your accepted answer you can't call `callback(value)` without a type assertion; the compiler just isn't clever enough to see that this will be safe. But your question as asked seems to be "make a reverse type look up" and not "convince the compiler that `Fwd>` is equivalent to `T` when `T` is some unspecified generic", so I'm not going to worry about that. – jcalz Aug 25 '21 at 13:18

1 Answers1

2

A quite verbose but working solution is...

type ReverseValueTypeLookup<T extends ValueTypeMap[keyof ValueTypeMap]> = {
  [x in keyof ValueTypeMap]: ValueTypeMap[x] extends T ? x : never;
}[keyof ValueTypeMap];
// exclude key x when ValueTypeMap[x] is not assignable to T, and then get the types of values

type C = ReverseValueTypeLookup<Color>; // type 'color'
type N = ReverseValueTypeLookup<number>; // type 'number'

If you still can't distinguish the types among Color | number | Position, use type narrowing like...

var data: Color | number | Position;
if (typeof data === 'number') {
  // data is number
} else if ('r' in data) {
  // data is Color
} else if ('x' in data) {
  // data is Position
}
T.D. Stoneheart
  • 811
  • 4
  • 7