Generate a new structured Record / Object using the keys from an existing one using code

Within my TypeScript program, I have defined two base types (Player, State) and a few nested Record types that serve as mappings.

Using a typed function, an instance of one of these records is created based on an existing instance of the nested record.


type Player = "1" | "2";
type State = "A" | "B" | "C";

type StateMapping = Record<State, State>;
type PlayerStateMappings = Record<Player, StateMapping>
type PlayerStates = Record<Player, State>;

const playerStateMappings: PlayerStateMappings = {
    "1": {
        "A": "B",
        "B": "C",
        "C": "A"
    },
    "2": {
        "C": "B",
        "B": "A",
        "A": "C"
    },
}

function nextStates(currentState: State): PlayerStates {
    var nextStates = {};
    for(const player of Object.keys(playerStateMappings)){
        nextStates[player] = playerStateMappings[player][currentState]
    }
    return nextStates;
}

console.log(nextStates("A"))

An issue arises with this code where a type error occurs at the return statement because the object was initially created without the required keys and added later:

TS2739: Type '{}' is missing the following properties from type 'PlayerStates': 1, 2
.

I am seeking a solution to avoid this type error by meeting the following criteria:

  1. The typesystem must remain strict, ensuring the nextStates function returns a complete and valid PlayerStates object.
  2. The nextStates object should be generated programmatically based on the keys of the playerStatesMapping object, eliminating the need to hardcode all players again.

Several potential solutions found on SO either compromise condition 1 or 2:

Approaches violating condition 1:

  1. Making the PlayerStates type partial:
    type PlayerStates = Partial<Record<Player, State>>;
  2. Enforcing type using the as keyword:
    var nextStates = {} as PlayerStates
    ; (from this question)

Approaches violating condition 2:

  1. Setting default values for each Player in the object creation:
    var nextStates = {"1": "A", "2": "B"}

Although the typing may seem excessive in this simplified example, it accurately reflects more complex projects where the mentioned requirements are crucial.

PS: With a background in Python, I am looking for a functionality similar to dict comprehension that would facilitate initializing a new dictionary based on iteration.

Answer №1

Thanks to the response from @hackape, I was able to generate the code below, although it involved redefining Object.fromEntries and Object.entries:

type Player = "1" | "2";
type State = "A" | "B" | "C";
type StateMapping = Record<State, State>;
type PlayerStateMappings = Record<Player, StateMapping>
type PlayerStates = Record<Player, State>;

const playerStateMappings: PlayerStateMappings = {
    "1": {
        "A": "B",
        "B": "C",
        "C": "A"
    },
    "2": {
        "C": "B",
        "B": "A",
        "A": "C"
    },
}

// more stringent version of Object.entries
const entries: <T extends Record<PropertyKey, unknown>>(obj: T) => Array<[keyof T, T[keyof T]]> = Object.entries

// more stringent version of Object.fromEntries
const fromEntries: <K extends PropertyKey, V>(entries: Iterable<readonly [K, V]>) => Record<K, V> = Object.fromEntries

function nextStates(currentState: State): PlayerStates {
  return fromEntries(
    entries(playerStateMappings).map(([player, mapping]) =>
      [player, mapping[currentState]]
    )
  )
}

Link to Playground

Answer №2

Consider adopting a more functional programming approach to address this issue. Eliminating the need for the nextStates variable can help solve the problem at hand.

function calculatePlayerStates(currentState: State): PlayerStates {
  return Object.fromEntries(
    Object.entries(playerStateMappings).map(([player, mapping]) =>
      [player, mapping[currentState]]
    )
  )
}

Answer №3

The error in question stems from your variable nextStates, as you haven't explicitly defined its type, resulting in it being inferred as an empty object {} upon declaration.

To address this issue, consider using the Partial type for your nextStates variable to allow it to start off as empty. However, you'll need a mechanism to inform the compiler when the full PlayerStates object has been fully populated using a guard. Here's a sample scenario:

/*
  Code snippet demonstrating key concepts like type mapping and guards
*/

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

Tips for maintaining tab state using Angular Material

In my Angular 8 application with Angular Material, I have implemented four tabs where each tab allows editing. However, when going back from the edit mode to the tabs, I want the last selected tab to remain selected. My approach so far is as follows: exp ...

Using the Nodejs Array prototype filter method within a JSON object

