问题
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?
回答1:
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;
}
回答2:
This is what's working for me
const classToObject = theClass => {
const originalClass = theClass || {}
const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(originalClass))
return keys.reduce((classAsObj, key) => {
classAsObj[key] = originalClass[key]
return classAsObj
}, {})
}
回答3:
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.
回答4:
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);
}
来源:https://stackoverflow.com/questions/34699529/convert-javascript-class-instance-to-plain-object-preserving-methods