Ways to minimize a javascript object so that it only includes properties from an interface

Here is an example of how a declared interface looks in TypeScript:

interface MyInterface {
  test: string;
}

An implementation with an extra property may look like this:

class MyTest implements MyInterface {
  test: string;
  newTest: string;
}

For Example (the variable 'reduced' still contains the property 'newTest'):

var test: MyTest = {test: "hello", newTest: "world"}

var reduced: MyInterface = test; // a solution is needed here

Question

How can you ensure that the 'reduced' variable only contains the properties declared in the 'MyInterface' interface in a general way?

Purpose

The issue arises when attempting to use the 'reduced' variable with angular.toJson before sending it to a rest service. The toJson method transforms the newTest variable, even though it's not accessible on the instance during compile time. This causes the rest service to reject the json because it has properties that shouldn't be included.

Answer №1

Unfortunately, achieving this task is not feasible due to the nature of interface being a Typescript concept and generating empty JavaScript code when transpiled. This results in a lack of properties to interact with at runtime.

//transpiled code is empty!
interface MyInterface {
  test: string;
}

To work around this constraint, you can follow the approach suggested by @jamesmoey. Another method I prefer is defining the 'interface' as a class instead:

class MyInterface {
  test: string = undefined;
}

One way to proceed is using lodash to select specific properties from the 'interface' to inject into your object:

import _ from 'lodash';  //npm i lodash

const before = { test: "hello", newTest: "world"};
let reduced = new MyInterface();
_.assign(reduced , _.pick(before, _.keys(reduced)));
console.log('reduced', reduced)//only contains 'test' property

Check out the working example on JSFiddle.

This practical solution has proven effective for me, bypassing debates over semantic concerns like whether it truly functions as an interface or naming conventions (e.g. IMyInterface or MyInterface), while enabling mocking and unit testing.

Answer №2

With the introduction of Object Spread and Rest in TS 2.1, a new way is now available:

let myExample: Example = {name: "apple", type: "fruit"}

let { name, ...modified } = myExample;

As a result, the modified object will contain all properties except for "name".

Answer №3

Another method to consider:

It has been noted in previous responses that it is impossible to completely avoid runtime actions; TypeScript transpires into JavaScript, primarily by discarding interface/type definitions, annotations, and assertions. The type system becomes erased, leaving your MyInterface absent from the runtime code where it is needed.

Thus, a solution involves creating an array of keys that should be retained in the reduced object:

const myTestKeys = ["test"] as const;

This approach is delicate since any modifications to MyInterface may go unnoticed by your code. To mitigate this issue, you can establish type alias definitions that trigger a compiler error if myTestKeys does not align with keyof MyInterface:

// An error will be thrown if myTestKeys contains entries not present in keyof MyInterface:
type ExtraTestKeysWarning<T extends never =
  Exclude<typeof myTestKeys[number], keyof MyInterface>> = void;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type 'UNION_OF_EXTRA_KEY_NAMES_HERE' does not satisfy the constraint 'never'

// An error will be thrown if myTestKeys lacks entries from keyof MyInterface:
type MissingTestKeysWarning<T extends never =
  Exclude<keyof MyInterface, typeof myTestKeys[number]>> = void;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type 'UNION_OF_MISSING_KEY_NAMES_HERE' does not satisfy the constraint 'never'

While this approach may lack elegance, modifying MyInterface triggers an error in one or both of the above lines, prompting developers to adjust myTestKeys.

Though methods exist to broaden the scope or reduce intrusiveness, TypeScript's primary function is to alert you via compiler warnings when changes occur unexpectedly within an interface; actual runtime behavior adjustments are rare.


With the desired keys defined, you can create a pick() function to extract specific properties from an object:

function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
  return keys.reduce((o, k) => (o[k] = obj[k], o), {} as Pick<T, K>);
}

Applying this function to your test object generates reduced:

var test: MyTest = { test: "hello", newTest: "world" }

const reduced: MyInterface = pick(test, ...myTestKeys);

console.log(JSON.stringify(reduced)); // {"test": "hello;"}

Success!

Link to Play around with the code

Answer №4

