2

I just moved from javascript to typescript and I am on the learning phase. I have two union types and from them I want to make one type.

I tried everything, Exclude and Extract Type also but no luck.

Below is my code with expected result and actual result.

type Union1 = 'key1' | 'key2';

type Union2 = 'value1' | 'value2';

type MergeObj<K extends string | number, V> = {
  [T in V as K]: T;
};

type Result = MergeObj<Union1, Union2>;

// expected: { key1: 'value1', key2: 'value2' }
// actual: { key1: 'value1' | 'value2', key2: 'value1' | 'value2' }

Typescript: https://www.typescriptlang.org/play?#code/C4TwDgpgBAqgdgSwPZwIxQLxQOQGsIirZQA+O+IATNgNwBQdoksiKlmOAbgIYA2ArhCKkufQdXqNw0ALIQATgHMIAeQBGAKwA8AaSgQAHsAhwAJgGco54PIRxFIuPwC2ahQBooANQB8HAN50UFAA2gAqUHbeUNyWOgC6AFxQYfQAvpJM0ABKEOb8vMAcckqqmlrwyGielWw+kgD0DfoGkADGxqbJ-lAUqMnYPAJC2J4UlAND4sRpdE0xHfx83b0E-aLDwmSDYhDUYwQTG4Jbx3szQA

Edit 1

In real case, I have two union with the mixed type as below

type Union1 = 'key1' | 'key2' | 'key3' | 4 | 'key5';

type Union2 = 'value1' | 'value2' | 3 | 'value4' | 'value5';

so the union can have string or number and the union will have same amount of value always. ie. currently in example, I have 5.

so I want to convert from those 2 union type to one type as below.

type Result = {
  key1: 'value1';
  key2: 'value2';
  key3: 3;
  4: 'value4';
  key5: 'value5';
}
  • 1
    will there be any actual relation between the name of the key and the value in your real use case? For example, both ends with the number or something like that? – wonderflame Jul 01 '23 at 14:41
  • so in real use case, I have union of mixed type ( number, string ) with same number means both have same number of value in union. – Stackoverflow User Jul 01 '23 at 14:47
  • I meant some common prefixes or suffixes in the keys and values. Otherwise, as @jcalz mentioned, we can't rely on the order – wonderflame Jul 01 '23 at 14:48
  • I want something like that, the key and value of the object respect the index of union. so ie. key1 and value1 needs to be together always and so on.... – Stackoverflow User Jul 01 '23 at 14:50
  • I'm afraid If there is no obvious relation in the values themselves (not order) there is nothing we can do. – wonderflame Jul 01 '23 at 14:52
  • I edited my question, please look it and is it possible so please let me know the answer – Stackoverflow User Jul 01 '23 at 15:01
  • It seems that you don't understand. We ask whether there is some identifier that would let us link the value with the key. For example, key - `somename_first` and the value - `somevalue_first` – wonderflame Jul 01 '23 at 15:06
  • Sorry, I didn't understand your question till now but I think you are talking about some prefix or suffix in the union values but there is no suffix or prefix in the union value so no identifier is there on the values. Order is also not matters for me in this case but the value of key1 needs to be value1, if it comes on the random position, I dont care. I hope you can understand me. – Stackoverflow User Jul 01 '23 at 15:12
  • `Order is also not matters` where did this come from? Sorry, but I'll disengage here. Good luck! – wonderflame Jul 01 '23 at 15:14
  • Hey @jcalz my input needs to be union and I searched over the internet and there is one way to convert it from union to tuple but they are saying it is bad practice so I don't know what should I do!! – Stackoverflow User Jul 01 '23 at 15:14
  • As per the conversation, I think this is not possible, so I am happy if @jcalz or any can answer with why so in future, if someone will face this type of problem then they can see this and don't waste their time. I am happy if my dumbness can help someone, LoL. – Stackoverflow User Jul 01 '23 at 15:19
  • Here by order means, if you are defining an object ie. {one: 'one', two: 'two', three: 'three'}, if it is changed to {one: 'one', three: 'three', two: 'two'} then I don't care but the key and value needs to be same. I don't know why I can not explain my problem clearly but I tried so much to explain everyone in question with an example. This is just random example I am giving but in actual scenario the union is unique combination of random string and number so there is no prefix and suffix that is repeating in every key/value. – Stackoverflow User Jul 01 '23 at 15:46

1 Answers1

1

This is essentially impossible, for the same reason it's impossible to turn union types into tuple types (see How to transform union type to tuple type): union types in TypeScript are unordered (or, more accurately, their order is an implementation detail and cannot be relied upon). The type type U = 1 | 2 is completely equivalent to type U = 2 | 1 and the compiler is free to change one to the other. So correlating two unions by "index" or "position" or "order" is impossible, since you can't tell the difference between type Keys = "key1" | "key2" and type Keys = "key2" | "key1".

If you had tuples of keys and values, you could iterate through them to get the object you want. Indeed we could go through the motions of teasing apart the compiler-determined order of a union in order to implement your MergeObj type, something like this:

// get the "Last" element in a union
type Last<T> =
  ((T extends any ? ((x: (() => T)) => void) : never) extends
    ((x: infer I) => void) ? I : never) extends () => infer U ? U : never

// convert a union to a tuple
type UnionToTuple<T, A extends any[] = []> =
  [T] extends [never] ? A : UnionToTuple<Exclude<T, Last<T>>, [Last<T>, ...A]>

// merge key/value unions by first turning them to tuples
type MergeObj<K extends PropertyKey, V> =
  [UnionToTuple<K>, UnionToTuple<V>] extends [infer KK extends PropertyKey[], infer VV extends any[]] ?
  { [P in keyof KK & keyof VV & `${number}` as KK[P]]: VV[P] } : never

I'm not going to detail how that works, because it's beside the point. Let's try it out:

// type Nums = 1 | 2 | 3 | 4 | 5; //     

type Union1 = 'key1' | 'key2' | 'key3' | 4 | 'key5';
type Union2 = 'value1' | 'value2' | 3 | 'value4' | 'value5';

type O = MergeObj<Union1, Union2>
/* type O = {
    key1: "value1";
    key2: "value2";
    key3: 3;
    4: "value4";
    key5: "value5";
} */

Hey, that works exactly as you want, right? So what's the problem? Well, if you look at the commented-out line // type Nums = 1 | 2 | 3 | 4 | 5; // If we uncomment that line back in, something happens:

type Nums = 1 | 2 | 3 | 4 | 5; //     

type Union1 = 'key1' | 'key2' | 'key3' | 4 | 'key5';
type Union2 = 'value1' | 'value2' | 3 | 'value4' | 'value5';

type O = MergeObj<Union1, Union2>
/* type O = {
    4: 3;
    key1: "value1";
    key2: "value2";
    key3: "value4";
    key5: "value5";
} */

The resulting object type has changed, due only to a change in seemingly unrelated code. The compiler feels free to represent unions however it likes and in whatever order it likes. It turns out that by making explicit reference to 1 | 2 | 3 | 4 | 5 earlier in the code than before Union1 and Union2 were defined, the compiler will tend to list these first in any future unions it encounters:

// type Union1 = 4 | "key1" | "key2" | "key3" | "key5"
// type Union2 = 3 | "value1" | "value2" | "value4" | "value5"

And that ruins everything. There's not much point in continuing here.


To recap: there is simply no way to define Union1 and Union2 as unions and then reliably correlate them based on "index", "position", or "order". You should find a completely different approach, such as recording the desired order in an ordered data structure like a tuple; or start with the desired object and derive the unions from them; or give up entirely, depending on your use case.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360