Attempting to create a function that filters and returns specific strings within JSON data. Any advice is appreciated. Thank you! [ { line: '{"status":"waiting"}' } ] var body_W = []; body_W.push({line: JSON.stringif ...

In JavaScript, merging objects will exclusively result in an identifier being returned

When working with mongoose, I have encountered an issue where combining data from multiple finds only displays the id instead of the entire object. Interestingly, when I use console.log() on the object directly, it shows all the contents. Below are snippe ...

Passing v-on to a sub element within a Vue component

Consider a component with the HTML structure outlined below: <template> <div class="row"> <div class="innerRow"> <div class="outterLabel"> <label class="labelCss">{{label}}</label> ...

Securing the connection between clients and servers through encryption

For my mobile client, I am using Xamarin, with node.js as my backend and MongoDB as the database. The main issue I am facing is how to securely store user data in the database. If I only do server-side encryption, there is a risk of hackers intercepting th ...

Execute JavaScript function on click event in NextJS

Is it possible to execute a JavaScript function on the client side without using addEventListener? This situation works with addEventListener. MyComponent.js import Script from 'next/script' export default function MyComponent({ props }) { ...

Arranging and structuring Handlebars templates

In this particular example... http://example.com/1 ...I am experimenting with organizing a Handlebars template and its corresponding content in separate files, as opposed to having them all combined in the HTML file or JavaScript script, like in this con ...

Issue with validation of radio buttons in an AngularJS form

Currently, I'm in the process of building a web application that involves 4 radio buttons. My main focus now is on validating these radio buttons to ensure that none are checked by default. In the scenario where the user tries to submit the form witho ...

Is it possible to manually trigger a version change transaction in IndexedDB?

I have been working on a Chrome extension that uses the IndexedDB to store data client-side in an IDBObjectStore within an IDBDatabase. The data structure requires users to be able to modify the object store freely, such as adding new objects or modifying ...

Is it possible for me to create separate class/interface based on property values?

My question pertains to a class named Selection (which could alternatively be an interface). The Selection class may feature a coverage property with a value of 'all' or 'selected'. If the coverage property is set to 'selected&ap ...

Having difficulty loading the JSON configuration file with nconf

I'm currently attempting to utilize nconf for loading a configuration json file following the example provided at: https://www.npmjs.com/package/nconf My objective is to fetch the configuration values from the json file using nconf, however, I am enc ...

Enhancing FuelUX repeater binding with a secondary JavaScript object

I am looking to customize the fuelux repeater grid by connecting with the child object and showcasing extra icons along with messages. ...

Navigating through Array Elements with ngFor and the Next Button

Just diving into the world of Ionic 3 - I'm interested in using ngFor to loop through an array. So far, I've managed to display one item at a time using the slice method. Now, I want to be able to move on to the next item in the array when the us ...

TestCafe Environment Variables are not properly defined and displaying as undefined

Exploring TestCafe and diving into the world of automated testing. Trying to master the tools with guidance from Successfully executing code on my Windows setup! fixture`Getting Started`.page`http://devexpress.github.io/testcafe/example`; test("My ...

Struggled with setting up the WebSocket structure in typescript

Issue Running the code below results in an error: index.tsx import WebSocket from 'ws'; export default function Home() { const socket = new WebSocket('ws://localhost:1919/ws'); return ( <div>Home</div> ); } ...

I am no longer able to connect to mySQL using node js

I recently upgraded my node version, and now I'm facing issues accessing my database through my application. I have tried various solutions such as: - Changing the route to 127.0.0.1. - Adding my port number 3306. - Including socketPath: '/Applic ...

By utilizing a combination of JavaScript and jQuery, we can dynamically fill interconnected select boxes with data from an

After finding solutions to this particular query, I successfully managed to populate a select box based on the selection made in another select box. (You can see my answer here) This was achieved by retrieving data from an array structure that was generate ...

Issue with updating the state index in React.js

this.data = {bar : [{a:30,b:50},{a:65,b:20},{a:18,b:95},{a:10,b:155}]};//data this.data.bar[2].a = 85;//modify data I want to change the value of property 'a' at index 2 in bar using setState. How can I achieve this? ...

How come the deleteOne and findById methods in mongoose are able to function properly even after the document has

During my development of an API using nodejs to interact with a MongoDB database, I encountered a peculiar issue after deleting a document. My API consists of various endpoints for retrieving all animals in the database, fetching a specific animal using i ...

Issue with Jest Testing: React Fragment Not Being Rendered

Currently, I am facing an issue while testing components related to rendering the rows within a material-ui table especially when using React Testing Library. To create the table rows of a material-ui table, I am utilizing react fragments to iterate throug ...