将JavaScript类实例转换为普通对象并保留方法

29
我希望将实例类转换为普通对象,同时不会失去方法和/或继承属性。例如:
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应该长成这样:

{
    // 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'); }
}

这是否有可能实现,如果可以,怎样实现?
如果你想了解的话,我需要这个是因为我正在使用一个框架,它只接受对象作为输入,而我正在尝试使用TypeScript和类继承。
此外,我只需要进行一次上述转换,因此性能不是要考虑的问题。
通过迭代对象属性来解决将无法在编译器的目标选项设置为es6时工作。在es5上,通过迭代对象属性(使用Object.keys(instance))的现有实现将起作用。
到目前为止,我有这个实现:
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;
}

但是这仍然没有起作用。生成的对象只包括所有类的属性,但仅包括 PersonWorker 的方法。我在这里缺少了什么?

为什么你不能传递实例?你基本上只是复制它。 - Louay Alakkad
2
toJSON() {return Object.keys(this).reduce((obj, key) => {obj[key] = this[key]; return obj;}, {});} - Louay Alakkad
@Louy 当通过babel或node 4+来针对es6进行目标设置时,使用Object.keys的旧方法将无法工作。 - eAbi
4
准确地说,这不是将内容转换为JSON格式,而是将其转换为对象。JSON显式地是一个对象表示法。 - Dave Newton
1
针对 @DaveNewton 的评论,JSON 是一种文本格式,因此不存在所谓的 JSON 对象,因为对象属于 JavaScript 领域,而不是 JSON。请注意,toJSON 也是一个特殊的方法名称,如果存在,将由 JSON.stringify 使用以获得对象的字符串表示形式。 - Heretic Monkey
显示剩余2条评论
7个回答

33

已经有很多答案了,但这是最简单的方法,它使用展开语法解构赋值对象:

const {...object} = classInstance

1
最佳答案!<3 - Damien Romito
12
不保留方法。 - Aleksandar Bencun
2
@AleksandarBencun 它实际上确实保留了方法,但是构造函数被覆盖了,因为当使用{...SomeClass}将类展开到新对象时,新构造函数将是JS对象的本机Object构造函数。 - Niv
1
这并没有回答问题;OP明确表示:不丢失方法和/或继承属性... - Yahya Abou Imran
这不保留方法。 - Mohamed

13

这是对我有效的方法

更新的答案(包含递归)

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

原始答案

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


1
那个答案太棒了,我不明白为什么它被忽略了。干得好,谢谢分享! - servatj
1
这个答案看起来很简洁明了。但是,我的问题要求嵌套类也要转换成普通的javascript对象(保留方法)。我认为你需要递归地调用你的函数来解决这个特定的场景。当你编辑你的解决方案以涵盖嵌套的情况后,我会将你的答案标记为已接受的。谢谢! :) - eAbi
@alex-cory 如果您解决了我之前评论中提到的问题,请通知我更改已接受的答案!谢谢! - eAbi
1
@eAbi 我更新了代码,现在支持递归。 - Alex Cory

9

好的,我之前的实现是错误的,而且错误非常愚蠢。

使用es6时,正确的实现方式是:

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

这是唯一一个与继承一起使用的可行解决方案。 - Yahya Abou Imran

2
这里是toJSON()方法的实现。我们将当前实例的属性和方法复制到一个新对象中,并排除不需要的方法,即toJSON和constructor。
toJSON() {
    var jsonedObject = {};
    for (var x in this) {

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

我已经在Chrome中测试了toJSON()返回的对象,发现该对象的行为与您期望的一样。


不行,不起作用。我有一个类层次结构,所以它不会起作用。 - eAbi
2
它可以工作。我已经测试了对Human、Person和PersonWorker调用toJSON的情况。它按预期工作。 - Seshu Kumar Alluvada
它在那个例子上运行得很好。但在我的用例中仍然无法正常工作。不明白问题出在哪里 :| - eAbi
让我尝试一些不同的东西。 - eAbi
似乎问题出现在目标为es6时。我已经更新了我的问题。 - eAbi
1
这在es6中不起作用。然而,eAbi提供的解决方案可以。 - Darkman

2

这个解决方案会丢失方法,但它是将类实例转换为对象的非常简单的解决方案。

obj = JSON.parse(JSON.stringify(classInstance))

这在理论上是慢的,因为它需要进行两次转换。 - Kazuya Gosho

2

我在很多方面上都参考了Alex Cory的解决方案,但这是我想出来的。它期望被分配到一个类作为一个函数,并在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;
  }, {})
}

如果您正在使用TypeScript,则可以将此接口放在任何应转换为对象的类上:

export interface ToObject {
  toObject: Function;
}

然后在你的类中,不要忘记绑定this

class TestClass implements ToObject {
   toObject = toObject.bind(this);
}

0

使用 Lodash

该方法不是递归的。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接