I have been coding in javascript for some time, but am fairly new to Node. I recently undertook a project that involves a complex object structure with multiple levels of prototypical inheritance and sub objects. This structure needs to be periodically saved / loaded. Saving and loading in JSON is desirable.
The Question
Is there a more elegant way of accomplishing the task of saving/loading these complex Javascript objects than my current method (outlined below)? Is it possible to design it in such a way where the constructors can initialize themselves as if they were normal objects without being bound by all of the restoring functionality?
My Solution
The base 'class' (from which, by design, all other objects under consideration inherit protoypically) has a function which processes an 'options' argument, adding all of it's properties to the current object. All deriving objects must include an options argument as the last argument and call the processing function in their constructor.
Each object also must add it's function name to a specific property so that the correct constructor function can be called when the object needs to be rebuilt.
An unpack function takes the saved object JSON, creates a plain object with JSON.parse and then passes that object in as the 'options' argument to the object's constructor.
Each object is given a unique id and stored in a lookup table, so that a function under construction with links to other objects can point to the right ones, or create them if it needs to.
Here is a plunker which demonstrates the idea (obviously in a non-Node way).
If you don't want to load the plunker, here's an excerpt which should hopefully provide the gist of what I'm trying to do:
function BaseClass(name, locale, options){
if(name) this.name = name;
if(locale) this.locale = locale;
// If options are defined, apply them
this.processOptions(options);
// create the classList array which keeps track of
// the object's prototype chain
this._classList = [arguments.callee.name];
// Create a unique id for the object and add it to
// the lookup table
if(!this.id) this.id = numEntities++;
lookupTable[this.id] = this;
if(!this.relations) this.relations = [];
// other initialization stuff
}
BaseClass.prototype = {
processOptions: function(options) {
if(options && !options._processed){
for(var key in options){
if(options.hasOwnProperty(key)){
this[key] = options[key];
}
}
options._processed = true;
}
},
addNewRelation: function(otherObj){
this.relations.push(otherObj.id);
}
// Other functions and such for the base object
}
function DerivedClassA(name, locale, age, options){
if(age) this.age = age;
this.processOptions(options);
if(options && options.connectedObj){
// Get the sub object if it already exists
if(lookupTable[options.subObj.id]){
this.subObj = lookupTable[options.subObj.id];
}
// Otherwise, create it from the options
else {
this.subObj = new OtherDerivedClass(options.subObj);
}
}
else {
// If no options then construct as normal
this.subObj = new OtherDerivedClass();
}
// If something needs to be done before calling the super
// constructor, It's done here.
BaseClass.call(this, name, locale, options);
this._classList.push(arguments.callee.name);
}
DerivedClassA.prototype = Object.create(BaseClass.prototype);
As mentioned, this gets the job done, but I can't help but feeling like this could be much better. It seems to impose a ridiculous amount of restrictions on the inheriting 'classes' and how their constructors must behave. It makes a specific order of execution critical, and requires that each object be deeply involved and aware of the restoration process, which is far from ideal.
Related
Don't do it
This question has some comments with a low opinion of the very notion of reconstructing the objects. The commenters either couldn't or wouldn't explain why they thought it was a bad idea, but since asking I have come the to same conclusion. Here's why.
If you think about MVVM, the purpose of having a model and a view-model is to separate behaviour from data. This is kind of funny, because the point of object-orientation is to combine them. But in a distributed world, the data has to be shipped around. If your code and data are all munged together then you have to either invent MVVM or keep de- and re-constructing objects.
The code to de- and re-construct objects is a testing and maintenance time-sink you don't need, and introduces two failure modes. Don't do it. Have a method-less class to hold the state and a stateless class that operates on the method-less class. This is the essence of MVVM, and really nothing more than application of Memento pattern.
Memento (283)
Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
Design Patterns, Gamma et al, 1995
Original question
The data of my view models is passed back and forth between client JS and server Web APIs as JSON.
It is well understood that JSON.stringify(object) serialises only members that have a non-null value that is not a Function. Thus, JSON.parse(JSON.stringify(someObject)) will remove all the methods from the object.
My current implementation has each graph node implemented as a Typescript class with Serialise and Deserialise methods. JQuery.ajax calls a Web API and implicitly parses the resultant JSON into a DAG of object definitions, each of which has a Type property indicating which type of class it was prior to serialisation. I have a map of constructors indexed by name and the appropriate constructor is retrieved and the data passed as the constructor parameter.
Depending on type there may be children; if so things proceed recursively down the graph.
I have been wondering whether, rather than copy all the property values, I couldn't just assign an appropriate prototype. Bring the mountain to Mahomed, you might say. This would eliminate quite a bit of clutter in my codebase.
As I write it occurs to me that I could use $.extend, but I'm progressively weeding jQuery out of my codebase so this would be a retrograde step.
Is there any known peril in my proposition of diddling the prototype?
Does anyone have a better idea? Other than $.extend, I mean. Something TypeScripty, by preference.
It has been observed in comments that assigning the prototype means the constructor is never called. This is irrelevant. The object state is already set up, all that is required is to make the methods available.
I recently built object with methods which content could be serialized and then reconstructed.
I simply added an argument which could take a JSON object and assign it to itself.
Example using plain object:
function myObject() {
this.valueA = 1;
this.valueB = 2;
this.valueC = 3;
this.add = function() {
return this.valueA + this.valueB + this.valueC;
};
}
var o = new myObject();
console.log(o.add());
console.log(JSON.stringify(o));
If you serialized this you would get:
{"valueA":1,"valueB":2,"valueC":3}
Now, to reconstruct this you can add a Object.assign() to the object like this taking the argument and merge it with self:
function myObject(json) {
this.valueA = 0;
this.valueB = 0;
this.valueC = 0;
this.add = function() {
return this.value1 + this.value2 + this.value3;
};
Object.assign(this, json); // will merge argument with itself
}
If we now pass the parsed JSON object as argument it will merge itself with the object recreating what you had:
var json = JSON.parse('{"valueA":1,"valueB":2,"valueC":3}')
function myObject(json) {
this.valueA = 0;
this.valueB = 0;
this.valueC = 0;
this.add = function() {
return this.valueA + this.valueB + this.valueC;
};
Object.assign(this, json); // will merge argument with itself
}
var o = new myObject(json); // reconstruct using original data
console.log(o.add());
If you now have children via array you simply repeat the process recursively down the chain.
(A bonus is that you can also pass options this way).
I am probably not the first one doing this, but I can't find or figure out how to do it.
In my javascript I have an object with 5 properties, which are all object withg data properties and functions.
So it looks like this
function Maindata() {
var obj = this;
this.id = null;
this.profile= new Profile();
this.company= new Company();
//and more
this.getid = function(){
return obj.id;
};
}
and the profile and company object also has data and functions.
Now I get an object from the server, which is the same object, but only the data, and not the functions.
Now I want to project all of the properties of the retrieved object from the server to my JavaScript object.
I've read to use jQuery Extend, but that doesn't work for me, because it only preserves the functions on the root (Maindata) object, but not on the sub-objects.
How can I load all the data from the subobjects too?
Do I have to $extend the subobjects one by one, like this:
$.extend(true, maindata.profile, result.profile);
$.extend(true, maindata.company, result.company);
or is there another solution?
First of all, in general - you want to put all functionality on the prototype and all data on the object itself:
function Maindata() {
var obj = this;
this.id = null;
this.profile= new Profile();
this.company= new Company();
//and more
}
Maindata.prototype.getid = function() {
return this.id;
};
The prototype is assigned to every object created with Maindata as a constructor and it is shared across all Maindata instances. So it is likely more efficient and easier to reason about. It's kind of like classes in other languages.
This means you can do:
// creates an object with the given prototype without running the constructor
var o = Object.create(Maindata.prototype);
$.extend(o, result); // copy data over
// Object.assign(o, result); // with ES2015 assign syntax.
If your object is nested - then you need to do this in a nested way for subproperties. You can't get types for free since they are not encoded in the JSON. You can implement a custom serializer that does this automatically for you - some libraries like Backbone do this for you but IMO they do a pretty bad job and doing this yourself is pretty straightforward.
I've defined an enumerable property in the prototype object and would like it to appear when I convert a prototyped object to JSON.
My first idea was to set it in toJSON but because I don't really want to keep it in the object afterwards I'll have to more or less clone the whole object in the function and set the necessary property.
Redefining the property in the target object and just proxying with the context of the current object doesn't seem to be an option as well, since I can't really use apply or call when getting dynamic properties.
Working solutions I could come up with so far seem to require quite an amount of code and aren't flexible and concise enough, so I'm wondering if there are any best practices of solving this task.
Here is an example which could seem a bit synthetic but still, I believe, conveys the idea:
function ProjectFolder() {
this.files = [];
Object.defineProperty(this, 'size', {enumerable: true, get: function() {
return this.files.length;
}});
}
function GithubProjectFolder() {
this.files = ['.gitignore', 'README.md'];
}
GithubProjectFolder.prototype = new ProjectFolder();
var project1 = new ProjectFolder();
JSON.stringify(project1);
// output: {"files":[],"size":0}
// size is present
var project = new GithubProjectFolder();
JSON.stringify(project);
// output: {"files":[".gitignore","README.md"]}
// size is absent
I'll have to more or less clone the whole object in the function and set the necessary property.
Yes, and there's nothing wrong with that. That's how .toJSON is supposed to work:
ProjectFolder.prototype.toJSON = function toJSON() {
var obj = {};
for (var p in this) // all enumerable properties, including inherited ones
obj[p] = this[p];
return obj;
};
However, there are two other points I'd like to make:
The size of a folder doesn't really need to be stored separately in the JSON when it already is encoded in the length of the files array. This redundant data seems to be superfluous, and can confuse deserialisation. Unless something requires this property to be present, I'd recommend to simply omit it.
In ProjectFolders, the .size is an own property of each instance - in GithubProjectFolders it is not. This suggest that you're doing inheritance wrong. Better:
function GithubProjectFolder() {
ProjectFolder.call(this);
this.files.puhs('.gitignore', 'README.md');
}
GithubProjectFolder.prototype = Object.create(ProjectFolder.prototype);
If you'd fix that alone, the size will appear in the serialisation of your project.
I am serializing and storing an object that was created from a WinJS.Class like this:
var myClass = WinJS.Class.define(...);
var myObject = new myClass();
var serialized = JSON.stringify(myObject);
//store the object
And later I'm pulling the object out of storage and I want to deserialize it and cast it as a myClass. Is that possible with WinJS out of the box or do I need to create a constructor for my class that is capable of taking an object that can turn it into a new object?
I haven't broken into TypeScript yet, and I think that would help out in this situation, but until then I'm wondering how to do it with plain JavaScript/WinJS.
There are a few ways to handle this, and none are particularly special to WinJS. Simply put: JSON serialization only serializes and deserializes the obje values, not its methods, prototype, or other type information.
Option 1: Copy values to new instance of your class
This is usually best accomplished by having your constructor take the deserialized object as a parameter and copying the data to the new instance.
There are a variety of variations of this. Using the object constructor is generally the best for performance, as this typically enables the JS engine to apply the greater number of optimizations to the object.
WinJS.UI.setOptions can be helpful here, or you can just copy the data using a simple loop like this:
var keys = Object.keys(source);
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
destination[key] = source[key];
}
Option 2: Setting __proto__
Warning: This can have significantly adverse performance effects, so it's not appropriate in some situations. But occasionally it can be handy.
Object.setPrototypeOf(myObject, myClass.prototype);
Note that setPrototypeOf is relatively new. It's there on Win8.1 for web apps (which I'm guessing this is about) and in IE 11, but not available in Safari, for example. On older browsers/ Safari, assigning to proto is the equivalent (but if available, setPrototypeOf is better).
This will attach methods from myClass to the object, but in addition to the negative performance effects, also does not run your constructor on the object - so it still may not be in exactly the same state as the object you originally serialized.
Other helpful thing: JSON "revivers"
JSON.parse takes an optional second parameter, called a "reviver". This lets you provide a function that gets the opportunity to transform each node of the JSON being deserialized. This can be useful for rehydrating serialized dates into JavaScript Date objects, for example. It also gets the opportunity to transform the top-most object, which could be useful in some cases to turn the deserialized object into the "class" you want.
Javascript is a dynamic language so I think you dont need to cast the deserialized object, just treat it as myClass type and that's it. Hope it helps you.
You should consider using the 'Options' constructor pattern, where the option value is the deserialized object:
// MovieModel Constructor
// ----------------------
function MovieModel(options) {
this._titleValue = options.title || "Sample Title";
}
Where the movie methods closure is something like this:
// MovieModel Methods
// ------------------
var movieModelMethods = {
title: {
get: function () {
return this._titleValue;
},
set: function (val) {
this._titleValue = val;
this.dispatchEvent("title");
}
}
};
Since WinJS class define can only specify one constructor function (as far as I understand it), you may use the static members to define a factory function that will take the serialized data as a parameter. This factory methdod will actually create a new instance and will set the values one by one and return the new object.
It as some advantages like the fact that you can actually manage the data structure changes over the time you enhance the app...
The drawback is that you cannot write new MySuperClass() all the time...
...
// let's suppose we already called JSON.parse(data);
create: function(serializedData) {
var newObj = new MySuperClass();
newObj.name = serializedData.name || "";
newObj.color = serializedData.color || "";
return newObj;
}
Then you will call somewhere else in the app :
var myInstance = MySuperClass.create(serializedDataFromfile);
You should just be able to call JSON.parse after pulling it out of local storage:
var myObject2;
myObject2 = JSON.parse(localStorage["mySeriazliedObject"];
I've been looking into HTML 5's new local storage and it seems pretty cool so far. I've decided to use JSON to serialize my objects into strings and to parse them back into objects, which all sounds very nice. However, it's easier said than done. I've discovered that you can't just JSON.stringify() an object and expect it to pack nicely for you, but I can't figure out what I have to do instead.
That's not all, though: my object contains two arrays, each of which holds another type of object and one of which is multidimensional. Here's what my rather complex and inter-dependent object architecture looks like:
function Vector2(x, y) {
this.x = x;
this.y = y;
}
function Bar(ID, position) {
this.id = id;
this.position = position;
}
function Goo(state, position) {
this.on = state;
this.position = position;
}
function Foo(name, size) {
this.name = name;
this.size = size;
this.bars = new Array(width)
this.goos = new Array(10);
this.Initialize();
}
Foo.prototype.Initialize() {
for(var x = 0;x<this.size.x;x++) {
this.bars[x] = new Array(this.size.y);
for(var y=0;y<this.size.y;y++) {
this.bars[x][y] = new Bar(x + y, new Vector2(x, y));
}
}
for(var i = 0;i<this.goos.length;i++) {
this.goos[i] = new Goo(on, new Vector2(i, i/2 + 1));
}
}
Each of those objects has plenty of additional functions as well, each added using the same prototype method that I used to add the method to Foo. Like I said, complex. My question is, how do I serialize all this? Do I really need to tack on toJSON() functions to every object?
Finally, once I've packed all this and saved it to localstorage, I know how to retrieve it, but I'm pretty much clueless on how to unpack it with JSON. That's another matter for another time, though, and I suspect it might be a bit easier to figure out on my own once I learn how to pack everything up.
Note: I wouldn't normally such a potentially broad question, but I couldn't really find anything here on SO or with my (admittedly weak) Google-fu that really addresses the issue, and I don't know how to break this question down any further.
Usually, you don't just serialize complex data structures in javascript because the normal serialization doesn't handle multiple difference things all have references to the same object, can't handle circular references, etc...
What I would recommend instead is that you figure out what the real state of your application is. Not the whole instantiated object structure, but what is the minimum amount of information that is actually needed to reconstruct the state of your data. Then, once you've figure that out (it should only be data, no actual objects), then you can create functions or methods to get that data from your data structures or create a new data structure from the data.
In looking at your code, the actual state of a Foo object is a two dimensional array of Bar objects and a one dimensional array of Goo objects and a name and a size. A Bar just has an x, y and id. A Goo just has a state and an x and a y. That would be pretty easy state to write a Foo method to generate and a Foo method to accept that state from saved storage.
Tacking on toJSON and fromJSON functions is probably the right way to do it. You should only be saving the actual unique data for any given object as JSON, it would not be a good idea to serialize the entire instantiated object for several big reasons, #1 being that it's just unnecessary. Also, think of the case where you add or modify functions for one of your objects in the near future: clients who had stored their own serialized objects, will never get your updates. Simply storing the unique instance data, and having functions to convert that data back to a real object (using your most recent definition) is the way to go.
You can pack functions to strings:
var a = function() {return "a"};
a.toString()
// And also:
function b() {return "b"};
b.toString();
So from there on you could hack the JSON source (by Douglas Crockford) and include support for functions. Or something.
I hope I'm not too late here but I just encountered the same problem.
I wanted to persist a promise object (returned from a JQuery AJAX call) to local storage.
Luckily I stumbled upon a little Javascript library called Stash (http://rezitech.github.io/stash/). It makes both the serialization of complex Javascript objects to Strings and the parsing of a String to a Javascript object possible.
The best thing, you don't have to explicitly perform the serialization/parsing because the data types are automatically recognized and transformed when you want to persist/retrieve an object to/from local storage.
Here is a quick test which yielded a positive result
// the api endpoint
var url = "www.api-host.com/api/bla/blub";
// create json call returning promise object
var promise = $.getJSON(url);
// persist promise object in local storage using dash.js
var key = url;
stash.set(key, promise);
// retrieve promise object from local storage
var stashGet = stash.get(key);
// display in console
console.log(stashGet);
Regards, Matthias