将JavaScript普通对象转换为模型类实例

41

我需要实现一个类似ODM的小功能。我从数据库得到普通的JavaScript对象,我需要将其转换为我的模型类实例。假设模型长这样:

    class Model{
       constructor(){
           this.a = '777';
           ---- whole bunch of other things ---
       }
       print(){
           console.log(this.a);
       }
   }

我需要将var a = {b:999, c:666}转换为模型实例,并且能够在之后调用a.print(),当a.print()执行时,在控制台中应该放置777。如何做到这一点?


1
{b:999, c:666}怎么能成为一个Model实例呢?你的Model只有一个a属性,没有bc属性。也许这就是人们不理解你问题的原因。 - Bergi
@Bergi,对象中可能有几十个字段,而且不应该在构造函数中列出所有这些字段。 - silent_coder
@slient_coder:当然,你所有的字段都应该在构造函数中列出。如果没有被创建,实例就不会有这些字段。 - Bergi
@Bergi 这是 JavaScript,伙计。你可以在任何方法中键入 this.b = xxx,它将完全有效。 - silent_coder
这对我来说看起来像是Casting plain objects to function instances (“classes”) in javascript的一个副本(在ES6中没有任何不同)。 请告诉我这是否有所帮助。 - Bergi
我很惊讶这个问题在大约三年时间里得到了如此少的关注。 - DarkNeuron
8个回答

44

有一个简单的方法。只需将对象分配给实例(this)

class Model
{
  constructor(obj){
    Object.assign(this, obj)
  }
  print(){
    console.log(this.a);
  }
}

let obj = {a: 'a', b: 'b', c: 'c'}
    
let m = new Model(obj)
console.log(m)
m.print()  // 'a'


8
如果我理解问题正确,您可以导出一个工厂函数并利用Object.assign来扩展您的基础Model
// Export the factory function for creating Model instances
export default const createModel = function createModel(a) {
  const model = new Model();
  return Object.assign(model, a);
};
// Define your base class
class Model {
  constructor() {
    this.a = 777;
  }
  print() {
    console.log(this.a, this.b, this.c)
  }
}

并像这样调用:

const myModel = createModel({ b: 999, c: 666 });
myModel.print();

Babel REPL示例

或者,当然,您可以放弃工厂并将a作为参数(或rest参数)传递给构造函数,但这取决于您喜欢的编码风格。

3
我建议使用 class Model { static create(a) { … } … },并且可能需要一个更具描述性的名称,例如 createFromfromJSON - Bergi
1
如果a.a最初包含不同的值,情况会怎样呢?比如说“-500”?我认为有些数据将会丢失。或者是在普通对象中设置的数据,或者是在构造函数中设置的数据。如果带有时间的模型被扩展了属性和只读getter,并且这些属性与普通对象中的字段名称相同,那么这种情况将如何处理? - Ph0en1x
@Ph0en1x,问题中没有指定任何这方面的信息。无论如何,在Object.assign中转换source对象以排除键都是很简单的。 - CodingIntrigue
1
@Ph0en1x:试试看?最后一个值会覆盖之前的值。稍后扩展实例(在构建之后)是反模式。只读的getter方法肯定会引起问题,你必须明确适应你的createModel方法来解决它们。 - Bergi

3
我建议重写你的类,将所有属性存储在一个名为this.props的JS对象中,并在其构造函数中接受此对象:
class Model {
  constructor (props = this.initProps()) {
    this.props = props
    // other stuff
  }
  initProps () {
    return {a: '777'}
  }
  print () {
    console.log(this.props.a)
  }
}

然后,您就可以将this.props存储为普通的JS对象,并使用它轻松地重新创建相应的类实例:

new Model(propsFromDatabase)

如果您不想将所有属性移动到this.props,您可以使用Object.assign使对象保持简单:

class Model {
  constructor (props = this.initProps()) {
    Object.assign(this, props)
    // other stuff
  }
  initProps () {
    return {a: '777'}
  }
  print () {
    console.log(this.a)
  }
}

但我建议使用前一种方法,因为它可以避免名称冲突。


