Typescript double-sided dictionary: a comprehensive guide

Looking for a dual-sided dictionary implementation in TypeScript that allows you to retrieve values using keys and vice versa.

An initial approach could be storing both items as keys:

dict = {"key": "value", "value": "key"}

But I am curious if there are alternative solutions available.

Answer №1

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!

Answer №2

Enums in Typescript automatically come with reverse mappings by default when they are numeric.

For example:

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

Aside from that, it seems like there isn't a simpler way than what you already did.

Answer №3

Method: There are multiple ways to tackle this issue, but the approach outlined below seemed to be the most efficient within a limited time frame. By utilizing a Proxy, we were able to tap into the inherent functionality of the Object getter and incorporate some customized features.

Applications:

  1. Retrieve value based on key. Using the conventional key lookup method will yield the expected value. e.g., console.log(dict.key) or console.log(dict['key'])
  2. If no key or value is specified, the Proxy will return the object in its original initialization state. console.log(dict).
  3. Obtain key based on value. Aiming for the value as a reference will trigger a search for the corresponding key, or if not found, retrieve all entries of the object. console.log(dict['value']); would show the associated key
  4. This setup also facilitates modification. While values can be altered, there might be consideration towards making them immutable.

Note that this solution may not cover all scenarios, only addressing what was requested.

class DoubleSidedDictionary {
    constructor(init) {
        return new Proxy(init, {
            get(target, name) {
                if(typeof name !== 'string') {
                    return target;
                }
                if (target[name]) {
                    return target[name];
                }
                const entries = Object.entries(target);
                const entry = entries.filter(entry => name == entry[1]);
                return entry.length > 0 ? entry[0][0] : undefined;
            }
        });
    }
}

const dict = new DoubleSidedDictionary({"key": "value", "something": "else"});

This implementation accommodates stringified versions of various data types. It is essential to convert them back to their original forms when necessary.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to transfer information between pages using onclick in node.js and express?

script1.js const database = new Datastore('database.db'); database.loadDatabase(); app.get('/api', (request, response) => { database.find({},(err,data)=> { if(err){ response.end(); return; ...

Sorting a list based on user-defined criteria in Ionic 3

Currently working on a project using Ionic and Firebase, I am faced with the task of comparing arrays produced by users with those stored in Firebase. This comparison is essential for filtering a list of products. The user can reorder a list containing 3 ...

Convert require imports to Node.js using the import statement

Currently learning NodeJs and encountering a problem while searching for solutions. It seems like what I am looking for is either too basic or not much of an issue. I am working on integrating nodejs with angular2, which involves lines of code like: impo ...

Utilizing the power of Node.js with Oracle seamlessly, without the need for the Oracle Instant

Currently, I am working on testing the connectivity to our Oracle databases. Recently, I came across node-oracledb, a tool released by Oracle that aims to simplify this process. However, one major hurdle is the requirement of having the Oracle Instant Clie ...

The FileSaver application generates a unique file with content that diverges from the original document

I'm attempting to utilize the Blob function to generate a file from information transmitted via a server call, encoded in base64. The initial file is an Excel spreadsheet with a length of 5,507 bytes. After applying base64 encoding (including newlines ...

In the case that the prop is empty or undefined, display an error message before rendering the full component

I am working on an ImageBlock component, which has several props like url, alt, caption, and optionally quality with a default value of 75. The essential prop here is the url. I need a quick way to immediately display an AlertError if the url is not provi ...

Transmit information to the client-side webpage using Node.js

One of the main reasons I am diving into learning Node.js is because I am intrigued by the concept of having the server send data to the client without the need for constant querying from the client side. While it seems achievable with IM web services, my ...

Utilizing titanium to develop a functionality that listens for button presses on any area of the screen

I am trying to simplify the action listener for 9 buttons on a screen. Currently, I have individual event handlers set up for each button, which seems inefficient. Is there a way to create an array of buttons and manipulate them collectively? For example ...

The dropdown navigation bar fails to close upon being clicked

I'm currently facing an issue with the navbar in my project. The bootstrap navbar doesn't close automatically after clicking on a link. I have to move my mouse away from the navbar for it to close, which is not ideal. Additionally, I'm worki ...

Having trouble getting the ValidatorPipe to function properly in my nest.js application

Issue Description There is an issue with the current behavior where initializing a validation pipe for a request body does not reject invalid types as expected. Desired Outcome The expected behavior should be that when a user provides a value that does n ...

Working with dynamic checkbox values in VueJS 2

In my project using VueJS 2 and Vuetify, I am creating a subscription form. The form is dynamic and fetches all the preferences related to a subscription from the server. In the example below, the preferences shown are specifically for a digital magazine s ...

Preventing users from navigating away from the page without choosing an answer in a React application

My dilemma involves the implementation of a question component, where I aim to prevent users from leaving a page without selecting an answer. Below is my question component: import React, { Component } from 'react'; import { Link } from 're ...

Continue looping in Javascript until an empty array is identified

Currently, I am in search of a solution to create a loop in Javascript that continues until the array of objects is empty. The object I am working with looks like this: "chain": { "evolves_to": [{ "evolves_to": [{ ...

Can you determine the sequence in which these code statements are placed on the call stack?

I am a beginner in the world of Javascript and currently seeking to grasp a better understanding of how the JS execution engine operates. I am aware that any asynchronous code statement is pushed onto the call stack and then promptly removed, allowing it t ...

Steps for integrating a universal loader in Angular

My implementation of a global loader is as follows: In the CoreModule: router.events.pipe( filter(x => x instanceof NavigationStart) ).subscribe(() => loaderService.show()); router.events.pipe( filter(x => x instanceof NavigationEnd || x in ...

Issue with Material UI Table not refreshing correctly after new data is added

Currently, I am utilizing a Material-UI table to display information fetched from an API. There's a form available for adding new entries; however, the problem arises when a new entry is added - the table fails to update or re-render accordingly. For ...

Ways to confirm non-null values and bypass the row if it is

I have been attempting to compare 2 dates in order to appropriately display data in a table. I have tried the following approach: ${this.dateToCompare.getTime()} > ${row.CreateDate.getTime()} However, there is an issue where CreateDate has a null value ...

Guide to fetching and returning data from AJAX using a function

In my JavaScript file, I have a function that retrieves a value asynchronously from the server: function retrieveValue(userId) { $.ajax({ type: "POST", url: "http://localhost/bghitn/web/app_dev.php/get_number_of_articles", data ...

Vuetify offers a convenient solution for implementing multiple buttons in a Vue

My search bar retrieves data from an API query in the following format: Data: [[id, datafield1, datafield2],[id, datafield1, datafield2],...] I wish to include an on/off button for each row of the data, with each button having its own independent state. ...

What is the best way to extract the ID of an element that triggers an OnChange event?

Just a heads up: The solution to my problem ended up being CSS code that was triggered by clicking on a widget. Once I realized it was CSS, along with the widget name and hover action, I was able to handle it successfully. Previous question before the PS ...