Transform a javascript object with class attributes into a simple object while keeping the methods

I am seeking a way to convert an instance of a class into a plain object, while retaining both methods and inherited properties. Here is an example scenario:

class Human {
    height: number;
    weight: number;
    constructor() {
        this.height = 180;
        this.weight = 180;
    }
    getWeight() { return this.weight; }
    // I want this function to convert the child instance accordingly
    toJSON() {
        // ???
        return {};
    }
}
class Person extends Human {
    public name: string;
    constructor() {
        super();
        this.name = 'Doe';
    }
    public getName() {
        return this.name;
    }
}
class PersonWorker extends Person {
    constructor() {
        super();
    }
    public report() {
        console.log('I am Working');
    }
    public test() {
        console.log('something');
    }
}
let p = new PersonWorker;
let jsoned = p.toJSON();

The desired structure of jsoned should resemble this:

{
    // from Human class
    height: 180,
    weight: 180,
    // when called should return this object's value of weight property
    getWeight: function() {return this.weight},

    // from Person class
    name: 'Doe'
    getName(): function() {return this.name},

    // and from PersonWorker class
    report: function() { console.log('I am Working'); },

    test: function() { console.log('something'); }
}

Is there a way to achieve this objective, and if so, how?

I require this functionality due to utilizing a framework that solely accepts objects as input, even though I am working with TypeScript and class inheritance.

Furthermore, performance implications are negligible since this conversion will only occur once.

Please note that solutions involving iteration through object properties may not work if the compiler's target option is set to es6. Such implementations might work on es5, where iterating through object properties using Object.keys(instance) is feasible.

My current implementation snippet is as follows:

toJSON(proto?: any) {
    // ???

    let jsoned: any = {};
    let toConvert = <any>proto || this;

    Object.getOwnPropertyNames(toConvert).forEach((prop) => {
        const val = toConvert[prop];
        // don't include those
        if (prop === 'toJSON' || prop === 'constructor') {
            return;
        }
        if (typeof val === 'function') {
            jsoned[prop] = val.bind(this);
            return;
        }
        jsoned[prop] = val;
        const proto = Object.getPrototypeOf(toConvert);
        if (proto !== null) {
            Object.keys(this.toJSON(proto)).forEach(key => {
                if (!!jsoned[key] || key === 'constructor' || key === 'toJSON') return;
                if (typeof proto[key] === 'function') {
                    jsoned[key] = proto[key].bind(this);
                    return;
                }
                jsoned[key] = proto[key];
            });
        }
    });
    return jsoned;
}

However, the current implementation does not yield the expected results. The resultant object includes all properties from each class but only methods from PersonWorker. What could be missing or incorrect in this approach?

Answer №1

While there have been many responses, here is a straightforward solution that utilizes the spread syntax and de-structuring technique on the object:

const {...data} = myInstance

Answer №2

This is the solution that has been effective for me

Updated Answer (utilizing recursion)

const getKeyNames = x => Object.getOwnPropertyNames(x).concat(Object.getOwnPropertyNames(x?.__proto__))
const checkIfObject = v => Object.prototype.toString.call(v) === '[object Object]'

const convertClassToObject = clss => getKeyNames(clss ?? {}).reduce((object, key) => {
  const [val, arr, obj] = [clss[key], Array.isArray(clss[key]), checkIfObject(clss[key])]
  object[key] = arr ? val.map(convertClassToObject) : obj ? convertClassToObject(val) : val
  return object
}, {})

var classInstance = new Response()
var objectRepresentation = convertClassToObject(classInstance)
console.log({ object: objectRepresentation, class: classInstance })

Original Answer

const convertClassToObject = theClass => {
  const originalClass = theClass || {}
  const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(originalClass))
  return keys.reduce((classAsObj, key) => {
    classAsObj[key] = originalClass[key]
    return classAsObj
  }, {})
}

https://i.sstatic.net/0I6L2.png

Answer №3

Well, it turns out that my initial implementation in the original post was incorrect, and the error was embarrassingly silly.

For proper implementation using es6, here's the corrected version:

