0

I'm new to JavaScript so bear with me if what I'm asking is not "how you do it in JavaScript". Advice on other approaches are welcome.

I have a class named State and I need need to serialize objects of that class using JSON.stringify(). The next step is to deserialize them back into an objects. However, my class uses setters and getters.

The problem that I'm facing is that after I deserialized those objects the setters and getters seem to be gone. I just cannot figure out how I can properly turn serialized objects back into objects of that class so that they behave exactly the same as objects that are created using new directly.

In another language I would cast those objects into State objects. I cannot find a JavaScript mechanism that seems to work that way.

The code looks as follows:

class State {
  constructor(href) {
    this.url = href;
  }

  set url(href) {
    this._url      = new URL(href);
    this.demoParam = this._url.searchParams.get("demoParam");
  }

  get url() {
    return this._url;
  }

  set demoParam(value) {
    let param = parseInt(value, 10);
    if(isNaN(param)) {
      param = 2;
    }
    console.log("Setting 'demoParam' to value " + param);
    this._demoParam = param;
  }

  get demoParam() {
    return this._demoParam;
  }

  toJSON() {
    let stateObject  = {};
    const prototypes = Object.getPrototypeOf(this);
    for(const key of Object.getOwnPropertyNames(prototypes)) {
      const descriptor = Object.getOwnPropertyDescriptor(prototypes, key);
      if(descriptor && typeof descriptor.get === 'function') {
        stateObject[key] = this[key];
      }
    }
    return stateObject;
  }
}

let originalState = new State(window.location.href);

let newState1 = JSON.parse(JSON.stringify(originalState));
newState1.demoParam = 12;

let newState2 = Object.create(State.prototype, Object.getOwnPropertyDescriptors(JSON.parse(JSON.stringify(originalState))));
newState2.demoParam = 13;

let newState3 = Object.assign(new State(window.location.href), JSON.parse(JSON.stringify(originalState)));
newState3.demoParam = 14;

let newState4 = Object.setPrototypeOf(JSON.parse(JSON.stringify(originalState)), State.prototype);
newState4.demoParam = 15;

I would expect that everytime I set the demoParam property of a newStateX object I'd see a console log message. However. I only see it twice, i.e. for every new State(window.location.href) statement.

I have used the answer of this question. However, it does not work as expected.

ackh
  • 1,648
  • 2
  • 18
  • 36

2 Answers2

0

when you serialize an object you trigger the toString or the toJSON method of your class' instance and end up with just a "dumb" JSON representation of your enumerable attributes.

if you want to recreate an instance that behaves like it did prior to serialisation, you will need to set an extra key/value pair in your toJSON function like ___internalType: 'state' and then later use eg. a switch statement to recreate your specific class with the new MyClass(serializedData)and passing in your serialised instance. Within the constructor of your class, you set all the attributes you need and voilà, you have your "old" instance again

/edit: to clarify the reason why your console logs aren't showing up is because you are not recreating an instance of your class but just creating a new plain Object.

japrescott
  • 4,736
  • 3
  • 25
  • 37
  • Hmm, I guess I was hoping for a mechanism I hadn't discovered yet. From what I've seen so far, JavaScript classes seem to be a questionable and unfinished concept. There is no proper information hiding, inheritance works completely different compared to other languages and my use case isn't covered either. Seems to be a better option to ditch classes altogether for what I'm trying to achieve. – ackh Feb 10 '20 at 15:47
  • yes, inheritance works via the protoype chain which is different than java etc. But also in other languages you need to hydrate your object somehow after you serialized it to a JSON/protobuf/string. information hiding (àla private variables) will be achievable in the future with [private fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Class_fields#Private_fields) but you can achieve it now by using closures – japrescott Feb 10 '20 at 15:55
0

You can use Object.assign to copy plain object data into an "empty" new instance of the class along the lines of this code:

function cast(o) {
   if (!o._cls) return o;
   var _cls = eval(o._cls);
   return Object.assign(new _cls(), o);
}

In JavaScript i personally like to avoid using classes for my data objects. TypeScript offers some better opportunities to solve this problem, one of these is TypedJSON:

https://github.com/JohnWeisz/TypedJSON

ZPiDER
  • 4,264
  • 1
  • 17
  • 17
  • Isn't the result of that the same that I cover with `let newState3 = Object(assign new State(...` above already? – ackh Feb 10 '20 at 16:33