Create a TypeScript type that requires an object to contain at least one of a particular set of keys

This particular query has proven quite challenging for me to articulate, leading to difficulty in finding a definitive solution.

Below is the code snippet in question:

type myType = {
    apple ?: any
    banana ?: any
}

const sample = function(myObject : myType) {
    if (typeof myObject.apple !== 'undefined' || typeof myObject.banana !== 'undefined') {
        // Do something
    } else {
        // This will cause an error!
    }
}

sample({ apple: true }) // This works fine
sample({ banana: 'hello world' }) // This works fine
sample({ apple: false, banana: 'foo bar' }) // This works fine
sample({}) // This does NOT work
sample({ banana: undefined }) // This does NOT work
sample({ taco: 'hello world' }) // This does NOT work

The goal here is to create a type that can identify instances where the input is not as expected.

My Query: How can I adjust myType to generate an error when all of its keys are not set, yet still allow cases where at least one known key is set?


As an additional point: It seems plausible to utilize the approach below, but it appears somewhat clunky and may not be considered best practice. An alternative solution would be preferred given that my actual code contains numerous keys, making this method rather tedious.

type myType_1 = {
    apple : any
    banana ?: any
}
type myType_2 = {
    apple ?: any
    banana : any
}
type myType = myType_1 | myType_2

Answer №1

To achieve this, utilize a helper type like so: Try TypeScript Playground

type OneOf<T> = {
  [K in keyof T]-?: Pick<T, K> & Partial<T>
}[keyof T]

Keep in mind that to capture the scenario where sample({ banana: undefined }) is used, the type of banana should not be any as undefined can be assigned to any.

Answer №2

This particular query has some relevance, but it's not entirely applicable to your situation.

To eliminate the presence of 'undefined' values in an argument, a common approach is to iterate through each value and replace any instances of 'undefined' with 'never'. Since creating a value that corresponds to 'never' without type assertion is impossible, TypeScript throws an error upon encountering 'undefined'.

Here's an example illustrating this concept:


type ReplaceUndefined<Obj> = {
  [Prop in keyof Obj]: Obj[Prop] extends undefined ? never : Obj[Prop]
}

type Test = ReplaceUndefined<{ name: undefined }> // {name: never}

Furthermore, we must ensure that the keys in the provided argument match those in 'myType'. Typically, this condition suffices: keyof Obj extends keyof Source.

To simultaneously check for 'undefined', we need to merge these checks into one:


type ReplaceUndefined<Obj> = {
  [Prop in keyof Obj]: Obj[Prop] extends undefined ? never : Obj[Prop]
}

type Validation<Obj, Source> = keyof Obj extends keyof Source ? ReplaceUndefined<Obj> & Source : never

However, the validation process isn't complete yet. We also need to validate empty objects, which can be achieved using an example from a previous answer.

The complete code snippet is as follows:

type myType = {
  apple?: any
  banana?: any
}

type AtLeastOne<Obj, Keys = keyof Obj> = Keys extends keyof Obj ? Pick<Required<Obj>, Keys> : never


type ReplaceUndefined<Obj> = {
  [Prop in keyof Obj]: Obj[Prop] extends undefined ? never : Obj[Prop]
}


type Validation<Obj, Source> = keyof Obj extends keyof Source ? AtLeastOne<ReplaceUndefined<Obj> & Source> : never

const sample = function <Obj,>(myObject: myType & Validation<Obj, myType>) {

  if (typeof myObject.apple !== 'undefined' || typeof myObject.banana !== 'undefined') {
    // Do something
  } else {
    // This will cause an error!
  }
}

sample({ apple: true }) // This is good
sample({ banana: 'hello world' }) // This is good
sample({ apple: false, banana: 'foo bar' }) // This is good
sample({}) // error
sample({ banana: undefined }) // error
sample({ taco: 'hello world' }) // error

Interactive Playground

If you're keen on exploring static type validation further, feel free to peruse my blog.

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

The object's type remains a mystery

While working on implementing jwt authentication in Ionic, React with TypeScript, I faced a typescript error when trying to add a check in my App.tsx file after successful implementation. The error stated: Object is of type 'unknown' Below is ...

New Update in Typescript 4.9: The type 'NonNullable<K>' could potentially represent a primitive value, which is not allowed as the right operand in the 'in' operator. Error code: 2638

