Maintain typings while filtering object properties (keys)

I've been working on a function to filter an object's properties in JavaScript, but I'm struggling with getting the types correct. Here's what I have so far:

// filter object keys
export const filterObjKeys = <T extends {}, K extends keyof T>(
  obj: T,
  includeKeys: K[] = [],
  excludeKeys: K[] = [],
): T => {
  return Object.fromEntries(
    Object.entries(obj).filter(([k, v]) =>
      includeKeys?.length > 0 ? includeKeys.includes(k) : !excludeKeys.includes(k),
    ),
  ) as Omit<T, typeof excludeKeys>;
};

Although the function works, the resulting types are not accurate for my needs. This is what I want instead:

interface Student {
  firtsName: string;
  lastName: string;
  email: string;
  class: number;
}

const student: Student = {
  firtsName: 'John',
  lastName: 'Doe',
  email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="016041632f626e6c">[email protected]</a>',
  class: 3,
};

const filteredStudentInc = filterObjKeys(student, ['firtsName', 'lastName']);
// {firtsName: 'John', lastName: 'Doe'}

const filteredStudentExc = filterObjKeys(student, [], ['email'])
// {firtsName: 'John', lastName: 'Doe', class: 3}

Answer №1

Let's start by updating our constraints. T extends {} does not imply that T is a non-primitive type. The appropriate choice would be object:

// true
type Case1 = number extends {} ? true : false
// false
type Case2 = number extends object ? true : false

For the keys, it's likely that only string keys are needed. Therefore, let's extract only string keys by intersecting keyof T & string or using Extract Extract<keyof T, string>

Now, let's implement the pickObjKeys:

export const pickObjKeys = <T extends object, K extends keyof T & string>(
  obj: T,
  keys: K[],
): Pick<T, K> => {
  return Object.fromEntries(keys.map((key) => [key, obj[key]])) as Pick<T, K>;
};

I have simplified the implementation's logic, opting for Array.prototype.map over Object.entries to generate entries for better readability. To ensure type safety, assertions are required. The selection of fields is accomplished using the Pick utility type.

Testing:

// const filteredStudentInc: Pick<Student, "firtsName" | "lastName">
const filteredStudentInc = pickObjKeys(student, ['firtsName', 'lastName']);

To improve type readability, a utility named Prettify is used. It transforms interfaces to types and simplifies intersections.

type Prettify<T> = T extends infer R
  ? {
      [K in keyof R]: R[K];
    }
  : never;

Prettify enhances readability by redeclaring and remapping types and fields.

Testing:

const pickObjKeys = <T extends object, K extends keyof T & string>(
  obj: T,
  keys: K[],
): Prettify<Pick<T, K>> => {
  return Object.fromEntries(keys.map((key) => [key, obj[key]])) as Prettify<
    Pick<T, K>
  >;
};

// const filteredStudentInc: {
//   firtsName: string;
//   lastName: string;
// }
const filteredStudentInc = pickObjKeys(student, ['firtsName', 'lastName']);

Now, let's move on to excludeObjKeys:

const excludeObjKeys = <T extends object, K extends keyof T & string>(
  obj: T,
  keys: K[],
): Prettify<Omit<T, K>> => {
  return Object.fromEntries(
    Object.keys(obj).reduce<[string, unknown][]>((acc, key) => {
      if (!keys.includes(key as K)) acc.push([key, obj[key as K]]);

      return acc;
    }, []),
  ) as Prettify<Omit<T, K>>;
};

The logic here is more intricate, with the use of Object.keys and Array.prototype.reduce() to filter out excluded properties from the entries. The type removal is facilitated by Omit utility type.

Testing:

// const filteredStudentExc: {
//   firtsName: string;
//   lastName: string;
// }
const filteredStudentExc = excludeObjKeys(student, ['email', 'class']);

Access the 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