toJSON(proto) {
    let jsoned = {};
    let toConvert = proto || this;
    Object.getOwnPropertyNames(toConvert).forEach((prop) => {
        const val = toConvert[prop];
        // exclude certain properties
        if (prop === 'toJSON' || prop === 'constructor') {
            return;
        }
        if (typeof val === 'function') {
            jsoned[prop] = val.bind(jsoned);
            return;
        }
        jsoned[prop] = val;
    });

    const inherited = Object.getPrototypeOf(toConvert);
    if (inherited !== null) {
        Object.keys(this.toJSON(inherited)).forEach(key => {
            if (!!jsoned[key] || key === 'constructor' || key === 'toJSON')
                return;
            if (typeof inherited[key] === 'function') {
                jsoned[key] = inherited[key].bind(jsoned);
                return;
            }
            jsoned[key] = inherited[key];
        });
    }
    return jsoned;
}

Answer №4

Although this approach may result in a loss of methods, it offers a straightforward method to transform a class instance into an object.

newObj = JSON.parse(JSON.stringify(classObj))

Answer №5

Here is a breakdown of the toJSON() method implementation. We are creating a new object and transferring all properties and methods from the current instance while excluding unwanted methods such as toJSON and constructor.

toJSON() {
    var jsonedObject = {};
    for (var key in this) {

        if (key === "toJSON" || key === "constructor") {
            continue;
        }
        jsonedObject[key] = this[key];
    }
    return jsonedObject;
}

I have conducted tests on the object produced by toJSON() using Chrome, and it operates exactly as expected.

Answer №6

Inspired by Alex Cory's approach, I have made some modifications to create my own solution. This function is designed to be assigned to a class as a method with the corresponding binding on this.

const convertToObject = function() {
  const originalObject = this || {};
  const keys = Object.keys(this);
  return keys.reduce((classObject, key) => {
    if (typeof originalObject[key] === 'object' && originalObject[key].hasOwnProperty('convertToObject') ) {
      classObject[key] = originalObject[key].convertToObject();
    } else if (typeof originalObject[key] === 'object' && originalObject[key].hasOwnProperty('length')) {
      classObject[key] = [];
      for (var i = 0; i < originalObject[key].length; i++) {
        if (typeof originalObject[key][i] === 'object' && originalObject[key][i].hasOwnProperty('convertToObject')) {
          classObject[key].push(originalObject[key][i].convertToObject());
        } else {
          classObject[key].push(originalObject[key][i]);
        } 
      }
    } else if (typeof originalObject[key] === 'function') { } // do nothing
    else {
      classObject[key] = originalObject[key];
    }
    return classObject;
  }, {})
}

If you are using TypeScript, you can apply this interface to any class that needs to be converted to an object:

export interface ConvertibleToObject {
  convertToObject: Function;
}

Remember to bind this in your classes like so:

class ExampleClass implements ConvertibleToObject {
   convertToObject = convertToObject.bind(this);
}

Answer №7

Utilizing Lodash Library

The approach outlined here does not involve recursion.


  convertToPlainObject() {
    return _.pickBy(this, entry => {
      return (
        !entry ||
        _.isString(entry) ||
        _.isArray(entry) ||
        _.isNumber(entry) ||
        _.isPlainObject(entry)
      );
    });
  }

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

From PHP to Javascript and back to PHP

I'm currently tackling an issue within my project: My database, utilizing PHP, provides an array containing a collection of JavaScript files that require loading. This list is stored in the $array(php) variable. My task is to extract these source fil ...

enable jQuery timer to persist even after page refresh

code: <div class="readTiming"> <time>00:00:00</time><br/> </div> <input type="hidden" name="readTime" id="readTime"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script&g ...

Implementing a change event upon setting a value to an input element using JavaScript

My plan is to develop a Chrome extension that can automatically fill passwords. The code for this functionality looks like the following: //get the account document.querySelector("input[type=text]").addEventListener('input', () => { ...

Angular: Utilizing Nested ng-repeat Alongside groupBy Feature on Initial Page Load or Refresh

With some help, I've made it this far on my project. However, I need to take one more step to achieve my goal. I want to group data based on an attribute that is currently passed through an ng-click action. Is there a way to automatically do this on p ...

Developing a custom functionality to retrieve a server cookie for authentication in NextJS version 14

