I serialized my canvas using the canvas.toJSON() method. But trying to recreate my canvas, results in the objects being redrawn, but being 100% unresponsive (cannot drag around).
I tried to be clever and tried redrawing the objects 1 by 1 by extracting them from the JSON (pseudocode of what I am doing below).
forEach(jsonRepresentation.objects, function (obj) {
canvas.add(obj);
}
canvas.renderAll();
This also does not work and leads the objects to NOT draw but clicking on the CANVAS results in the error:
TypeError: Object #<Object> has no method 'setupState'
Any idea of what could be happening?
The application is very complex, so I am not sure a fiddle/plunkr could be possible.
The objects DO have custom attributes, but I made sure modify the toObject method prototype as follows:
fabric.Object.prototype.toObject = (function (toObject) {
return function () {
return fabric.util.object.extend(toObject.call(this), {
customAttribute1: this.customAttribute1,
...
});
};
})(fabric.Object.prototype.toObject)
In order for those attributes to be persisted (which they are after a manual scanning of the resulting JSON).
UPDATE:
I noticed that the issue arises since a lot of my objects have objects inside of them that are not being recursively parsed to the FabricJS canvas format. I will continue to investigate.
I just had a somewhat similar problem. In my application I did not want to save the complete canvas, but only some objects added by the user. The objects' methods are not represented in JSON, just variables (width, height,...) - so when trying to redraw the objects in the canvas, I (probably you too) got the mentioned error.
I solved this by recreating the objects based on those variables, that made it into the JSON representation. In my application the FabricJS-data was stored as geometry besides some additional data (id, metadata).
for(var primitiveKey in data[0].value.primitives){
var currentPrimitive = data[0].value.primitives[primitiveKey];
var reassembledPrimitive = [];
reassembledPrimitive.id = currentPrimitive.id;
reassembledPrimitive.metadata = currentPrimitive.metadata;
reassembledPrimitive.geometry = new Fabric.Rect(currentPrimitive.geometry);
this.primitiveList.push(reassembledPrimitive);
}
Drawing the primitives:
redrawImage:function(){
this.canvas.add(this.image);
for(var i = 0; i < this.primitives.length; i++){
this.canvas.add(this.primitives[i].geometry);
}
}
If using different object types, you would have to read each object's type and instantiate accordingly.
The solution is not very pretty, let me know if you already found another and better one.
Cheers,
smon
Related
I am trying to create a class to my javascript game to add multiplayer but within the class i am having problems with the values of arrays changing as you can see in the sendNetEntities() function
class NET_IO{
//probably put address here
//I want to check for localhost to denote MASTER client
constructor(host, netlayer){
this.socket = io();
this.netLayer = netlayer
console.log(this.socket)
this.netEntities = this.netLayer.entities
//setInterval(() => {this.update()}, 200)
}
getNetEntities(){
this.socket.emit('getNetEntities', (net_entities) => {
console.log(net_entities)
this.netEntities = net_entities
})
}
sendNetEntities(layer){
var netEnt = this.netEntities
console.log(netEnt) //this returns [background: Entity, NIkTag: Entity, player: Entity]` the value i want
var ent = JSON.stringify(netEnt);
console.log(ent) //this returns []
this.socket.emit('sendNetEntities', ent)
}
update(layer, callback){
//check host if localhost dont retreive new data only send
this.sendNetEntities(layer)
callback(this.netEntities)
}
}
I think im having problems with variables somehow being references of something instead of instances. But im not entirely sure all of the rules behind that for javascript. can anyone help me shed some light on this problem. I'm willing to edit my question as needed
EDIT
further debugging leads me to believe that it must be some sort of problem with socket.io. if i run this this.socket.emit('sendNetEntities', {netEnt}) my return on the server is {netEnt:[]} I havent had problems like this in socket.io in the past. Am i doing something wrong. is socket.io the problem
Based on this:
//this returns [background: Entity, NIkTag: Entity, player: Entity]` the value i want
console.log(netEnt)
var ent = JSON.stringify(netEnt);
console.log(ent) //this returns []
I think you are treating an Array as an Object. In JavaScript, this is technically possible because almost everything is an Object, including arrays. However, this may lead to unexpected behavior:
// Create an array and an object
a = [] // an array
o = {} // an object
// Set some properties on both
a.p = 42
o.p = 42
// Show differences between arrays and objects:
console.log(a.constructor) // ƒ Array()
console.log(a) // [p: 42]
console.log(JSON.stringify(a)) // []
console.log(o.constructor) // ƒ Object()
console.log(o) // {p: 42}
console.log(JSON.stringify(o)) // {"p":42}
As you can see, JSON.stringify() ignores properties set on arrays.
So the solution is to use netEnt either as an array or as an object, without mixing the types:
// As an array, don't use property names. Use the integer array indices:
netEnt = [entity1, entity2, entity3]
background = netEnt[0]
nikTag = netEnt[1]
player = netEnt[2]
// As an object, property names can be used:
netEnt = {background: entity1, NIkTag: entity2, player: entity3}
background = netEnt.background
nikTag = netEnt.NIkTag
player = netEnt.player
update:
The fundamental problem is your classes use arrays, but access them as objects. The best solution is to change your classes so they either:
use arrays and access the arrays as arrays.
use objects and access the objects as objects.
Without seeing your class definitions, I cannot show you how to do this. However, it is as simple as changing the initial value of the class instances from [] to {}.
The following is a quick fix that serializes your array "objects" into true JS objects so JSON.stringify() will work as expected. However, in the future I highly recommend learning the difference between JS arrays and objects. This quick fix imposes a totally unnecessary performance penalty because JS arrays are being misused as objects:
sendNetEntities(layer){
var netEnt = this.netEntities
// Convert netEnt array "object" into true JS object
var trueObject = {}
for (prop in netEnt) {
trueObject[prop] = netEnt[prop]
}
var ent = JSON.stringify(trueObject);
this.socket.emit('sendNetEntities', ent)
}
Note in getNetEntities(), you will probably have to do the reverse: convert from true JS objects back to array "objects." I was unsure of the input format of net_entities, so I left this as an exercise.
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'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 need to somehow deep clone the entire set of bindings of my ScriptEngine object.
What I have tried
I have tried so far the Cloner library to clone the entire Bindings structure. This would be great if it worked because it would have ensured a precise copy, including private variables. But this leads to jvm heap corruption (the jvm just crashes with exit code -1073740940). Sometimes it doesn't crash but weird things happen, like the System.out.println() stops working as it should...
I have also looked into cloning the objects using js code inside the ScriptEngine, so that I can get those as NativeObjects and manage them in some java maps. But all cloning methods which I found have flaws. I want a precise snapshot of the objects. For instance if each of two objects a and b contain fields (say a.fa and b.fb) which reference the same object c, when cloned using jQuery.extend() (for instance) the fields a.fa and b.fb of the cloned a and b will reference different clones of c, instead of referencing one same clone. And many other edge issues.
I also tried to clone the entire ScriptEngine using Cloner (not only the bindings), and I also tried using Rhino's js engine and clone the entire scope (instead of the bundeled ScriptEngine wrapper). But the heap corruption issue persists.
Why I need to do it
I need this because I must be able to restore the values of the entire ScriptEngine bindings to some previous point. I need to make precise snapshots of the bindings.
The application is part of my doctoral research project which consists of running state machines with nodes (implemented in java) which have js code attached. The js code is typed in by the end user and it is being evaled at runtime. When final state can't be reached through a path, the algorithm makes steps backwards, trying to find alternative paths. On each step backward it must undo any changes that might have occurred in the js engine bindings.
All the global variables names are known before js eval-ing, and are objects (the user types in code for the nodes and this is then organized (within java) into js objects with certain name patterns). But their content can be anything because that is controlled by the user js code.
So I guess my only sollution now is to clone js object using js code.
Aside from "edge cases", jQuery.extend can be used in the way you mention. a b and their clones will all reference the same object c.
var c = { f:'see' };
var a = { fa: c };
var b = { fb: c };
var cloneA = $.extend({}, a);
var cloneB = $.extend({}, b);
console.log(a.fa === b.fb, cloneA.fa === cloneB.fb, a.fa === cloneB.fb);
// true true true
But it seems like you want to clone all the objects (including c) while keeping track of the objects' relationships. For this, it may be best to use object relation tables.
I see this a lot with nested javascript objects and JSON because people tend to forget that JSON is purely a text format. There are no actual javascript objects in a JSON file except for a single string of text instanceof String. There are no beans or pickles or any preservative-heavy foods in javascript.
In an object relation table, each 'table' is just an array of "flat" objects with only primitive valued properties and pointers (not references) to other objects in the table (or in another table). The pointer can just be the index of the target object.
So a JSON version of the above object relationship might look something like
{
"table-1":[
{ "a": { "fa":["table-2",0] } },
{ "b": { "fb":["table-2",0] } }
],
"table-2":[
{ "c": { "name":"see" } },
{ "d": { "name":"dee" } },
{ "e": { "name":"eh.."} }
]
}
And a parser might look like
var tables = JSON.parse(jsonString);
for(var key in tables){
var table = tables[key];
for(var i = 0; i < table.length; i++){
var name = Object.keys(table[i])
var obj = table[i][name];
for(var key2 in obj){
if(obj[key2] instanceof Array && obj[key2][0] in tables){
var refTable = obj[key2][0];
var refID = obj[key2][1];
var refObj = tables[refTable][refID];
var refKey = Object.keys(refObj)[0];
obj[key2] = refObj[refKey];
}
}
this[name] = obj;
}
}
console.log(a.fa === b.fb, b.fb === c);
// true true
I realize object relationship mapping has it's downfalls, but taking snapshots of the scripting engine does sound a bit crazy. Especially since your intention is to be able to recall each previous step, because then you need a new snapshot for every step... that will very quickly take up shit ton of disk space.. unless you're just keeping track of the snapshot diffs between each step, like a git repo. That sounds like an awful lot of work to implement a seemingly simple "undo" method.
Damn.. come to think of it, why not just store each step in a history file? Then if you need to step back, just truncate the history file at the previous step, and run each step over again in a fresh environment.
Not sure how practical that would be (performance wise) using java. Nodejs (as it exists today) is faster than any java scripting engine will ever be. In fact, I'm just gonna call it ECMAscript from now on. Sorry bout the rant, its just that
Java is slow, but you already know.
Because it readily shows, that's as fast as it goes.
May I suggest a different approach.
Don't try to clone the ScriptEngine. It doesn't implement Serializable/Externalizable and the API doesn't support cloning. Attempts to force a clone will be workarounds that might break in some future Java version.
Serialize Bindings to JSON using cycle.js. It will encode object references in the form {$ref: PATH}. When you deserialize it will restore the references.
As far as I can tell cycle.js doesn't serialize functions but its possible to add function serialization yourself using Function.toString() (See below and Example)
Alternatively if using a library is not an option its fairly straightforward to implement your own serialization to suit your needs:
var jsonString = JSON.stringify(obj, function(key, val) {
if (typeof(value) === 'function')
return val.toString();
// or do something else with value like detect a reference
return val
})
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