Limit the selection of 'pickable' attributes following selections in the picking function (TypeScript)

In the codebase I'm working on, I recently added a useful util function:

const pick = <T extends object, P extends keyof T, R = Pick<T,P>>(
obj: T,
keys: P[]
): R => {
if (!obj) return {} as R
return keys.reduce((acc, key) => {
   return {...acc, [key]:obj[key] };
}, {} as R)
};

The function is functioning correctly and TypeScript correctly infers the return type. However, there's one issue with the 'keys' parameter - I want to restrict it based on previously chosen keys.

For example:

const obj = {name: 'John Doe', age: '33', city: 'NYC'} 

// When providing keys in the array parameter, TypeScript infers them correctly
const a = pick(obj, ['name', 'age']) 

// HOWEVER, this is also allowed without any errors from TypeScript
const b = pick(obj, ['name', 'age', 'age']) 

// Moreover, If I've already typed 'name', I want the editor to suggest only 'age' and 'city' as options, but currently all keys are still shown.

I've tried various solutions (including currying), but haven't been successful yet. It seems like a challenging TypeScript puzzle. Any help would be greatly appreciated!

Answer №1

TypeScript lacks built-in support for creating a truly "unique" array without any duplicate elements, making it challenging to define keys: UniqueArray<P>.

One approach is to create a complex generic and conditional type that serves as a constraint on the tuple type passed into the keys parameter. This type ensures that each element in the array excludes all previous element types.

To illustrate this concept:

type ExcludeArray<T, U extends any[], A extends any[] = []> =
    number extends U['length'] ? [...T[]] : (
        U extends [infer F, ...infer R] ?
        ExcludeArray<Exclude<T, F>, R, [...A, F extends T ? F : T]> : A
    );

function pick<T extends object, K extends Array<keyof T>>(
    obj: T,
    keys: [...K] extends ExcludeArray<keyof T, K> ? K : ExcludeArray<keyof T, K>
): Pick<T, K[number]>;
function pick(obj: any, keys: PropertyKey[]) {
    if (!obj) return {}
    return keys.reduce((acc, key) => {
        return { ...acc, [key]: obj[key] };
    }, {})
};

The ExcludeArray<T, U> type takes a type T and a tuple type U, ensuring that duplicates are removed from the resulting array.


However, despite this solution works as intended, it doesn't offer robust IntelliSense support. When utilizing pick(), you may encounter issues with autocomplete prompting due to limitations with how TypeScript handles constraints.

In conclusion, while the proposed solution addresses the need for unique arrays, it may not provide optimal developer experience with IntelliSense. Consider whether accepting an array with possible repeats might be an acceptable compromise in situations where the uniqueness of elements is less critical than overall functionality.

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

Attempting to input a parameter into a personalized directive

Currently developing a small search application using Elasticsearch and AngularJS. The app consists of 2 pages: home and results page. However, I am encountering an issue with my custom search directive where I need to pass the value of a service into it. ...

Leveraging the power of the map function to cycle through two arrays

