Accept only the keys specifically assigned to an object

Trying to develop a TypeScript class where it is initialized with an object and contains a method that only accepts keys from that object. Here's the code:

class MyClass {
  properties = {};

  constructor(properties) {
    this.properties = properties;
  }

  // Method should only accept keys from this.properties
  pick(...propNames) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {});
  }
}

Encountered a similar issue as mentioned here, but struggling to apply it in my scenario since the properties are externally passed.

const props = { key: 'value', key2: 'value2' };
interface PropKeys {
  key: string;
  key2: string;
}
type KeyName = keyof(PropKeys);

// How can I make this work for the class?
const instance = new MyClass(props);
instance.pick('key', 'key2'); // Works fine
instance.pick('key3'); // Should produce a type error

Is there a solution? Can this be accomplished without explicitly defining InstanceKeys, but by deriving them from the initialize props?

Looking into generics, thinking of something like this:

class MyClass {
  properties = {};

  constructor<Type>(properties: Type) {
    this.properties = properties;
    type TypeKeys = keyof(Type);
  }
  
  pick(...propNames: TypeKeys[]) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {});
  }
}

However, encountering two type errors:

  • On <Type>: "Type parameters cannot appear on a constructor declaration."
  • On TypeKeys[]: "Cannot find name 'TypeKeys'. (Out of scope.)"

UPDATE: Getting closer, but facing an issue when properties are initially defined above the constructor:

class MyClass<PropType extends Properties> {
  properties: PropType = {};

  constructor(properties: PropType) {
    this.properties = properties;
  }

  pick(...propNames: Array<keyof(PropType)>) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {});
  }
}

The TS error encountered on that line is

Type '{}' is not assignable to type 'PropType'. '{}' is assignable to the constraint of type 'PropType', but 'PropType' could be instantiated with a different subtype of constraint 'Properties'

The concern here is that any passed-in properties may have their own keys, but must be instances of type Properties, which limits the values.

Answer №1

To properly specify the generic type, it should be placed on the class declaration rather than its constructor. The keyof Type should be defined as an anonymous type. Additionally, you need to provide a type for properties so TypeScript understands that it can be accessed using keyof Type. This is achieved in this example by assigning it a type of Partial<Type>.

A type assertion is also used to ensure that the initial empty object within your reduce method is typed as Partial<Type>, allowing TypeScript to correctly index it once created.

class MyCustomClass<Type> {
  properties: Partial<Type> = {};

  constructor(properties: Type) {
    this.properties = properties;
  }
  
  select(...propNames: (keyof Type)[]) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {} as Partial<Type>);
  }
}

TypeScript Playground

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 function navigator.canShare() encountered a permissions denial while running in Typescript

Currently, I am in the process of developing an Angular8 PWA and have successfully implemented webshare to share text content. To my excitement, Chrome has now extended its support for file sharing starting from May 2019. However, while attempting to int ...

Is it possible to retrieve the precise key currently indexed within type declaration?

I am currently working on developing a simple type that would require a nested object key to reference the outer level. For better understanding, let's take an example: const obj = { foo: { name: 'bar', ref: 'foo' // & ...

What could be causing my code to not run after subscribing to the observables?

In my code, I have two methods that return two lists: one for accepted users and the other for favorite users. The first part of my code works well and returns both lists, but in the second part, I need to filter out the accepted users who are also on the ...

"Exploring the dynamic duo of Angular2 and ng2Material

I am currently facing an issue with the styling in my code while using ng2Material with Angular2. First: A demonstration of Material style functioning properly can be seen in this plunker. When you click on the button, you will notice an animation effect. ...

There was an issue with matching the call for formatting the start date to "dd MMMM yy" in the function

Hey there, while deploying my project, I encountered this error: https://i.sstatic.net/kiXLA.png Error: No overload matches this call. Overload 1 of 4, '(value: string | number | Date): Date', resulted in the following error: Argument with ...

Output in Typescript for the chosen option