Are you looking to only assign properties from an interface? TypeScript does not have built-in functionality for this, but it is easy to create a function that can achieve this.

interface IPerson {
    name: string;
}

class Person implements IPerson {
name: string = '';
}
class Staff implements IPerson {
name: string = '';
    position: string = '';
}

var jimStaff: Staff = {
    name: 'Jim',
    position: 'Programmer'
};

var jim: Person = new Person();
limitedAssign(jimStaff, jim);
console.log(jim);

function limitedAssign<T,S>(source: T, destination: S): void {
    for (var prop in destination) {
        if (source[prop] && destination.hasOwnProperty(prop)) {
            destination[prop] = source[prop];
        }
    }
}

Answer №5

The newTest property mentioned in your example will not be accessible through the reduced variable, highlighting the importance of using types. TypeScript provides type checking functionality rather than directly manipulating object properties.

Answer №6

Upon encountering an issue with the accepted answer provided by @wal, I discovered that it only caters to flat object use cases and may not be suitable for nested fields. To address this, I utilized a helpful package named Joi (https://www.npmjs.com/package/joi). Apart from effortlessly removing unexpected fields, Joi offers robust validation capabilities. This ensures not only sanitized data but also validation against expected criteria, triggering errors if any required field is absent or of an invalid type. Moreover, you can define intricate schemas according to your needs.

Here's a quick example:

import Joi from 'joi';
const schema = Joi.object({
    name: Joi.string().required(),
    items: Joi.array()
      .items(Joi.object({ id: Joi.string().required() }))
      .required(),
  });

  const result = Joi.attempt(
    {
      name: 'test',
      weirdField: 'bla bla bla',
      items: [{ id: '1', weirdField: 234 }],
    },
    schema,
    { stripUnknown: true },
  );

  console.log(result);

{ name: 'test', items: [ { id: '1' } ] }

Answer №7

How can you ensure that the 'reduced' variable only includes properties declared in the 'MyInterface' interface?

Since TypeScript follows a structural typing system, any object with matching properties is considered type compatible and can be assigned to another.

In the upcoming release of TypeScript 1.6, there will be a feature called freshness which will help identify potential typos more easily (only applicable to object literals):

// ERROR : `newText` does not exist on `MyInterface`
var reduced: MyInterface = {test: "hello", newTest: "world"}; 

Answer №8

Simple illustration:

const all_cars = { sedan: 'Toyota', SUV: 'Jeep', truck: 'Ford' };
const { sedan, ...other_cars } = all_cars;
console.log(sedan); // Toyota

Answer №9

To address this issue, consider utilizing a class instead of an interface and implementing a factory method. By creating a public static member function that returns a new object of its type, you can ensure that the model is the sole entity where permitted properties are defined. This prevents accidental updates to properties when changes occur within the model.

class MyClass {
  test: string;

  public static from(myClass: MyClass): MyClass {
    return {test: myClass.test};
  }
}

For instance:

class MyTest extends MyClass {
  test: string;
  newTest: string;
}

const myTest: MyTest = {test: 'foo', newTest: 'bar'};
const myClass: MyClass = MyClass.from(myTest);

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

Vue.js does not support sorting columns

In this specific codepen example, I have created a Vue table that allows for sorting by clicking on the column name. Although I can successfully retrieve the column name in the function and log it to the console, the columns are not sorting as expected. Wh ...

What is the best way to incorporate Vue.js into a Django project?

We are currently working on a school project that involves creating a complete website using Django and Vue. However, we are facing challenges when it comes to integrating Vue into our Django project. In order to simplify things, we have decided to use Vu ...

Getting a portion of a href link in Java using Selenium

I am looking to compare the current URL in Google Chrome with a specific href link that contains two URLs. I need to extract the first link from this specific href link: click here <a href="http://pubcontentqa.perion.com/dz2/html/interstitial.html?http ...

When calling an API endpoint, nodeJS is unable to access the local path

I've encountered a strange issue with my code. When I run it as a standalone file, everything works perfectly fine. However, when I try to utilize it in my API endpoint and make a request using Postman, it doesn't seem to function properly. What ...

Detecting Eyes with the Power of JavaScript and HTML5

Looking for suggestions, steps, or algorithms for performing eye detection on 2D images using JavaScript and HTML5. I have successfully converted RGB to YCbCr color space. Now seeking assistance with eye extraction. function detectEyes(e) { var r, g ...

Using the Python-requests module, we can send a POST request with two "values" to scrape and renew a website

The initial inquiry has been resolved, however, there is still EDIT to address. Currently, I am utilizing Python along with the requests module for web scraping. This requires me to simulate clicking a Renew-Button, which is actually a link (href) wrapped ...

Enhance the visibility of the google.maps marker by zooming in

I am having trouble properly zooming to a specific marker. When trying to change the view to a designated marker, I am unable to achieve it successfully. I have attempted: map.setCenter(location); map.setZoom(20); as well as map.fitBounds(new google.m ...

Guide to autonomously scrolling exclusively within the <div> element in Vue.js

I am currently developing a frontend application using Vue 2. Within the application, I have implemented a chat feature and enabled auto-scroll to the end of the chat on update(). An issue has arisen where the scroll action affects not only the chat box ...

What is the best way to obtain a virtual in mongoose when a property is excluded?

Imagine I have a post Model with fields like UserId, Title, Desc, and a likes array which references UserIds. When querying, I have a virtual property to calculate the number of likes a post has: schema.virtual("numLikes").get(function () { return this. ...

Is there a different way to retrieve data in Node.js instead of using mysqli

<?php $connect = mysqli_connect('localhost', "root", "", "test"); if(!$connect){ die(mysqli_connect_error()); } $sql = "SELECT * FROM articles"; $result = mysqli_query($connect, $sql) or die ...

Angular Unit testing error: Unable to find a matching route for URL segment 'home/advisor'

Currently, I am working on unit testing within my Angular 4.0.0 application. In one of the methods in my component, I am manually routing using the following code: method(){ .... this.navigateTo('/home/advisor'); .... } The navigateTo funct ...

Updating the ngModel within a directive using another directive

Struggling to grasp why modifications to an ngModel don't transfer between directives. Take a look at this plunker for a simplified example of the issue. Essentially, there's a directive I've defined that utilizes ngModel with an isolate sc ...

Eliminating Sunday and holiday hours between two specified dates

Looking for suggestions or methods to calculate idle time between two given date times? Challenges I'm facing include: If the start Date falls on a Sunday or Holiday: any remaining Sunday hours and the morning of the next day (until 6:30 AM) shoul ...

Prevent a submit button from submitting a form using jQuery

I am working on developing a password manager similar to a chrome extension. The goal is to detect the username and password fields when a user tries to log in to a website, such as Facebook, and then prompt the user if they would like to save these creden ...

Managing browser back button functionality

I've been struggling to find a solution for handling the browser back button event. I would like to prompt the user with a "confirm box" if they click on the browser back button. If they choose 'ok', I need to allow the back button action, ...

Comparing the efficiency of using arrays versus mapping to an object and accessing data in JavaScript

When considering the basics of computer science, it is understood that searching an unsorted list typically occurs in O(n) time, while direct access to an element in an array happens in O(1) time for HashMaps. So, which approach yields better performance: ...

Utilizing Prototype in Node.js Modules

Currently, I am working on a project involving multiple vendor-specific files in node. These files all follow a similar controller pattern, so it would be more efficient for me to extract them and consolidate them into a single common file. If you're ...

Encountering error code 2064 without any clear explanation in sight

Hey, I'm currently facing an issue while uploading values to a MySQL table from Node.js. The error 1064 keeps popping up, indicating that the query is badly formatted. However, I can't seem to pinpoint the exact problem. Here's the query in ...

Looking to grab the names of clicked elements using VueJS

Utilizing Vue, I am in need of determining which element was clicked - one being an i and the other a button. This information is crucial for passing a variable to a shared modal that displays different text based on the click event. While I could create t ...

JavaScript validation for basic "select" isn't functioning as expected

I need assistance with my simple "select" validation using JavaScript. The issue I am facing is that when validating the "select" element, it does not display the select options to users after a validation error occurs. "The select option is not re-enabl ...