Amidst the changes in Typescript 4.9, there are some updates to how the in operator operates. Given the code snippet below, what approach can I take to convince tsc that id is not just a primitive value? export type KeyOrId = | { key: string; ...

Using the esnext compiler option in Typescript causes issues with importing ES6 modules from external libraries

When I specify either the module or target property in the compiler options to esnext (so I can use import("example") statements), my es6 import statements for npm installed libraries stop working (local modules like "./test.ts" still work). For example, ...

Continue to fulfill a promise until a specific condition is satisfied, all within the confines of a while loop

My goal is to create a while loop that encapsulates a promise, ensuring that my method runs until a specific condition is satisfied. I've attempted to do this, but unfortunately encountered an issue with infinite promises being generated, eventually l ...

Testing Angular Reactive Forms: Synchronizing HTML and Control values mismatch

I have been diligently following the Angular reactive form unit testing tutorial here, but I continue to struggle with keeping the control value and HTML value in sync. Below is how I've implemented it; note that I am trying to use setValue along with ...

Prevent Component Reloading in Angular 4 when revisiting the page

My application consists of three main components: 1) Map 2) Search 3) User Profile Upon logging in, the MAP component is loaded by default. I can navigate to other screens using the header menu link. I am looking to implement a feature where the map comp ...

Yup will throw an error if both a minimum value is set and the field is also marked

I am attempting to validate my schema using yup: import * as yup from "yup"; let schema = yup.object().shape({ name: yup.string().min(5) }); const x = { name: "" }; // Check validity schema .validate(x, { abortEarly: false }) . ...

Having trouble with Typescript accurately converting decimal numbers?

I am struggling with formatting decimals in my Typescript class. export myclass { deposit: number; } After converting my web API class to this Typescript class, my decimal amounts lose their additional zero. For example, 1.10 becomes 1.1. I want to keep ...

Is there a way to set up a function so that it only accepts parameter types that are specifically mapped to certain other types?

For a project, I am currently developing a function that verifies whether a specific field is required by passing the field's name as an argument. My goal is to ensure that the function only accepts field names that are defined within the Config type ...

Raycasting in Three.js is ineffective on an object in motion

Working on a project that combines three.js and typescript, I encountered an issue while attempting to color a sphere by raycasting to it. The problem arises when the object moves - the raycast doesn't seem to acknowledge the new position of the objec ...

Node OOM Error in Webpack Dev Server due to Material UI Typescript Integration

Currently in the process of upgrading from material-ui v0.19.1 to v1.0.0-beta.20. Initially, everything seems fine as Webpack dev server compiles successfully upon boot. However, upon making the first change, Node throws an Out of Memory error with the fol ...

What could be the root cause behind the error encountered while trying to authenticate a JWT?

I've been working on integrating a Google OAuth login feature. Once the user successfully logs in with their Google account, a JWT token is sent to this endpoint on my Express server, where it is then decoded using jsonwebtoken: app.post('/login/ ...

What is the best way to set a JSON string as a variable?

I am attempting to send form input data to a REST service. Currently, the format is as follows: { "locationname":"test", "locationtype":"test", "address":"test" } However, the service is only accepting the following format: { "value": "{ loca ...

"Exploring the process of extracting data from a JSON URL using NextJS, TypeScript, and presenting the

After tirelessly searching for a solution, I found myself unable to reach a concrete conclusion. I was able to import { useRouter } from next/router and successfully parse a local JSON file, but my goal is to parse a JSON object from a URL. One example of ...

How to conditionally apply a directive to the same tag in Angular 4

I am implementing angular 4 and have a directive in my template for validation purposes. However, I would like to first check if a specific condition is true before applying the directive. Currently, my code looks like this: <div *ngIf="groupCheck; els ...

Error! Unable to Inject ComponentFactoryResolver

Recently, I attempted to utilize ComponentFactoryResolver in order to generate dynamic Angular components. Below is the code snippet where I am injecting ComponentFactoryResolver. import { Component, ComponentFactoryResolver, OnInit, ViewChild } from "@an ...

Transforming the Material UI v5 Appbar theme upon opening a dialog box

My layout consists of an AppBar, Drawer, and the page content. Within the content, there is a Dialog that opens when a user clicks on a button. https://i.sstatic.net/5Rlno.png Interestingly, when the dialog is open, the color of the AppBar changes to whi ...

The status of the Mat-checkbox is determined by the response from the modal dialog

In my project, I have a list of users stored in an array. Using HTML, I utilized the *ngFor directive to create a checkbox for each user element. Additionally, there is an @Input assignedAccountIds[] which contains the IDs of default selected users. The c ...

Updating a data structure to include a new attribute within a subcategory (Routes)

Is there a way to include the whenRoute optional parameter in the data section of the Route type without directly altering the Angular types file? const routes: Routes = [ { path: 'pages', loadChildren: () => import('./pages/pa ...

What is the reason for a class's attributes being considered undefined even after they have been previously set?

Within my code, there is a class called WorkspaceDatabase that stems from the Dynamic Tree Example. I have incorporated some debugging information to gain a clearer understanding of the issue at hand. The Issue: Upon entering the complete() function, an ...