Right now in React, I am utilizing array.map(function(text,index){}) to loop through an array. But, how can I iterate through two arrays simultaneously using map? UPDATE var sentenceList = sentences.map(function(text,index){ return <ListGr ...

Converting SHA1 function from JavaScript to Python

Can you help with translating this Javascript code to Python? def sha1(str1, raw): hexcase = 0 chrsz = 8 str1 = utf16to8(str1) def utf16to8(str): out = "" len = len(str) i = 0 while i < len: ...

Testing event emitters in node.js: a step-by-step guide

Imagine the scenario where I need to create a basic task. However, I also need to develop a test that validates the following criteria: The task should emit an object. The emitted object must have a property named "name". I am utilizing mocha and chai e ...

Issue: Infinite loops detected in AJAX functions

I am using $.get methods in the following code snippet: $.get("/url" , function(z) { var a = $(z).find("span"); for (var i = 0 ; i<a.length; i++){ var v = $(a).eq(i).children("strong:first").text(); alert(v); } }); However, the s ...

Is it possible for you to pinpoint the mistake in the code below?

I've been trying to locate an error in the function but have been unable to do so. As a beginner in this area, I would appreciate your patience. <html> <head><title>print names</title> <script langua ...

Attempting to create a JavaScript class within an HTML document

Having an issue with instantiating a JavaScript class in a separate HTML file. The JavaScript class looks like this: class Puzzle { constructor(fenStart, pgnEnd) { this.fenStart = fenStart; this.pgnEnd = pgnEnd; } } module.exports = Puzzle; ...

Struggling with your Dropdown Menu onclick functionality in Javascript? Let me lend

I am seeking assistance with implementing a Javascript onclick Dropdown Menu. The current issue I am facing is that when one menu is opened and another menu is clicked, the previous menu remains open. My desired solution is to have the previously open menu ...

Observing the innerHTML of a Vue component

Currently, I am utilizing an npm package called vue3-markdown-it to display markdown within some of my content. When the component renders, I need to access its innerHTML and make customized modifications before displaying it in my div. However, there is ...

Transmit information using JSON format in Angular 8 using FormData

i am struggling with sending data to the server in a specific format: { "name":"kianoush", "userName":"kia9372", "email":"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bcd7d5ddd8ce85...@example.com</a>" } H ...

Issue with Bootstrap 4 nav bar where the first nav item is not highlighted and a problem with scrollspy

I have encountered an issue that I need help resolving. My Bootstrap 4 navbar has scrollspy enabled to update as you navigate through the page's sections, and a jQuery function provides smooth scrolling when clicking on navigation links. However, I am ...

What is the process of accessing JSON data transmitted from a Express server in JavaScript?

Working with a node server, I've set up a client-server model, meaning the client connects to "localhost" and express generates a response based on certain logic. While I'm able to send a JSON object through the response, I'm unsure of how t ...

Unexpected results: jQuery getJSON function failing to deliver a response

I am facing an issue with the following code snippet: $.getJSON('data.json', function (data) { console.log(data); }); The content of the data.json file is as follows: { "Sameer": { "Phone": "0123456789", }, "Mona": { "Phone": ...

What is the most effective method for incorporating keyframes using JavaScript in a dynamic way?

Is there a way to animate an HTML element by using a variable with keyframes, but without directly manipulating the DOM? Below is the HTML code: <div class="pawn" id="pawn1"></div> Here is the CSS keyframes code: @keyframe ...

Is there a more efficient method to execute this AJAX request?

$('#request_song').autocomplete({ serviceUrl: '<%= ajax_path("trackName") %>', minChars:1, width: 300, delimiter: /(,|;)\s*/, deferRequestBy: 0, //miliseconds params: { artists: 'Yes' }, onSelect: functi ...

Manipulate CSS Properties with Javascript Based on Dropdown Selection

I am currently working on implementing a feature that involves changing the CSS property visibility: of an <input> element using a JavaScript function triggered by user selection in a <select> dropdown. Here's what I have so far in my cod ...

Elusive Essence: Mysterious Origins of the

Beginner in the world of Ionic and Angular. I am attempting to create a test app and incorporating the factory function. I obtained the design from Ionic Creator and now trying to add my code to it. Here is my controller file. angular.module('app.c ...

After the "div" tag comes the magic of AJAX, PHP, and JAVASCRIPT, replacing

My Ajax implementation is successfully displaying the selected content on a div, but unfortunately, everything that comes after the div is getting replaced by this output. I am unsure of why this is happening and how to resolve it. If you have any insigh ...

Enhancing the Value of BehaviorSubject with Object Assign in Angular using Typescript and RxJs

I have a user object stored as a BehaviorSubject which is being observed. I need help figuring out how to detect if a specific value within my user object has changed. I've noticed that my current implementation doesn't seem to work correctly, a ...

Experiencing difficulties with using the javascript alternative to jQuery.ajax()

Here is a JavaScript Object: const data = { name: "John", age: 30, } const jsonData = JSON.stringify(data); The AJAX Request below successfully passes the data: $(function(){ $.ajax({ url:'two.php', ty ...