Deduce the general subclass within a dynamic data structure

My goal is to develop a configuration management class with key mapping and resolution strategies. I want to create an accessor function that can retrieve the value along with its correct data type from the dynamic map.

type ParameterDescription<T> = {
  default: T;
};

type ParameterDictionary = Record<
  string,
  ParameterDescription<string | number | boolean>
>;

type ParameterSubType<T> = T extends ParameterDescription<infer U> ? U : never;

const config: ParameterDictionary = {
  foo: { default: "test" },
  bar: { default: 1 },
  baz: { default: true },
};

function get<T extends ParameterDictionary, K extends keyof T>(config: T, key: K): ParameterSubType<T[K]>;
function get<T extends ParameterDictionary>(config: T, key: string): undefined {
  return config[key].default || undefined;
}

const a = get(config, "foo"); // type is string
const b = get(config, "bar"); // type is number
const c = get(config, "baz"); // type is boolean
const d = get(config, "boz"); // type is undefined

I'm exploring the possibility of achieving this without explicitly declaring an interface listing the key types like:

interface {
  foo: ParameterDescription<string>
  bar: ParameterDescription<number>
}

Answer №1

The main issue lies in your explicit annotation of the config variable as type ParameterDictionary, like this:

const config: ParameterDictionary = {
  foo: { default: "test" },
  bar: { default: 1 },
  baz: { default: true },
}

This approach negates more specific information that the compiler could infer from the object literal initializer. It's better to let the compiler infer the type instead:

const config = {
  foo: { default: "test" },
  bar: { default: 1 },
  baz: { default: true },
};

If you still want a warning if config doesn't match the expected type, consider using the satisfies operator on the initializer:

const config = {
  foo: { default: "test" },
  bar: { default: 1 },
  baz: { default: true },
} satisfies ParameterDictionary;

By implementing this, the compiler will understand that config.foo.default is a string, etc.


Next, adjust the call signature of get() to allow key to be any property key, not limited to known keys of config. This modification might look something like:

function get<T extends ParameterDictionary, K extends PropertyKey>(
  config: T, key: K
): K extends keyof T ? T[K]["default"] : undefined;

function get(config: ParameterDictionary, key: string) {
  return config[key]?.default; // Fix implemented here
}

The return type of get() is conditional based on whether K (type of key) is a known key of T (type of

config</code). If yes, it gets the type of <code>default
property of T[K]; otherwise returns undefined.

const a = get(config, "foo"); // const a: string
const b = get(config, "bar"); // const b: number
const c = get(config, "baz"); // const c: boolean
const d = get(config, "boz"); // const d: undefined

(Note: Fix was applied to ensure last result is truly undefined at runtime and not trigger a TypeError)


It's important to note that TypeScript object types are not strictly "sealed"; a type lacking mention of a key doesn't guarantee absence of such key on instances. Returning undefined for unknown keys may not always be precise:

const config2 = {
  foo: { default: "test" },
  bar: { default: 1 },
  baz: { default: true },
  oops: { default: "oops" }
} satisfies ParameterDictionary; // valid

const oops: typeof config = config2; // also valid
const e = get(oops, "oops");
// result: undefined but should be "oops"
console.log(e); 

This situation relies on aliasing and widening, posing an unlikely scenario, though worth noting. Implementations can opt for returning unknown or reject unknown keys altogether for safer operation.

Playground link to code

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 implement a search pipe in Angular 2 that effectively filters a JavaScript object?

Seeking guidance on creating a search pipe for Angular 2 to filter nested objects based on a search term. A basic version is up and running, but encountering two issues. The first issue involves hard coding key names or resorting to using JSON.stringify, ...

Update gulp configuration to integrate TypeScript into the build process

In the process of updating the build system for my Angular 1.5.8 application to support Typescript development, I encountered some challenges. After a complex experience with Grunt, I simplified the build process to only use Gulp and Browserify to generat ...

Angular2 - Transforming SVG elements with dynamic styles using ng-style

I'm trying to create SVG lines using ng-repeat and need to adjust the translation of each line. However, I'm having trouble getting the style to apply using ng-attr-style. my-component.js: import {Component} from 'angular2/core'; @Co ...

Disabling the <md-expansion-panel> in Angular2 Material2: A Step-by-Step Guide

I am encountering some difficulties with the official documentation of material design, they mentioned Expansion panels can be disabled using the disabled attribute. A disabled expansion panel can't be toggled by the user, but can still be manipulate ...

