Is it possible to enforce a certain set of parameters without including mandatory alias names?

My inquiry pertains to handling required parameters when an alias is satisfied, which may seem complex initially. To illustrate this concept, let's consider a practical scenario.

If we refer to the Bing Maps API - REST documentation for "Common Parameters and Types" (accessible here: Bing Maps - Common Parameters and Types)

Upon reviewing the documentation, you may notice certain parameter names are aliased.

For instance, imagine a coordinates object containing both latitude and longitude values, each with short aliases - "lat" for latitude, and "lng" for longitude. How can we design types that intelligently accept necessary combinations while disregarding redundant parameters that have already been fulfilled?

type Latitude  = { latitude: number }  | { lat: number };
type Longitude = { longitude: number } | { lng: number };
type Location  = Latitude & Longitude;

function formatLocation(loc: Location) {
    //... Performing useful operations
}

formatLocation({
    lat: 123.456,
    latitude: 123.456, // <--- Ideally, this should trigger an error or be ignored since "lat" is already provided.
    longitude: -12.3456
});

I understand that TypeScript's "advanced types" (such as exclude, extract, omit, etc.) offer solutions for this scenario. However, I'm curious if there is a dynamic approach to achieving the same outcome.

Answer №1

To create a function named

OmitSuperfluousProperties<T>
, where it takes a union type T and generates a new union that explicitly restricts extra keys from other members. For instance,
OmitSuperfluousProperties<{a: string} | {b: number}>
would produce something like {a: string, b?: never} or {a?: never, b: number}.

Here's a potential definition:

type OmitSuperfluousProperties<T, K extends PropertyKey = AllKeys<T>> =
    T extends any ? (
        T & Partial<Record<Exclude<K, keyof T>, never>>
    ) extends infer O ? { [P in keyof O]: O[P] } : never
    : never;
type AllKeys<T> = T extends any ? keyof T : never;

In this implementation, distributive conditional types are utilized to deconstruct T into its individual elements, process each element, and then combine them back together.

The AllKeys<T> type is a conditional type that extracts all keys from the union type T. Consequently,

AllKeys<{a: string} | {b: number}>
would yield "a" | "b". This result serves as a default for the second argument of
OmitSuperfluousProperties<T, K>
. Each member of
T</code is merged with <code>Partial<Record<Exclude<K, keyof T>, never>>
. So, when T is {a: string} | {b: number} and K is "a" | "b", we get {a: string} & {b?: never} for the first member, and {b: number} & {a?: never} for the second one. Lastly, conditional type inference is used to convert intersections into singular object types.


Let's test it out:

type StrictLocation = OmitSuperfluousProperties<Location>

/* Output:
{
    latitude: number;
    longitude: number;
    lng?: undefined;
    lat?: undefined;
} | {
    latitude: number;
    lng: number;
    longitude?: undefined;
    lat?: undefined;
} | {
    lat: number;
    longitude: number;
    latitude?: undefined;
    lng?: undefined;
} | {
    lat: number;
    lng: number;
    latitude?: undefined;
    longitude?: undefined;
}*/

That appears to be the desired type structure. To confirm:

formatLocation({
    lat: 123.456,
    longitude: -12.3456
}); // succeeds

formatLocation({
    lat: 123.456,
    latitude: 123.456,
    longitude: -12.3456
}); // fails 
/* Error: Argument of type '{ lat: number; latitude: number; longitude: number; }
is not assignable to parameter of type 
'{ latitude: number; ...}';
*/

While you receive an error by providing values for both latitude and lat, the error message may lack clarity for users who are unfamiliar with the issue. Nonetheless, at least it indicates a type compatibility problem!

Hopefully, this information proves beneficial. Good luck!

Access the Playground code here

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 required property '0' is not found in the type 'any[]' but it is needed in the type '[{ post_id: string; title: string; }]'

I have gone through this post but I am still struggling to understand the issue. Why am I unable to pass this array to the update call? // create a new object with updated post properties const newPost = await this.postRepository.create(data); await thi ...

Steps for preventing text manipulation in ng2-ace-editorWould you like to restrict users from copying, pasting

How can I prevent users from copying, pasting, and dropping text in ng2-ace-editor? https://github.com/fxmontigny/ng2-ace-editor is the library I implemented in my Angular 5 application. ...

Exception occurs when arrow function is replaced with an anonymous function

Currently, I am experimenting with the Angular Heroes Tutorial using Typescript. The code snippet below is functioning correctly while testing out the services: getHeroes() { this.heroService.getHeroes().then(heroes => this.heroes = heroes); } H ...

