+ 13
[SOLVED] Serialize and deserialize objects in JavaScript.
I have been searching for an easy way to serialize deep nested objects to JSON format without losing its functions and constructor. I was able to serialize it using Object.prototype.toString recursively. Unfortunately, I could not deserialize it without eval(). I will be glad if there is a straightforward (and secure) solution or a CDN hosted library I can use.
23 ответов
+ 6
Ore... sorry it took me a while to get back to this question. The OCP issue you asked about was an interesting one indeed as the solution wasn't straight forward... especially if you wanted to avoid using the eval() function.
I saved an earlier version of your code, so it's a bit different from the latest version where you implemented a CanvasTypeWizard class. To help clarify all the changes, I've placed a lot of inline comments in the two codes below - based on your earlier versions:
a) Review inline recommendations on JS tab:
https://code.sololearn.com/WFY81y0O2B7x/?ref=app
b) Review implemented recommendations on JS tab:
https://code.sololearn.com/WJ9V9EXswF69/?ref=app
+ 17
Ore
for functions, you can use new Function(), but you've chosen a difficult path to walk.
You're going to run into issues with JSON though, since it can't distinguish between, say, a string that says "function: <body>" and a function you've serialised that way.
you can specify a .toJSON() method on any object.
And it will use that instead of the default serialiser when calling JSON.stringify
And with JSON.parse, you can give it a revive function.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter
I believe This is simply not possible....
without also transfering the definitions of those functions (don't)
Eg - class MyClass () { ... }
// somewhere else
const serialized = instanceOfMyClass.serialize()
const instance = MyClass.hydrate(serialized)
Basically, you serialize the pure data.
then you can hydrate a new instance with that data.
You cannot serialize the class itself or its methods, it might not be possible!
+ 12
Ore Is there a reason for needing to serialize the functions? Traditionally, serialization is used to preserve state of publicly accessible property values in a portable text format - or rather a format that isn't binary.
Typically, the serialized object could be deserialized into an object with the bound functions using something like:
object.assign(obj, JSON.parse(serialized))
This comes with some limitations, which aren't really a bad thing. 😉
For example, a privately scoped variable, such as those that are enclosed or block scoped will not automatically be restored as those aren't automatically serialized either.
You might need to override the toJSON() and fromJSON() methods on the various objects to extend the serialization with additional meta data to include in the stringified result.
But... if you want to serialize function definitions into strings, then deserialize back into memory, you'll have to use eval(). There's no way around it that I'm aware of.
Still... this seems to be unnecessary.
+ 4
So, let me get this straight, you want to convert a JavaScript object to string with it's functions and then you don't want to lose the work of these functions?
What about we go back to the beginning a bit, js has an interpreter that distinguish between a string and js code, if you would like to "resolve" a string to work like JavaScript functions, classes, variables, etc... you need to pass that to the interpreter of that file.
One way is to use eval, because eval does that for you, I don't think there is another hacky way.
+ 4
I'm sorry for the late reply, however, David Carroll is right, I read about that similar method to eval "JSON.parse" in "you don't know js" book by kyle simpson, and they are similar in the matter of work ability, "Function" is no difference, anything that will parse code into the compiler will eventually be a weak point in your application.
Have you read about "innerHTML"? Very interesting it's, if you have it as a place just to write a static context it's harmless, however, if you have it as a dynamic rendered by serverside response or by an input that might send a request to the server it's (not recomended), because it can be injected very easley.
For any further information about security I think you probably need a security expert in the field.
EDIT: Also there is something I have not tested before, and I don't know about, but I heard of it in which it's "JSONP", it's a very interesting topic to look for too.
I wish that ws helpful. 🙂
+ 4
Thanks for all your contributions. I have improved the code design and I think my problem is solved now. Your comments were all helpful.
Jouhar
David Carroll
Piyush[🎂boi]
https://code.sololearn.com/W9e4QbH8LXeJ/
Kode Krasher I am glad you found this thread helpful.
Vania Litvin Sorry. I don't understand your language.
+ 4
A summary of the key changes are as follows:
- get type() moved to CanvasObject and dynamically resolves type by using:
this.constructor.name
- Removed the array of class names.
- Rather, I assigned classes to the global object.
Example:
var CanvasText = class CanvasText { ... }
This allowed me to dynamically create new instances from the windows object.
There are a few other notable recommendations and revisions throughout the code.
Let me know if you have any questions.
I hope this was helpful.
+ 3
Jouhar Thanks for your reply.
+ 3
Piyush[21 Dec❤️] Thanks for your answer.
You said,
"Basically, you serialize the pure data. then you can hydrate a new instance with that data."
That is exactly what I am doing. The program structure is something like this:
class CanvasObject {
toJSON() {}
static fromJSON() {}
}
class CanvasText extends CanvasObject {}
class CanvasCircle extends CanvasObject {}
// and other sub classes
CanvasObject.fromJSON is responsible for hydrating the serialized data by calling the right constructor.
This means that CanvasObject.fromJSON must have knowledge of all children of CanvasObject. This is a violation of Open/Closed Principle. So my new question is..
How can I implement the hydrate function (fromJSON) without violating the OCP?
+ 2
David Carroll Thanks for answering my question. I would like the deserialized objects to retain their type (prototype).
deserialize(serialize(new A())) instanceof A // should be true
Therefore, I want to store their constructor in the serialized string.
I am not sure if there is a way to do that using JSON.stringify?!
+ 2
Wait... I think I get what you mean. I can define polymorphic methods for each objects using ES 6 classes. In that way, storing the constructor will not be necessary.
+ 2
Ore I missed your follow up question. I'll try to review later when I can make some time.
+ 1
Jouhar I cannot use eval() for 2 main reasons.
1. The performance is undesirable when dealing with many deeply nested objects.
2. It is not safe. The program stores the JSON in the user's local storage and it can easily be compromised before deserializing.
I don't think window.Function() is safe either?
+ 1
David Carroll It does not work, unfortunately.
So I am developing a drawing app. It is an updated version of this code bit.
https://code.sololearn.com/W9e4QbH8LXeJ/?ref=app
Different shapes can be drawn on the canvas. They are all sub classes of CanvasObject.
I need a way to implement "Export to JSON" and "Import from JSON".
The problem is... After serialization, I can't determine what type of object something used to be.
Presently, this is what I came up with
class CanvasObject {
toJSON() {
const withType = Object.assign({}, this, {typeof: this.type});
return JSON.stringify(withType);
}
static fromJSON(json) {
const parsed = JSON.parse(json);
switch (parsed.typeOf) {
// call the right constructor
}
}
}
But this is a clear violation of OCP. I think there should be a better solution?!
This used to be easy in statically typed languages. 😕
+ 1
David Carroll ???
+ 1
David Carroll No problem. I will be waiting.
+ 1
David Carroll You are the best! Thanks very much.
How come I did not think of constructor.name?! Thanks for suggesting it.
Also, this is my first time of seeing that dynamic class resolution thing: window[parsed.classname]. It seems it only works with var(not const or let)?
+ 1
Ore Actually... I want to thank you for posting such an interesting question. It forced me to experiment a bit to figure this out. There doesn't seem to be any explanation of where the classes are stored in memory. I couldn't find it in the window object, but I did notice the native classes in that object while debugging.
I tried using let and const at first with no success.
I then tried var and that did the trick. I may need to do a blog post on this observation.
But it seems to be a decent solution in lieu of interfaces, abstract methods, or generics which are available in TypeScript. I suspect, TypeScript might be doing something similar to my technique, but I have no idea until I do more digging.
0
🙂
0
David Carroll I am glad that you found my question interesting. I also do. JS is a fascinating language.
I have one more question though: Is there any reason why you used named classes? Unnamed class expressions seem to work too.
var CanvasTriangle = class extends CanvasObject {}
Lastly, you mentioned writing a blog post. Is it a personal blog? Because I would like to read any article you write about software development or computer science