Reputation: 3470
I want to convert an instance class to plain object, without losing methods and/or inherited properties. So for example:
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();
jsoned
should look like 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 this possible to achieve, and if so, how?
In case you're wondering, I need this because I am using a framework that, unfortunately, accepts as input only an object, whereas I am trying to use TypeScript and class inheritance.
Also, I am doing the above conversion once so performance isn't an issue to consider.
The solutions consisting of iterating through object properties will not work if the compiler's target option is set to es6
. On es5
, the existing implementations by iterating through object properties (using Object.keys(instance)
) will work.
So far, I have this implementation:
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;
}
But this is still not working. The resulted object includes only all the properties from all classes but only methods from PersonWorker
.
What am I missing here?
Upvotes: 31
Views: 48721
Reputation: 1339
A current alternative (2022) is to use structuredClone()
:
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
Upvotes: 0
Reputation: 11865
This is what's working for me
Updated Answer (with recursion)
const keys = x => Object.getOwnPropertyNames(x).concat(Object.getOwnPropertyNames(x?.__proto__))
const isObject = v => Object.prototype.toString.call(v) === '[object Object]'
const classToObject = clss => keys(clss ?? {}).reduce((object, key) => {
const [val, arr, obj] = [clss[key], Array.isArray(clss[key]), isObject(clss[key])]
object[key] = arr ? val.map(classToObject) : obj ? classToObject(val) : val
return object
}, {})
var classs = new Response()
var obj = classToObject(classs)
console.log({ obj, classs })
Original Answer
const classToObject = theClass => {
const originalClass = theClass || {}
const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(originalClass))
return keys.reduce((classAsObj, key) => {
classAsObj[key] = originalClass[key]
return classAsObj
}, {})
}
Upvotes: 14
Reputation: 465
Lots of answers already, but this is the simplest yet by using the spread syntax and de-structuring the object:
const {...object} = classInstance
Upvotes: 40
Reputation: 383
This solution will lose methods, but it is a very simple solution to convert a class instance to an object.
obj = JSON.parse(JSON.stringify(classInstance))
Upvotes: 3
Reputation: 592
This method isn't recursive.
toPlainObject() {
return _.pickBy(this, item => {
return (
!item ||
_.isString(item) ||
_.isArray(item) ||
_.isNumber(item) ||
_.isPlainObject(item)
);
});
}
Upvotes: 0
Reputation: 386
I'm riffing on Alex Cory's solution a lot, but this is what I came up with. It expects to be assigned to a class as a Function with a corresponding bind on this
.
const toObject = function() {
const original = this || {};
const keys = Object.keys(this);
return keys.reduce((classAsObj, key) => {
if (typeof original[key] === 'object' && original[key].hasOwnProperty('toObject') )
classAsObj[key] = original[key].toObject();
else if (typeof original[key] === 'object' && original[key].hasOwnProperty('length')) {
classAsObj[key] = [];
for (var i = 0; i < original[key].length; i++) {
if (typeof original[key][i] === 'object' && original[key][i].hasOwnProperty('toObject')) {
classAsObj[key].push(original[key][i].toObject());
} else {
classAsObj[key].push(original[key][i]);
}
}
}
else if (typeof original[key] === 'function') { } //do nothing
else
classAsObj[key] = original[key];
return classAsObj;
}, {})
}
then if you're using TypeScript you can put this interface on any class that should be converted to an object:
export interface ToObject {
toObject: Function;
}
and then in your classes, don't forget to bind this
class TestClass implements ToObject {
toObject = toObject.bind(this);
}
Upvotes: 2
Reputation: 3470
Ok, so the implementation in my OP was wrong, and the mistake was simply stupid.
The correct implementation when using es6
is:
toJSON(proto) {
let jsoned = {};
let toConvert = 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(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;
}
Upvotes: 9
Reputation: 562
Here is the implementation for the toJSON() method. We are copying over the properties & methods from the current instance to a new object and excluding the unwanted methods i.e. toJSON and constructor.
toJSON() {
var jsonedObject = {};
for (var x in this) {
if (x === "toJSON" || x === "constructor") {
continue;
}
jsonedObject[x] = this[x];
}
return jsonedObject;
}
I have tested the object returned by toJSON() in Chrome and I see the object behaving the same way as you are expecting.
Upvotes: 2