这不会将每个属性单独还原为默认值,而是只还原整个 props 对象。 - Bergi
@Bergi 是的,这是你从ODM所期待的 - 要么从数据库读取所有内容,要么从头开始创建。 - Leonid Beschastny
2
这种方法有严重的缺点。如果您以后需要使用您的属性,例如(new Model(a)).b,则需要为任何属性实现自定义getter。那是一项巨大的工作量。 - Ph0en1x
@RGraham 我现在明白了。嗯,在大多数情况下,我会期望这种行为。如果在保存的对象中不存在,为什么它应该存在于加载后的对象中呢? - Leonid Beschastny
@LeonidBeschastny 确实。可能是默认值。谁知道呢。如果有这么多不同的答案和未知因素,显然这是一个糟糕的问题。 - CodingIntrigue
显示剩余5条评论

3
如果您需要更加一致地进行类型转换,您也可以像创建通用函数一样创建自己的typecast函数。
function typecast(Class, obj) {
  let t = new Class()
  return Object.assign(t,obj)
}

// arbitrary class
class Person {
 constructor(name,age) {
   this.name = name
   this.age = age
 }
 print() {
   console.log(this.name,this.age)
 }
}

使用它将任何对象强制转换为任何类实例

let person = typecast(Person,{name:'Something',age:20})
person.print() // Something 20

1
这个怎么样?
var a = Object.create(Model.prototype, {
    b: {
        enumerable: true, // makes it visible for Object.keys()
        writable: true, // makes the property writable
        value: 999
    }, c: {
        value: 666
    }
});

你需要基本上从模型的原型中创建一个新的实例,并将你的新属性分配给它。你应该能够调用print

我需要编写自动化代码将POJO转换为模型实例。我无法手动填充属性。 - silent_coder
是的,属性也可以通过enumerablewritableconfigurableget以及set进行修改,就像这样:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty - G_hi3

1
你可以拥有一个静态的Model.from或者Model.parse方法,返回一个带有这些属性的新的Model:

class Model {
  static defaults = { a: 777, b: 888, c: 999, d: 111, e: 222 };
  constructor() {
    const { defaults } = Model;
    for (const key in defaults) this[key] = defaults[key];
  }
  print() {
    console.log(this.a);
  }
  static from(data) {
    const { defaults } = Model;
    return Object.assign(
      new Model(),
      defaults,
      Object.fromEntries(
        Object.entries(data).filter(([key]) => key in defaults)
      )
    );
  }
}

const data = {
  a: "a", b: "b", c: "c", ajkls: "this wont be included"
};
const myModel = Model.from(data);
console.log("myModel =", myModel);
console.log("myModel instanceof Model:", myModel instanceof Model);
console.log("myModel.print():")
myModel.print();


0
首先在你想要转换JSON的代码中声明一个类:
class LoginResponse {
  constructor(obj) {
    Object.assign(this, obj);
  }
  access_token;
  token_type;
  expires_in;
}

现在将通用的 JavaScript 对象转换为您所需的类对象:

const obj = {
  access_token: 'This is access token1',
  token_type: 'Bearer1',
  expires_in: 123,
};
  let desiredObject = new LoginResponse(obj);
  console.log(desiredObject);

输出结果将为:

 LOG  {"access_token": "This is access token1", "expires_in": 123, "token_type": "Bearer1"}

0

就像G_hi3的回答一样,但它“自动化”了属性对象的创建

function Model() {
  this.a = '777';
}

Model.prototype.print = function(){
    console.log(this.a);
}

   // Customize this if you don't want the default settings on the properties object.
function makePropertiesObj(obj) {
    return Object.keys(obj).reduce(function(propertiesObj, currentKey){
        propertiesObj[currentKey] = {value: obj[currentKey]};
        return propertiesObj;
    }, {}); // The object passed in is the propertiesObj in the callback
}

var data = {a: '888'};

var modelInstance = Object.create(Model.prototype, makePropertiesObj(data));
// If you have some non trivial initialization, you would need to call the constructor. 
Model.call(modelInstance);
modelInstance.print(); // 888


这里仍然缺少对构造函数的调用。 - Bergi
1
@Bergi 我故意没有调用构造函数,因为这会覆盖传入的数据 this.a = '777'; - Ruan Mendes
你应该在传入数据之前调用它,这是我的意思。如果你不调用它,你的实例可能会缺乏初始化。 - Bergi

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