34

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?

7
  • @wmehanna Not quite. Yes, I am using es6 with babel. So the final output is es5 already. What I want is to get an object just like my in example, because the class instance is not the same with that object. Commented Jan 9, 2016 at 22:17
  • Why can't you pass the instance? You're basically just copying it. Commented Jan 9, 2016 at 22:45
  • 2
    toJSON() {return Object.keys(this).reduce((obj, key) => {obj[key] = this[key]; return obj;}, {});} Commented Jan 9, 2016 at 22:47
  • 4
    To be precise, this isn't converting to JSON, this is converting to an object. JSON is explicitly an object notation. Commented Sep 21, 2016 at 21:14
  • 1
    To follow up on @DaveNewton's comment, JSON is a text format, and there is no such thing as a JSON object, since objects are in the realm of JavaScript, not JSON. Note that toJSON is also a special method name, used by JSON.stringify if present to get the string representation of an object. Commented Dec 17, 2019 at 23:21

8 Answers 8

43

Lots of answers already, but this is the simplest yet by using the spread syntax and de-structuring the object:

const {...object} = classInstance
Sign up to request clarification or add additional context in comments.

6 Comments

Best answer ! <3
It doesn't preserve methods.
@AleksandarBencun it actually does preserve methods, but the constructor is being overwritten because when using {...SomeClass} for spreading the class to a new object the new constructor will be the native Object constructor of JS objects
This is not answering the question; OP said explicitly: without losing methods and/or inherited properties
This does not perserve methods
|
14

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
  }, {})
}

enter image description here

4 Comments

That answer is awesome Man, I don't understand what it has been ignored. Well Done and thank for sharing!
This answer looks nice and concise. But, my OP required nested classes to be also converted to plain javascript object (preserving methods). I think you need to call your fn recursively to solve that specific scenario as well. I'll mark your answer as the accepted one after you edit your solution to cover the nested scenario. Thanks for this! :)
@alex-cory if you solve the problem mentioned in my previous comment please notify me to change the accepted answer! Thanks!
@eAbi I updated to have recursion
9

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;
}

1 Comment

This is the only proposed solution that works with inheritance.
3

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

2 Comments

This is theoretically slow because it converts two times.
This doesn't preserve methods.
2

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.

6 Comments

Nope, not working. I have a class hierarchy there, so it's not going to work
It works. I have tested calling toJSON on Human, Person and PersonWorker. It is working as expected.
It works on that example, indeed. But on my usecase it's still not working. Don't understand what's the problem :|
Let me try something different
This does not work with es6. However the solution privided by eAbi does.
|
2

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);
}

Comments

1

A current alternative (2022) is to use structuredClone():

https://developer.mozilla.org/en-US/docs/Web/API/structuredClone

1 Comment

This doesn't preserve methods as I've asked in my original post.
0

using Lodash

This method isn't recursive.

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.