46

I need to implement small ODM like feature. I get plain javascript object from database, and I need to convert it into my model class instance. Let's assume model looks like:

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

So I need convert var a = {b:999, c:666} to instance of model and being able to call a.print() after, and when a.print() executed 777 should be placed in console. How to do that?

6
  • 1
    How could {b:999, c:666} become a Model instance? Your Models only have an a property, not b or c ones. Maybe that's why people don't understand your question. Commented Nov 18, 2015 at 12:04
  • @Bergi it could be dozens of fields in objects and all of them should not be listed in constructor i think. Commented Nov 18, 2015 at 12:07
  • @silent_coder: Of course all your fields should be listed in the constructor? An instance wouldn't have those fields if they weren't created. Commented Nov 18, 2015 at 12:08
  • @Bergi It's javascript mate. You could type this.b = xxx in any method and it will be perfectly valid. Commented Nov 18, 2015 at 12:11
  • This looks like a duplicate of Casting plain objects to function instances (“classes”) in javascript to me (nothing is different in ES6). Please tell me whether that helps. Commented Nov 18, 2015 at 12:13

8 Answers 8

51

There have a simple method. Just assign the object to instance(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'

Sign up to request clarification or add additional context in comments.

Comments

8

If I understand the question correctly, you can export a factory function and make use of Object.assign to extend your base 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)
  }
}

And call it like:

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

Babel REPL Example

Or, of course, you could forego the factory and pass a in as a parameter (or rest parameters) to the constructor but it depends on your preferred coding style.

4 Comments

I would recommend class Model { static create(a) { … } … }, and maybe a more descriptive name like createFrom or fromJSON
What will be in the case if a.a intially contains different value. Let's say '-500' ? I think that some data will be losted. Or which was in plain object, or which was set in constructor. And how does this case will work if model with time will be extended with properties and read only getters with the same name as fields in plain object?
@Ph0en1x None of that is specified in the question. Either way, it would be trivial to transform the source object in Object.assign to exclude keys.
@Ph0en1x: Just try it out? The last value will overwrite the previous. And extending instances later (after construction) is an antipattern. Read-only getters will of course cause issues, you have to adapt your createModel method to work with them explicitly.
4

If you need to typecast more consistently, you can also create your own typecast function like generic function

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

call it to typecast any object to any class instance like

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

Comments

3

I would suggest rewriting your class to store all its properties in a single JS object this.props and accept this object in its constructor:

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

Then you'll be able to store this.props in your database as a plain JS object and then use it to easily recreate corresponding class instance:

new Model(propsFromDatabase)

Though, if you don't want to move all properties to this.props, you could use Object.assign to keep your object plain:

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

But I would recommend using the former approach, because it'll keep you safe from name collisions.

10 Comments

This doesn't default each property separately, but the whole props object only.
@Bergi yes, it's what you would expect from ODM - either read everything from DB, or create from a scratch.
Also, new Model({b:999, c:666}).print() // a === undefined
@RGraham yes, because it's in this.props.a. But good point, anyway. Added alternative approach.
This approach have serious disadvantage. If you need to use your property later, like (new Model(a)).b you will need to implement custom getter for any property. That's a big amount of work.
|
2

Edit 2024: I think my original idea is still good for this specific use case but in general the following (written in TypeScript) might be better:

class Model {
  a = 11;
  b = 22;
  c = 33;
  constructor(values: Record<"a" | "b" | "c", number> = {}) {
    for (key in values) {
      // to omit extra keys
      if (!(key in this)) continue;
      // to prevent injecting code
      if (typeof this[key] === "function") continue;
      this[key] = values[key];
    }
  }
  print() {
    console.log(this.a)
  }
}

Unfortunately I haven’t tested this so there might be a type error in the for..in loop, but I don’t think there is. In any case it’s not like my original solution would work in TypeScript :)

Original answer:


You could have a static Model.from or Model.parse method, that returns a new Model with those properties:

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) {
    return Object.assign(
      new Model(),
      Model.defaults,
      Object.fromEntries(
        Object.entries(data).filter(([key]) => key in Model.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();

Comments

1

How about this?:

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

You'd be basically creating a new instance of Model from it's prototype and assigning your new properties to it. You should be able to call print as well.

2 Comments

I need to write automated code to convert pojso into model instances. I couldn't fill properties manually.
Yes, the properties can also be modified with enumerable, writable, configurable and get and setlike this: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
0

Just like G_hi3's answer, but it "automates" the creation of the properties object

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

3 Comments

This still lacks a call to the constructor, though.
@Bergi I intentionally didn't call the constructor because it would override the data passed in this.a = '777';.
You could (should) call it before you pass in the data, I meant. If you don't call it, you might lack initialisation of your instance.
0

First declare a class in which you want to convert JSON:

class LoginResponse {
  constructor(obj) {
    Object.assign(this, obj);
  }
  access_token;
  token_type;
  expires_in;
}

Now convert the general javascript object into your desired class object:

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

Output will be:

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.