1

I need the same data served in these two types

type DataMap = { 
  id001: 'name1', 
  id002: 'name2', 
  id003: 'name3', 
  ....
} 

type DataTuple = [
  {id: 'id001', name: 'name1'},
  {id: 'id002', name: 'name2'},
  {id: 'id003', name: 'name3'}, 
  ...
]

I would like to only declare this type once and have a single source of truth, But I cannot figure out a utility type function that translates the type from an object to a tuple.

ideally I would like to do something like this:

type DataMap = { 
  id001: 'name1', 
  id002: 'name2', 
  id003: 'name3', 
  ....
}; 
type DataTuple = MapToTuple<DataMap>;

// or 

type DataTuple = [
  {id: 'id001', name: 'name1'},
  {id: 'id002', name: 'name2'},
  {id: 'id003', name: 'name3'}, 
  ...
];
type DataMap = TupleToMap<DataTuple>;

Are either of these functions MapToTuple<T> or TupleToMap<T> possible?

lonewarrior556
  • 3,917
  • 2
  • 26
  • 55

2 Answers2

1

For TypeScript 4.1+ you can implement TupleToMap like this, assuming you know that the id property should be the key and the name property should be the value:

type TupleToMap<T extends { id: PropertyKey, name: any }[]> = {
    [V in T[number]as V['id']]: V['name']
};

type DataMap = TupleToMap<DataTuple>;
/* type DataMap = {
id001: "name1";
id002: "name2";
id003: "name3";
} */

The above uses key remapping which was introduced in TS 4.1. For earlier versions you could write it this way instead:

type TupleToMap<T extends { id: PropertyKey, name: any }[]> = {
    [K in T[number]['id']]: Extract<T[number], { id: K }>['name']
};

For MapToTuple it's not clear where you intend to get the ordering of the tuple from. It might be obvious to a human being that the entry with id001 should come before the entry with id002, but for the compiler I'm not sure if it's worth trying to get that across. I will circle back if I come up with something not crazy.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • You put my attempt to shame in 20 mins, I spent a day on it. Thank you! For the order.. Order in js objects is now guaranteed so an array out of an object would be deterministic. Is this not the case in interfaces/types? – lonewarrior556 Feb 11 '21 at 03:07
  • 1
    No, there is no consistently observable order for keys of an object type in TypeScript. See [this question](https://stackoverflow.com/questions/55127004/how-to-transform-union-type-to-tuple-type) for a way to kind of do it that shows how bad it is. – jcalz Feb 11 '21 at 03:25
  • 1
    Like, you could do [this](https://tsplay.dev/gWojMm), but there's no guarantee that the compiler sees the keys in the same order you do. If you could handle an unordered array (like `DataArray` in the code linked here) that would be something well within the compiler's abilities. Otherwise, ‍♂️ – jcalz Feb 11 '21 at 03:31
0

I have a solution for TupleToMap<T> Although it is pretty long


type DataTuple = [
  { id: 'id001'; name: 'name1' },
  { id: 'id002'; name: 'name2' },
  { id: 'id003'; name: 'name3' }
];

type TupleKeys<T> = Exclude<keyof T, keyof any[]>;
 
type TupleToObject<T extends Record<string, any>> = {
  [k in TupleKeys<T>]: { [P in T[k]['id']]: T[k]['name'] };
}[TupleKeys<T>];

type MergeUnions<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any
  ? { [K in keyof R]: R[K] }
  : never;

type TupleToMap<T> = MergeUnions<TupleToObject<T>>;
type DataMap = TupleToMap<DataTuple>

ts playground link

lonewarrior556
  • 3,917
  • 2
  • 26
  • 55