Export row with object from PrimeNG Turbo table

Currently, I am faced with the task of exporting data from a PrimeNG Turbo table to a CSV file. The challenge lies in the fact that the 'rows' variable in the code snippet below is an array of objects which are displayed in the table body using n ...

Error: The Angular test bed is unable to locate the mixpanel definition

Is there a way to test the functionality of the form without considering Mixpanel? I am encountering an error as follows. login.component.ts ngOnInit() { Mixpanel.trackEvent("View Screen", { "Screen Name": "Login" }); this.createForm(); } cr ...

Is the actual flow of nodes and Gulp sequences different from the ".pipe()" chain described?

Below is the code where fileShouldBePreprocessedBySass() is called before the execution of console.log('intercepted!');. In the function fileShouldBePreprocessedBySass(targetFileAbsolutePath), the parameter targetFileAbsolutePath will be undefine ...

Changing from a string to a personalized data type

I'm a beginner in JavaScript and I've created a custom type called "Stage". My goal is to convert strings to this custom type. The strings will always match one of these values exactly (with the first letter capitalized and written the same way). ...

React TypeScript: Ensuring Accurate Typing for Dynamic Paths

Here is an example of what the router looks like: import { BrowserRouter, Route, Switch } from 'react-router-dom'; ... <Route exact path='/confirm-email/:confirmationCode' component={ConfirmEmail} /> ...

Using angular to create an external hyperlink - my inability to access external pages is frustrating

Here's the question at hand: How can I make links in HTML or TypeScript work properly? I want to include links to external pages within my app. In a user profile, I have links to the social media accounts of that user (e.g. instagram.com/sisterofclar ...

The refresh function in the table is not working as expected when implemented in a functional component. The table being used is Material

I am currently utilizing MaterialTable from https://material-table.com/#/docs/features/editable to manage data and perform CRUD operations within my application. I am seeking a way to automatically refresh the table data after any CRUD operation (add, upda ...

Issue with Reactive Form - The call signatures of 'FormGroup' are not compatible

I have a function written in TypeScript: // Form summaryAreaForm = new FormGroup ({ summary: new FormControl(null) }) // Function update() { document.getElementById('textDiv').innerHTML = this.summaryAreaForm('summary').value ...

Assistance with Troubleshooting a Complicated JavaScript/TypeScript Software

As someone new to the world of Javascript and Typescript, I am looking to gain a deeper understanding of how a specific large application runs through its steps. Can anyone suggest a debugger or other helpful tool for this task? ...

Developing Angular PWAs with a focus on microfrontends

I have set up multiple microfrontends using an "app-shell" type of application for the domain root, with each microfrontend on the first path element. Each app is constructed as a standalone angular application utilizing shared libraries to reuse common co ...

Tips for presenting a quiz in a random order and preventing duplicate questions in a React Native app

Hello everyone, I am working on creating a Quiz app in React Native that displays questions randomly without any duplication. So far, I have managed to display the Quiz questions randomly, but I'm stuck on preventing duplicates. This is a new learning ...

Typescript: retrieve the data type of the initial property within a specified type

After running a GraphQL query, I receive a nested object where the query name serves as the first-level key and the actual data is located on the second level: { getProduct: { id: "1", name: "test", } } Within m ...

The parameter 'To' cannot be assigned the argument of type '{pathname: string; shipment: TShipment;}'

Is there anyone who can assist me with a Typescript issue I'm currently facing? Upon running tsc in my project, I encountered the following error: I am getting the error saying that 'Argument of type '{ pathname: string; item: Item; }' ...

Refine specific unions by considering past values that have been used

Here's the scenario at hand: type Option = 'x' | 'y' | 'z' | 'w' type Inquiry = { query: string; choices: Option[]; defaultChoice: Option // here's where it gets tricky } I am looking to set the def ...

I am looking to update Vue.js 3's reactive values by utilizing a method implemented in an external file

Exploring how to update reactive values in Vue.js 3 using a method from an external file. Currently experimenting with Vue.js 3, I am looking to pass reactive values to a method defined in an external file for updating purposes. During the testing phase ...

Encountering the error "No exported member 'RouteComponentProps' in the 'react-router-dom' module while upgrading to react-router v6"

We are currently in the process of migrating legacy class-based code to the latest version 6 of react router. However, we are encountering the following error during the migration: Module '"react-router-dom"' has no exported member &a ...