Unexpected token { in Fuse-Box when using Typescript

Here's the beginning of my fuse.ts file import { CSSPluginOptions } from 'fuse-box/plugins/stylesheet/CSSplugin'; import { argv } from 'yargs'; import * as path from 'path'; import { CSSPlugin, CSSResourcePlugin, Env ...

Use the rowTemplate in a Kendo grid without replacing the existing one

I am currently utilizing Angular 1.4 with TypeScript and Kendo UI (employing angular directives). My goal is to create a RowTemplate for each row that dynamically changes the color based on a specific property of the item. While I am aware of jQuery solu ...

Is there a way to execute a code snippet just once when focusing on a specific field?

<form id="myForm"> <label for="fname">First name:</label><br> <input type="text" id="fname" name="fname"><br> <label for="mname">Middle name:</label> ...

Bespoke Socket.io NodeJS chamber

I am currently developing an application involving sockets where the requirement is to broadcast information only to individuals within a specific room. Below is a snippet of the code from my server.ts file: // Dependencies import express from 'expre ...

Discover the steps to dynamically set global data in Vue during runtime

I am currently working on a Vue application that requires fetching data from JSP at runtime, which means I cannot use .env files. As a solution, I am attempting to set data in Vue that can be accessed throughout the entire application (components, mixins, ...

Extract objects from a nested array using a specific identifier

In order to obtain data from a nested array of objects using a specific ID, I am facing challenges. My goal is to retrieve this data so that I can utilize it in Angular Gridster 2. Although I have attempted using array.filter, I have struggled to achieve t ...

Encountering an uncaughtException: Error stating that the module '....nextserverapphomelibworker.js' cannot be located while attempting to utilize pino.transport in Next.js

I recently set up a Next.js project with typescript using create-next-app. Opting for Pino as the logging library, recommended by Next.js, seemed like the logical choice. Initially, when I utilized Pino without incorporating its transport functionality, e ...

Enhancing TypeScript - Managing Variables within Namespace/Scope

Why is the console.log inside the function correctly logging the object, but after the function returns it logs undefined, failing to update the variable? In addition, when using this within testNameSpace, it returns window. Why is that? namespace testNa ...

Trouble integrating PDF from REST API with Angular 2 application

What specific modifications are necessary in order for an Angular 2 / 4 application to successfully load a PDF file from a RESTful http call into the web browser? It's important to note that the app being referred to extends http to include a JWT in ...

Explore a vast array of items in a collection

I have a massive collection of various objects that I need to sift through. With over 60,000 elements, the search operation can sometimes be painfully slow. One typical object in this array has the following structure: { "title": "title" "company": ...

Transferring Files from Bower to Library Directory in ASP.Net Core Web Application

Exploring ASP.Net Core + NPM for the first time, I have been trying out different online tutorials. However, most of them don't seem to work completely as expected, including the current one that I am working on. I'm facing an issue where Bower ...

In TypeScript and React, what is the appropriate type to retrieve the ID of a div when clicked?

I am facing an issue in finding the appropriate type for the onClick event that will help me retrieve the id of the clicked div. const getColor = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const color = event.target.id; // ...

Is there a way to simulate AWS Service Comprehend using Sinon and aws-sdk-mock?

As a newcomer to Typescript mocking, I am trying to figure out how to properly mock AWS.Comprehend in my unit tests. Below is the code snippet where I am utilizing the AWS Service Comprehend. const comprehend = new AWS.Comprehend(); export const handler ...

Accessing information from an Odata controller in Angular2

Greetings as a first-time question asker and long-time reader/lurker. I've been delving into learning angular2, but I'm facing some challenges when it comes to retrieving data from an odata controller. In my simple Angular 2 application, I'm ...

Error message 'Module not found' occurring while utilizing dynamic import

After removing CRA and setting up webpack/babel manually, I've encountered issues with dynamic imports. https://i.sstatic.net/CRAWr.png The following code snippet works: import("./" + "CloudIcon" + ".svg") .then(file => { console.log( ...

What is the best way to display the source code of a function in TypeScript?

I am interested in obtaining the source code for my TypeScript function ** written in TypeScript **. Here is the TypeScript code: var fn = function (a:number, b:number) { return a + b; }; console.log("Code: " + fn); This code snippet displays the Ja ...

Ways to display all utilized typescript types within a project

Here is a snippet of code I'm working with in my project: describe('Feature active', () => { it('Should render a Feature', () => { const wrapper = shallow(<MyComponent prop1={1}/>); expect(wrapper.prop('p ...

Next JS now includes the option to add the async attribute when generating a list of script files

We are currently working on a nextJs application and are looking to add asynchronous functionality to all existing script tags. Despite numerous attempts, we haven't been successful in achieving this. Can anyone provide some guidance or assistance? &l ...

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 ...

Encountering challenges with reusing modules in Angular2

I am currently working on an angular2 website with a root module and a sub level module. However, I have noticed that whatever modules I include in the root module must also be re-included in the sub level module, making them not truly reusable. This is w ...