NGRX Store: Unable to modify the immutable property '18' of the object '[object Array]'

While attempting to set up an ngrx store, I encountered 7 errors. Error Messages: TypeError: Cannot assign to read only property '18' of object '[object Array]' | TypeError: Cannot assign to read only property 'incompleteFirstPass ...

Having trouble with installing npm package from gitlab registry

I recently uploaded my npm package to the GitLab package registry. While the upload seemed successful, I am facing an issue trying to install the package in another project. When I run npm install, I encounter the following error: PS E:\faq\medu ...

Ways to redirect to a different page following a successful execution of a mutation in React-query

I am facing an issue where a memory leak warning appears when I redirect to another page after a mutation. Despite trying various methods, I have not been able to find a solution. The specific warning message is: Warning: Can't perform a React state ...

When converting a PDF to a PNG, the precious data often disappears in the process

I am currently facing a problem with the conversion of PDF to PNG images for my application. I am utilizing the pdfjs-dist library and NodeCanvasFactory functionality, but encountering data loss post-conversion. class NodeCanvasFactory { create(w, h) { ...

Having trouble uploading images using Ionic/Angular to a PHP script

I've been working on incorporating image uploading functionality into my Ionic app. Despite reading multiple tutorials, I haven't been able to get it up and running successfully. I'm specifically aiming for the app to work smoothly in a web ...

What is the proper way to invoke a child method after converting an object from a parent class to a child class?

When we have a subclass B that overrides a method from its superclass A in TypeScript, why does calling the method on an instance of A result in the parent class's implementation being called? In TypeScript, consider a class called Drug with properti ...

Issue with migrating TypeOrm due to raw SQL statement

Is it possible to use a regular INSERT INTO statement with TypeOrm? I've tried various ways of formatting the string and quotes, but I'm running out of patience. await queryRunner.query('INSERT INTO "table"(column1,column2) VALUES ...

What is the best way to optimize reactive values using the Vue composition API?

Imagine I have a block of code like this... const computedStyle = computed(() => normalizeStyle([undefined, styleProp, undefined]) ); const computedClass = computed(() => normalizeClass([ "button", classProp, { "b ...

I'm curious, which ref tag should I utilize for draft.js?

I'm currently working on a form using Draft.js with Next.js and TS, but I've encountered some errors in my code. Here is what I have so far: import {Editor, EditorState} from 'draft-js'; const [editorState, setEditorState] = useState( ...

Tips for verifying the variable type in a TypeScript ESLint custom rule

Trying my hand at creating a custom TypeScript-eslint rule that requires the callee's object of a node to be of a specific type, which I'll refer to as MyType. Utilizing the CallExpression from typescript-eslint in the code snippet below: Source ...

Using the useRef hook to target a particular input element for focus among a group of multiple inputs

I'm currently working with React and facing an issue where the child components lose focus on input fields every time they are re-rendered within the parent component. I update some state when the input is changed, but the focus is lost in the process ...

Utilizing Typescript for directive implementation with isolated scope function bindings

I am currently developing a web application using AngularJS and TypeScript for the first time. The challenge I am facing involves a directive that is supposed to trigger a function passed through its isolate scope. In my application, I have a controller r ...

Enhancing a component with injected props by including type definitions in a higher-order component

Implementing a "higher order component" without types can be achieved as shown below: const Themeable = (mapThemeToProps) => { return (WrappedComponent) => { const themedComponent = (props, { theme: appTheme }) => { return <Wrapped ...

Simple method for wrestling with objects in TypeScript HTTP POST responses

Can anyone help me understand how to extract information from an object received through an HTTP POST request? Below is the code I am using: this.http.post(url, user, httpOptions).subscribe(result => { console.log(result.status); }); The issue aris ...

Node corrupting images during upload

I've been facing an issue with corrupted images when uploading them via Next.js API routes using Formidable. When submitting a form from my React component, I'm utilizing the following functions: const fileUpload = async (file: File) => ...

Error encountered while implementing onMutate function in React Query for Optimistic Updates

export const usePostApi = () => useMutation(['key'], (data: FormData) => api.postFilesImages({ requestBody: data })); Query Definition const { mutateAsync } = usePostApi(); const {data} = await mutateAsync(formData, { onMutate: ...

Why do I keep getting an ExpressionChangedAfterItHasBeenChecked error after trying to update a random color in an

Is there a way to assign a random color from an array without causing the error message: "ExpressionChangedAfterItHasBeenChecked"? Even though the color of the chip changes quickly before the message appears, it seems like it's working. How can I reso ...