A message can be sent based on the user's choice of either Now or Later. If the user selects Now, the message will be sent immediately. If Later is chosen, a date format option needs to be created for setting a future date. <label for=""& ...

Extend the express request object with Typescript and then export the modified object

Seeking to enhance the Request object from express with custom fields using typescript. Based on this particular source, I created a file named @types/express/index.d.ts containing the following code : import { MyClass } from "../../src/MyClass" ...

TypeScript is unable to identify import paths

Currently facing an issue with a specific error in my development environment. I am working with React, TypeScript, and node.js using VSCode on a Mac. I can't pinpoint exactly when this error started occurring. Despite the existence of the file route ...

Create a fresh type by dynamically adjusting/filtering its attributes

Suppose we have a type defined as follows: type PromiseFunc = () => Promise<unknown>; type A = { key1: string; key2: string; key3: PromiseFunc; key4: string; key5: PromiseFunc; key6: SomeOtherType1[]; key7: SomeOtherType2[]; key8: ...

combine string inputs when ng-click is triggered

Is there a way to pass a concatenated string using ng-click to MyFunction(param: string)? I have tried but so far, no luck: <input id="MeasurementValue_{{sample.Number}}_{{$index}}" ng-click="Vm.MyFunction('MeasurementValue_{{sample.Number ...

Expanding the properties of an object dynamically and 'directly' by utilizing `this` in JavaScript/TypeScript

Is it possible to directly add properties from an object "directly" to this of a class in JavaScript/TypeScript, bypassing the need to loop through the object properties and create them manually? I have attempted something like this but it doesn't se ...

Alter the entity type when passing it as a parameter

Trying to alter the Entity type, I am invoking a function that requires two parameters - one being the entity and the other a location. However, when trying to assign a Type for the first entity, it throws an error: Error: Argument of type 'Node<En ...

Encountering a Typescript issue with mongoose

Working with node.js and various email addresses, I encountered a compile error: TS2345: Argument of type '(error: any, document: any) => void' is not assignable to parameter of type '(err: any) => void'. This error occurs at ...

Ionic Vue is throwing an error indicating that a property is not found on the type '{ $route(currentRoute: any): void; }'

Currently, I am developing an Ionic WebApp using Vue and TypeScript. My current task involves retrieving the current id parsed by the route. To achieve this, I have been working on a watcher: export default { data() { return { ...

Storing data received from httpClient.get and utilizing it in the future after reading

Searching for a solution to read and store data from a text file, I encountered the issue of variable scope. Despite my attempts to use the data across the project by creating a global variable, it still gets cleared once the variable scope ends. import ...

Leveraging Angular2's observable stream in combination with *ngFor

Below is the code snippet I am working with: objs = [] getObjs() { let counter = 0 this.myService.getObjs() .map((obj) => { counter = counter > 5 ? 0 : counter; obj.col = counter; counter++; return view ...

The variable 'HttpEvent<name[]>' cannot be assigned to the variable 'name[]' because their types are not compatible

This is the code I have written: export interface act { id: number; name: string; } public list!: act[]; getAll(token:any): void{ this.http.get<act[]>('http://localhost:4200/test', token) .subscribe( (val) => this. ...

What is the process for defining a collection of strings as a data type?

Is there a way to define a strict type for a group of strings that users must adhere to? I want to restrict input to only be one of the predefined strings in the group. How can I achieve this? This is my current approach: const validTypes: Array<strin ...

Is there a way to add zeros at the beginning of ZIP codes that have only 3 or 4 digits using LODASH or Typescript?

Looking at the JSON data below, it includes information on USPS cities, states, counties, latitude, longitude, and zip codes. With over 349,000 lines of data, it's very extensive. ... { "zip_code": 988, "latitude": 18.39 ...

When utilizing Angular, the mat-datepicker is displayed underneath the modal, even after attempting to modify the z-index

I am encountering a problem with a mat-datepicker displaying below a modal in my Angular application. Here are the key details: Html: <div class="col-12"> <mat-form-field appearance="fill"> <mat-label>Start Date ...