If you're working with JavaScript, I recommend creating a function that can transform a standard dictionary into a double-sided dictionary:
function doubleDictionary(t) {
var ret = Object.assign({}, t);
for (var k in t) {
ret[t[k]] = k;
}
return ret;
}
var foo = doubleDictionary({ a: "b", c: "d" });
console.log(foo.a); // "b"
console.log(foo.b); // "a"
console.log(foo.c); // "d"
console.log(foo.d); // "c"
For TypeScript, you can use the same function but enhance it with a signature to provide a strongly-typed return value to callers. Here's how you can do that:
// ObjToKeyValue<T> turns an object type into a union of key/value tuples:
// ObjToKeyValue<{a: string, b: number}> becomes ["a", string] | ["b", number]
type ObjToKeyValue<T> =
{ [K in keyof T]: [K, T[K]] }[keyof T];
// KeyValueToObj<T> turns a union of key/value tuples into an object type:
// KeyValueToObj<["a", string] | ["b", number]> becomes {a: string, b: number}
type KeyValueToObj<KV extends [keyof any, any]> =
{ [K in KV[0]]: KV extends [K, infer V] ? V : never };
// ReverseTuple<KV> swaps the keys and values in a union of key/value tuples:
// ReverseTuple<[1, 2] | [3, 4]> becomes [2, 1] | [4, 3]
type ReverseTuple<KV extends [any, any]> =
KV extends [any, any] ? [KV[1], KV[0]] : never;
// ReverseObj<T> takes an object type whose properties are valid keys
// and returns a new object type where the keys and values are swapped:
// ReverseObj<{a: "b", c: "d"}> becomes {b: "a", d: "c"}
type ReverseObj<T extends Record<keyof T, keyof any>> =
KeyValueToObj<ReverseTuple<ObjToKeyValue<T>>>;
// take an object type T and return an object of type T & ReverseObj<T>
// meaning it acts as both a forward and reverse mapping
function doubleDictionary<
S extends keyof any, // infer literals for property values if possible
T extends Record<keyof T, S>
>(t: T) {
const ret = Object.assign({}, t) as T & ReverseObj<T>; // return type
for (let k in t) {
ret[t[k]] = k as any; // need assertion here, compiler can't verify k
}
return ret;
}
With this implementation, you can achieve the same outcome as before while having the benefits of compile-time knowledge:
const foo = doubleDictionary({ a: "b", c: "d" });
// const foo: {a: "b", c: "d"} & {b: "a", d: "c"};
console.log(foo.a); // the compiler knows it is "b"
console.log(foo.b); // the compiler knows it is "a"
console.log(foo.c); // the compiler knows it is "d"
console.log(foo.d); // the compiler knows it is "c"
Code Link
I hope this explanation is helpful to you! Good luck with your coding!