How can one specify a type in Typescript with a precise number of properties with unspecified names?

Imagine I have a variable with a name and a value, both of which I need for a specific task such as logging. This can be achieved in the following way:

const some_variable = "abcdef"
const another_variable = 12345

const log1 = (name: string, value: any) => console.log(name, value)

log1("some_variable", some_variable)
log1("another_variable", another_variable)
log1("some_variable", some_variable, "another_variable", another_variable) // results in a compile time error
log1() // results in a compile time error

A more efficient approach would be

log2({some_variable})
log2({another_variable})

To achieve this, I can create a function

const log2 = (obj: { [key: string]: any }) => {
    const keys = Object.keys(obj)
    if( keys.length !== 1 ) throw new Error("only one key is expected")
    const key = keys[0]
    console.log(key, obj[key])
}

However,

log2({some_variable, another_variable}) // compiles but results in a run time error
log2({}) // compiles but results in a run time error

I want to force compilation errors on lines like log2({v1, v2}) and log2({}).
I aim to eliminate dynamic type checks and ensure that there is a compile time check to validate that the obj parameter has only one key, or an exact number of keys with names that are unknown to me.

Answer №1

If you do some intricate manipulations with the type system, it's possible to make the compiler raise an error if your object contains more than one known key. This snippet illustrates a potential approach:

type OneKey<T, K extends keyof T = keyof T> =
    string extends K ? never : number extends K ? never :
    K extends any ? { [P in keyof T]?: P extends K ? T[P] : never } : never;    

function log2<T extends object & OneKey<T>>(obj: T): void;
function log2(obj: any) {
    const keys = Object.keys(obj)
    if (keys.length !== 1) throw new Error("only one key is expected")
    const key = keys[0]
    console.log(key, obj[key])
}

This restricts T to object types without index signatures because objects with index signatures cannot be guaranteed to have only one key. It verifies that the object can be assigned to a "one-key" version of itself. For example, for an object like {a: string, b: number}, it checks against

{a?: string, b?: undefined} | {a?: undefined, b?: number}
. If the check fails as in this case, it indicates that the object has multiple keys. However, if you pass an object of type {a: string}, the checked type is {a?: string}, which matches the criteria.

To test its functionality, consider the following examples:

log2({ some_symbol }); // okay
log2({ some_symbol, some_other_symbol }); // error
//   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type '{ some_symbol: string; some_other_symbol: number; }' is not assignable 
// to type '{ some_symbol?: undefined; some_other_symbol?: number | undefined; }'.
log2({}); // error
//   ~~
// Type '{}' is not assignable to parameter of type 'never'.

Seems promising on initial inspection!

Note that there may be various edge cases where this setup might exhibit unexpected behavior due to limitations in the type-checking system. Objects with optional keys or those involving unions could lead to peculiar outcomes. Proceed cautiously!

Hoping this sheds light on your query. Best wishes!

Access the Playground link here

Answer №2

const log2 = (data: { [name: string]: any }) => {
    const names = Object.keys(data)
    // in the examples you provided, there are cases with 2 keys and 0 keys,
    // which causes an error as per your code logic
    if( names.length !== 1 ) throw new Error("only one key is expected")
    const name = names[0]
    console.log(name, data[name])
}

I'm not sure what result you anticipated. The code compiles correctly but may give a runtime error depending on input.

You might have intended to use the function like this:

log2({symbol_one: another_symbol})

This way, it sets up a dictionary with only one key, meeting your code's requirements.

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 are the best ways to handle data using the .pipe() function?

Looking to optimize an Angular component Typescript function that returns an Observable<book[]>. The logic involves: If (data exists in sessionStorage) Then return it Else get it from remote API save it in sessionStorage return it End ...

Exploring the depths of TypeScript's intricate type validation

Could you please review the comments in the code? I am trying to determine if it is feasible to achieve what I need or if TypeScript does not support such functionality. I require type checks for my addToRegistry function. Play around with TypeScript in ...

Creating a fresh JSON structure by utilizing an established one

I have a JSON data that contains sections and rubrics, but I only need the items for a new listing. The new object named 'items' should consist of an array of all the items. The final JSON output should be sorted by the attribute 'name&apos ...

Challenges arise when integrating Angular with Firebase, particularly in the realms of authentication and user

Currently, I am working on a project using Angular and Firebase. However, in the auth.service.ts file, Visual Studio Code is not recognizing the imports for auth and User. import { auth } from 'firebase/app'; import { User } from 'fireba ...