I am in the process of incorporating an email address verification feature for users registering on my NextJS website with a WordPress installation as a headless CMS. Here's what I plan to do: Set a server token with the following value {id: <use ...

selectize.js typescript: Unable to access values of an undefined object (reading '0')

I've been working on incorporating selectize.js into my project using webpack and typescript. After installing selectize.js and the necessary types, I added the following to my code: yarn add @selectize/selectize yarn add @types/select2 Within my c ...

I'm experiencing an issue with Gravity Forms validating hidden fields, is there a solution for this problem?

On my webpage, there are four fields labeled A, B, C, and D. Each field has its own set of conditional logic that determines whether it should be visible or hidden. Let's say a user lands on this page and only field B is displayed while the others are ...

The error message "Seed is not defined" is raised when the program attempts to

I'm currently diving into fullstack vue and I'm perplexed by the error occurring in this particular scenario. window.Seed = (function () { const submissions = [ { id: 1, title: 'Yellow Pail', ...

Tips for transferring PHP variable from a drop down menu

Hello, I am currently working on creating a dropdown menu using the <select> tag. My goal is to have it so that when someone clicks on one of the options in the dropdown, a new window opens. Additionally, I want the PHP variable from the main page to ...

Implementing authorization middleware using Express.js in Ajax

My app has a straightforward authorization middleware that functions flawlessly with regular GET and POST requests. However, when I send a POST request via AJAX, the middleware fails to redirect to a 401 page and instead bypasses it, allowing the data to b ...

JavaScript timing the completion of AJAX requests

I'm working on a basic ajax request using js and php to fetch data from mysql. Check out the code snippet below: function ajax_function(a,b) { $.ajax({ type : 'POST', url : mine.ajax_url, dataType : 'json&ap ...

Utilizing generics with Swagger in NestJS

export class PaginatedResult<T> { @Expose() @ApiResponseProperty(type: T}) // It's unfortunate that this isn't working because it's a type but being used as a value @Transform(({ obj }) => obj.data.map((data) => new obj.cla ...

Setting up the Firebase emulator: should you use getFirestore() or getFirestore(firebaseApp)?

After delving into the process of connecting your app to the Firebase emulators like Firestore emulator, I came across the primary documentation which outlined the steps for Web version 9: import { getFirestore, connectFirestoreEmulator } from "fireba ...

Tips on transitioning between two tables

Recently, I created an HTML page entirely in French. Now, I am attempting to incorporate a language translation feature on the website that allows users to switch between French and English (represented by two flag icons). My concept involves using a tabl ...

Revamping elements according to ordered array. Angular version 4.3

Dealing with an array of data that needs to be sorted for displaying in a component seems to be a challenge. Despite having a functional code sample demonstrating the concept, the sorting is not reflected in the Angular app's DOM. The original data i ...

Utilizing webpack 4's JSON tree-shaking functionality for keys that include the hyphen character

I need assistance with utilizing webpack 4's JSON tree-shaking feature as I am encountering a hurdle. Here is an example of some functional code: import { accessibility_16 } from '@collab-ui/icons/data/iconsData.json'; console.log("access ...

Error: Sorry, there was an issue with the code (React)

I'm attempting to build a React project without Node, and I'm trying to call a JS file from an HTML file. This is just a simple example for learning purposes. However, I keep encountering the Uncaught SyntaxError: Unexpected token '<&apos ...

Adjust the hue of a circle based on whether the user's position falls within its designated range

I am working with a map that displays several circles, each with its own radius. When the page loads, I capture the user's position and show it on the map. Initially, all circles are red. My goal is to determine if the user's current position fal ...

Tips for implementing JS function in Angular for a Collapsible Sidebar in your component.ts file

I am attempting to collapse a pre-existing Sidebar within an Angular project. The Sidebar is currently set up in the app.component.html file, but I want to transform it into its own component. My goal is to incorporate the following JS function into the s ...

Attempting to load using ajax, Chrome and jquery-mobile will modify the location of the window through setting window.location.href

I'm encountering a challenge with my mobile application where I need to redirect users to the login page on a 401 ajax call. However, it seems that jQM is attempting to load this via AJAX when the request is sent. This solution works flawlessly for s ...