Retrieving the key from an object using an indexed signature in Typescript

I have a TypeScript module where I am importing a specific type and function: type Attributes = { [key: string]: number; }; function Fn<KeysOfAttributes extends string>(opts: { attributes: Attributes }): any { // ... } Unfortunately, I am unab ...

Exploring the world of Typescript and Angular Filter functionalities

I am looking to utilize one of my Angular Filters in my controller as a function. I came across a solution on this page: How to use a filter in a controler The last answer provided exactly what I needed, so I implemented it in my JS code: var MyFunc ...

Issue with Ionic 3 subscribes triggering repeatedly

I've been struggling with the code for an Ionic taxi app for a few weeks now. My main issue is that whenever the page loads, the subscription gets triggered multiple times along with other functions within it. The same problem occurs when any travel i ...

The 'RegExpExecArray | null' type is required to include a method '[Symbol.iterator]()' which returns an iterator to iterate through its values

As someone who is new to TypeScript, I have a good understanding of the basics but recently came across a typecast error that I am struggling to solve. const [full, id]: string | null = /.*media\/[^\/]+\/(.*)/.exec(item.uri) When it comes t ...

Is it possible to use Angular signals instead of rxJS operators to handle API calls and responses effectively?

Is it feasible to substitute pipe, map, and observable from rxjs operators with Angular signals while efficiently managing API calls and their responses as needed? I attempted to manage API call responses using signals but did not receive quick response t ...

Ways to retrieve root context within a Vue Composition API in Vue 3.0 + TypeScript

Looking to develop a TypeScript wrapper function that can trigger toast notifications using a composition function, following the guidelines outlined in Vue 3.0's Composition API RFC. In this instance, we are utilizing BootstrapVue v2.0 for toast not ...

Demonstrating reactivity: updating an array property based on a window event

One example scenario involves setting specific elements to have an active class by assigning the property "active" as true (using v-bind:class). This property is modified within a foreach loop, after certain conditions are met, through the method "handleSc ...

Struggling to get the bindings to work in my Angular 2 single-page application template project

I have recently started using the latest SPA template within Visual Studio 2017: https://blogs.msdn.microsoft.com/webdev/2017/02/14/building-single-page-applications-on-asp.net-core-with-javascriptservices/ The template project is functioning properly. ...

Gatsby website failing to create slugs as anticipated

While trying to follow the Gatsby tutorial, I ran into an issue with generating slugs for MDX files in a subdirectory of src/pages. For instance, if I have a file like src/pages/projects/devmarks/index.md, the expected slug according to the tutorial should ...

Creating an interface that features a function capable of accepting its own type and interacting with other interface implementations

interface I { test: (a: I) => boolean; } class Test implements I { //constructor (public additional: number) {} test (a: Test) { return false; } } The code is functioning, however, when we remove the comment from the constructor line, it stops ...

Prevent auth0 express middleware from causing server crashes by handling failures silently

I am currently integrating auth0 into a node project for authentication using JWTs. Each request to an authenticated endpoint requires a token, and auth0 provided me with this middleware function: import {auth} from 'express-oauth2-jwt-bearer'; i ...

Customizing Components in Angular 2/4 by Overriding Them from a Different Module

Currently, I am utilizing Angular 4.3 and TypeScript 2.2 for my project. My goal is to develop multiple websites by reusing the same codebase. Although most of the websites will have similar structures, some may require different logic or templates. My p ...

one-time occurrence of $mdToast injection within a parent class

Seeking advice on how to efficiently place a single instance of $mdToast (from Angular Material) into a base class (Typescript). In my UI, I have five tabs with separate controller instances and it seemed logical to centralize the $mdToast declaration in a ...

What scenarios call for utilizing "dangerouslySetInnerHTML" in my React code?

I am struggling to grasp the concept of when and why to use the term "dangerous," especially since many programmers incorporate it into their codes. I require clarification on the appropriate usage and still have difficulty understanding, as my exposure ha ...

Tips for avoiding multiple reference paths in Angular TypeScript: - Simplify your imports

Setting up Typescript for an Angular 1.5 application has been a bit of a challenge. To ensure that a TS file can be compiled by gulp without any errors, I have to include the following line: ///<reference path="../../../../typings/angularjs/angular.d.t ...

Tips on avoiding issues with the backslash character in Typescript

Can someone help me with creating a regular expression in Typescript that can match the decimal separator character followed by a sequence of zeros in a string? I have tried to come up with an expression as shown below: /\.0+\b/g However, since ...