Best practices for stringifying JSON inner objects - javascript

Basically I have something like this:
MyClass
var MyClass = function() {
this.num = 123;
this.obj = new MyInnerClass();
};
MyClass.prototype.stringify = function() {
return JSON.stringify(this);
};
MyInnerClass
var MyInnerClass = function() {
this.foo = 456;
this.bar = 'bonjour!';
};
MyInnerClass.prototype.stringify = function() {
return JSON.stringify(this, function(k, v) {
// ignores 'foo' attribute
return k !== 'foo' ? v : undefined;
});
};
Each class has its own stringify implementation, so when I do:
var mc = new MyClass();
mc.stringify();
I would like something like calling MyClass.stringify should stringify my mc object, but respecting inner objects stringify implementations. Once we don't have control over the JSON.stringify method logic, is there a good way to do that?
Thank you!

If you look on MDN at JSON.stringify, you'll see a section that talks about a toJSON property
If an object being stringified has a property named toJSON whose value is a function, then the toJSON() method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the toJSON() method when called will be serialized.
Basically, define a toJSON method for your Object which creates another Object, but one that can be serialised as you desire. Then JSON.stringify will serialise the return of your toJSON function instead, i.e.
var MyClass = function() {
this.num = 123;
this.obj = new MyInnerClass();
};
var MyInnerClass = function() {
this.foo = 456;
this.bar = 'bonjour!';
};
MyInnerClass.prototype.toJSON = function () {
// shallow clone `this`, except .foo
var o = Object.create(null), k, blacklist = ['foo'];
for (k in this) // loop over enumerable properties
if (Object.prototype.hasOwnProperty.call(this, k)) // ignore inherited properties
if (blacklist.indexOf(k) === -1) // ignore blacklisted properties
o[k] = this[k]; // add to our clone
return o;
};
JSON.stringify(new MyClass()); // '{"num":123,"obj":{"bar":"bonjour!"}}'
This will also replace your need for the current stringify method.
Sadly you can't call JSON.stringify(this) inside .toJSON because it becomes circular and you get RangeError: Maximum call stack size exceeded, but you'd not get the desired result this way anyway as it would be serialised a second time, giving you a String in your JSON.

Related

How to convert an instance of a class to a string and then back to that class instance? [duplicate]

I'm writing a simple serialization / deserialization framework for some application-specific objects.
Consider the following:
"use strict";
function Dog(name) { this._name = name; };
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() { return this._name; }
var d1 = new Dog('fido');
var d2 = JSON.parse(JSON.stringify(d1)); // serialize / deserialize
> d1
Dog { _name: 'fido' }
> d1.getName()
'fido'
> d2
{ _name: 'fido' }
> d2.getName()
TypeError: d2.getName is not a function
At this point, one can ask "What does d1 have that d2 lacks?"
One approach that partially works is to manually assign the methods of d1 to d2:
> d2.constructor = d1.constructor
> d2.getName = d1.getName
> d2.getName()
'fido'
This has a couple of disadvantages. First, I have to manually assign each method of d1 to d2. Second, d2 gets its own properties, and doesn't share slots using the prototype mechanism:
> d2
Dog {
_name: 'fido',
constructor: [Function: Dog],
getName: [Function] }
So my refined question is: given an object (e.g. d2), is there a way to associate it with the prototype of another object (e.g. d1) so it inherits the same behavior?
Object.create() and Object.getOwnPropertyDescriptors() is what you need.
const obj = JSON.parse(JSON.stringify(d1))
const d3 = Object.create(Dog.prototype, Object.getOwnPropertyDescriptors(obj))
The difference between this and OP's method is that this method sets prototype properties on the prototype, whereas OP's method sets properties directly on the object. You can see this when you loop through object own properties using for-in loop with hasOwnProperty() method:
for (const i in d1) {
if (d3.hasOwnProperty(i)) {
console.log(i)
}
}
With my method it outputs only _name, but with OP's method it outputs also getName.
Unfortunately, Object.getOwnPropertyDescriptors() is part of ECMAScript 2017 and it's supported only in Firefox for now, so you'll need to use Babel.
Alternatively, you can use Object.setPrototypeOf(). It has better browser support than Object.getOwnPropertyDescriptors(), but it's discouraged by MDN, because it's slow.
const d3 = JSON.parse(JSON.stringify(d1))
Object.setPrototypeOf(d3, Dog.prototype)
As I was writing this, I had the idea of creating a custom constructor that uses the deserialized JSON to initialize the object:
Dog.createFromJSON = function(obj) {
var d = new Dog();
Object.keys(obj).forEach(function(key) {
d[key] = obj[key];
});
return d;
}
> d3 = Dog.createFromJSON(JSON.parse(JSON.serialize(d1)))
> d3
Dog { _name: 'fido' }
> d3.getName()
'fido'
Update: how to dynamically find the class and assign the prototype
As #Louis points out, #Gothdo's answer requires that you know what class the deserialized object belongs to. If you're willing to add the class name to the serialized object, you can use that to determine the class dynamically. So, for example, to expand on the OP's example:
> var d1 = new Dog('fido');
> d1['_class'] = 'Dog';
> let jsonString = JSON.stringify(d1)
'{"_name":"fido","_class":"Dog"}'
Using the trick described in deserialize JSON to JAVASCRIPT object (but tweaked for Node.js) you can use a string to get a handle to a class prototype via Node.js's global object:
> global[d1['_class']].prototype
Dog { getName: [Function] }
Now you can use that to dynamically reconstruct the object using #Gothdo's technique. Putting it all together:
/**
* Dynamically create an object from a JSON string of properties.
* Assumes the presence of a _class meta-property that names the
* resulting class.
*/
function reconstitute(jsonString) {
let obj = JSON.parse(jsonString);
let cls = global[obj['_class']];
delete obj['_class']; // remove meta-property
return Object.setPrototypeOf(obj, cls.prototype);
}
> reconstitute('{"_name":"fido","_class":"Dog"}')
Dog { _name: 'fido' }
The Best method so far would be:
let obj = Object.assign(new ClassyObject(), JSON.parse(JSON.serialize(the_obj_that_will_lost_prototype)))
Just improved and more direct:
let obj = Object.assign(new the_obj_that_will_lost_prototype.constructor(), JSON.parse(JSON.serialize(the_obj_that_will_lost_prototype)))
Simple Method: Change Class In-Place
If you really need to change the class of an object in-place, this function will work on any system that has Object.getPrototypeOf and Object.setPrototypeOf defined:
// set class of object to `name`
function setObjectClassName(obj, name) {
let newObj = eval('new ' + name + '()');
let proto = Object.getPrototypeOf(newObj);
Object.setPrototypeOf(obj, proto);
return obj;
}
Example using JSON.serialize() and JSON.parse():
class MyClass extends Object {}
let original = new MyClass();
original.foo = "bar";
console.log(original.constructor.name, original);
// MyClass { "foo": 'bar' }
let originalClassName = original.constructor.name;
let serialized = JSON.stringify(original);
console.log(serialized.constructor.name, serialized);
// String '{"foo":"bar"}'
let restored = JSON.parse(serialized);
console.log(restored.constructor.name, restored);
// Object { foo: 'bar' }
restored = setObjectClassName(restored, originalClassName);
console.log(restored.constructor.name, restored);
// MyClass { foo: 'bar' }
Better Method: Copy the Object
Mozilla warns against changing the prototype of an existing object, as it is:
a very slow operation in every browser and JavaScript engine
- Mozilla
If you don't absolutely need to change in-place, this function will copy an object and change the class of the copy:
function copyObjectAndChangeClass(obj, name) {
let newObj = eval('new ' + name + '()');
Object.assign(newObj, obj);
return newObj;
}

Using JSON.stringify on custom class

I'm trying to store an object in redis, which is an instance of a class, and thus has functions, here's an example:
function myClass(){
this._attr = "foo";
this.getAttr = function(){
return this._attr;
}
}
Is there a way to store this object in redis, along with the functions? I tried JSON.stringify() but only the properties are preserved. How can I store the function definitions and be able to perform something like the following:
var myObj = new myClass();
var stringObj = JSON.stringify(myObj);
// store in redis and retreive as stringObj again
var parsedObj = JSON.parse(stringObj);
console.log(myObj.getAttr()); //prints foo
console.log(parsedObj.getAttr()); // prints "Object has no method 'getAttr'"
How can I get foo when calling parsedObj.getAttr()?
Thank you in advance!
EDIT
Got a suggestion to modify the MyClass.prototype and store the values, but what about something like this (functions other than setter/getter):
function myClass(){
this._attr = "foo";
this._accessCounts = 0;
this.getAttr = function(){
this._accessCounts++;
return this._attr;
}
this.getCount = function(){
return this._accessCounts;
}
}
I'm trying to illustrate a function that calculates something like a count or an average whenever it is called, apart from doing other stuff.
First, you are not defining a class.
It's just an object, with a property whose value is a function (All its member functions defined in constructor will be copied when create a new instance, that's why I say it's not a class.)
Which will be stripped off when using JSON.stringify.
Consider you are using node.js which is using V8, the best way is to define a real class, and play a little magic with __proto__. Which will work fine no matter how many property you used in your class (as long as every property is using primitive data types.)
Here is an example:
function MyClass(){
this._attr = "foo";
}
MyClass.prototype = {
getAttr: function(){
return this._attr;
}
};
var myClass = new MyClass();
var json = JSON.stringify(myClass);
var newMyClass = JSON.parse(json);
newMyClass.__proto__ = MyClass.prototype;
console.log(newMyClass instanceof MyClass, newMyClass.getAttr());
which will output:
true "foo"
No, JSON does not store functions (which would be quite inefficient, too). Instead, use a serialisation method and a deserialisation constructor. Example:
function MyClass(){
this._attr = "foo";
this.getAttr = function(){
return this._attr;
}
}
MyClass.prototype.toJSON() {
return {attr: this.getAttr()}; // everything that needs to get stored
};
MyClass.fromJSON = function(obj) {
if (typeof obj == "string") obj = JSON.parse(obj);
var instance = new MyClass;
instance._attr = obj.attr;
return instance;
};
Scanales, I had the same issue and tried a technique similar to Bergi's recommendation of creating new serialization/deserialization methods...but found it didn't work for me because I have objects nested in objects (several deep). If that's your case then here's how I solved it. I wrote a base class (clsPersistableObject) from which all objects that I wanted to persist inherited from. The base class has a method called deserialize, which is passed the JSON string. This method sets the properties one by one (but does not wipe out the exist methods) and then recursively defer to the child object to do the same (as many times as necessary).
deserialize: function (vstrString) {
//.parse: convert JSON string to object state
//Use JSON to quickly parse into temp object (does a deep restore of all properties)
var tmpObject = JSON.parse(vstrString);
//objZoo2.animal.move();
//Note: can't just do something like this:
// CopyProperties(tmpObject, this);
//because it will blindly replace the deep objects
//completely...inadvertently wiping out methods on it. Instead:
//1) set the properties manually/one-by-one.
//2) on objects, defer to the deserialize on the child object (if it inherits clsPersistableObject)
//2b) if it doesn't inherit it, it's an intrinsic type, etc...just do a JSON parse.
//loop through all properties
var objProperty;
for (objProperty in tmpObject) {
//get property name and value
var strPropertyName = objProperty;
var strPropertyValue = tmpObject[objProperty]; //note: doing this .toString() will cause
if (objProperty !== undefined) {
//check type of property
if (typeof strPropertyValue == "object") {
//object property: call it recursively (and return that value)
var strPropertyValue_AsString = JSON.stringify(strPropertyValue);
//see if has a deserialize (i.e. inherited from clsPeristableObject)
if ("deserialize" in this[objProperty]) {
//yes: call it
this[objProperty]["deserialize"](strPropertyValue_AsString);
}
else {
//no: call normal JSON to deserialize this object and all below it
this[objProperty] = JSON.parse(strPropertyValue_AsString);
} //end else on if ("deserialize" in this[objProperty])
}
else {
//normal property: set it on "this"
this[objProperty] = tmpObject[objProperty];
} //end else on if (typeof strPropertyValue == "object")
} //end if (objProperty !== undefined)
}
}
it looks like you attempt to stringify a closed function. you can use ()=>{} to solve the scope problem.
function myClass(){
this._attr = "foo";
this._accessCounts = 0;
this.getAttr = ()=>{
this._accessCounts++;
return this._attr;
}
this.getCount = ()=>{
return this._accessCounts;
}
}
What you get back grom JSON.stringify() is a String. A string has no methods.
You need to eval first that string and then you'll be able to get the original object
and its methods.
var myObj = new myClass();
var stringObj = JSON.stringify(myObj);
---- EDIT -----
//Sorry use this:
var getBackObj = JSON.parse(stringObj);
//Not this
var getBackObj = eval(stringObj);
console.log(getBackObj.getAttr()); // this should work now

javascript class inherit from Function class

I like that in javascript, I can create a function, and then add further methods and attributes to that function
myInstance = function() {return 5}
myInstance.attr = 10
I would like to create a class to generate these objects. I assume I have to inherit from the Function base class.
In other words, I would like to:
var myInstance = new myFunctionClass()
var x = myInstance()
// x == 5
But I don't know how to create the myFunctionClass. I have tried the following, but it does not work:
var myFunctionClass = function() {Function.call(this, "return 5")}
myFunctionClass.prototype = new Function()
myInstance = new myFunctionClass()
myInstance()
// I would hope this would return 5, but instead I get
// TypeError: Property 'myInstance' of object #<Object> is not a function
I also tried the more complicated (and more proper?) inheritance method found here: How to "properly" create a custom object in JavaScript?, with no more luck. I have also tried using the util.inherits(myFunctionClass, Function) found in node.js. Still no luck
I have exhausted Google, and therefore feel that I must be missing something fundamental or obvious. Help would be greatly appreciated.
Your trying to inherit from Function. This is a right pain to do. I suggest you do the following instead
Live Example
var Proto = Object.create(Function.prototype);
Object.extend(Proto, {
constructor: function (d) {
console.log("construct, argument : ", d);
this.d = d;
// this is your constructor logic
},
call: function () {
console.log("call", this.d);
// this get's called when you invoke the "function" that is the instance
return "from call";
},
method: function () {
console.log("method");
// some method
return "return from method";
},
// some attr
attr: 42
});
You want to create a prototype object that forms the basis of your "class". It has your generic methods/attributes. It also has a constructor that gets invoked on object construction and a call method that gets invoked when you call the function
var functionFactory = function (proto) {
return function () {
var f = function () {
return f.call.apply(f, arguments);
};
Object.keys(proto).forEach(function (key) {
f[key] = proto[key];
});
f.constructor.apply(f, arguments);
return f;
}
}
A function factory takes a prototype object and returns a factory for it. The returned function when called will give you a new function object that "inherits" from your prototype object.
var protoFactory = functionFactory(proto);
var instance = protoFactory();
Here you create your factory and then create your instance.
However this isn't proper prototypical OO. we are just shallow copying properties of a prototype into a new object. So changes to the prototype will not reflect back to the original object.
If you want real prototypical OO then you need to use a hack.
var f = function () {
// your logic here
};
f.__proto__ = Proto;
Notice how we use the non-standard deprecated .__proto__ and we are mutating the value of [[Prototype]] at run-time which is considered evil.
JS does not allow a constructor to return a function, even though functions are objects. So you cant have an instantiation of a prototype that is itself executable. (Am I right in this? please correct if I'm not, it's an interesting question).
Though you could do a factory function:
var makeCoolFunc = function() {
var f = function() { return 5 };
f.a = 123;
f.b = 'hell yes!'
return f;
};
var func = makeCoolFunc();
var x = func();
You can extend Function and pass the wanted function body as String to the super constructor. The context of the function can be accessed with arguments.callee.
Example for an observable Attribute class:
export default class Attribute extends Function {
constructor(defaultValue){
super("value", "return arguments.callee.apply(arguments);");
this.value = defaultValue;
this.defaultValue = defaultValue;
this.changeListeners = [];
}
apply([value]){
if(value!==undefined){
if(value!==this.value){
var oldValue = this.value;
this.value=value;
this.changeListeners.every((changeListener)=>changeListener(oldValue, value));
}
}
return this.value;
}
clear(){
this.value=undefined;
}
reset(){
this.value=this.defaultValue;
}
addChangeListener(listener){
this.changeListeners.push(listener);
}
removeChangeListener(listener){
this.changeListeners.remove(listener);
}
clearChangeListeners(){
this.changeListeners = [];
}
}
Example usage:
import Attribute from './attribute.js';
var name= new Attribute();
name('foo'); //set value of name to 'foo'
name.addChangeListener((oldValue, newValue)=>{
alert('value changed from ' +oldValue+ ' to ' +newValue);
});
alert(name()); //show value of name: 'foo'
name('baa'); //set value of name to new value 'baa' and trigger change listener

Parse JSON String into a Particular Object Prototype in JavaScript

I know how to parse a JSON String and turn it into a JavaScript Object.
You can use JSON.parse() in modern browsers (and IE9+).
That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (i.e. with a certain prototype)?
For example, suppose you have:
function Foo()
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse({"a":4, "b": 3});
//Something to convert fooJSON into a Foo Object
//....... (this is what I am missing)
alert(fooJSON.test() ); //Prints 12
Again, I am not wondering how to convert a JSON string into a generic JavaScript Object. I want to know how to convert a JSON string into a "Foo" Object. That is, my Object should now have a function 'test' and properties 'a' and 'b'.
UPDATE
After doing some research, I thought of this...
Object.cast = function cast(rawObj, constructor)
{
var obj = new constructor();
for(var i in rawObj)
obj[i] = rawObj[i];
return obj;
}
var fooJSON = Object.cast({"a":4, "b": 3}, Foo);
Will that work?
UPDATE May, 2017: The "modern" way of doing this, is via Object.assign, but this function is not available in IE 11 or older Android browsers.
The current answers contain a lot of hand-rolled or library code. This is not necessary.
Use JSON.parse('{"a":1}') to create a plain object.
Use one of the standardized functions to set the prototype:
Object.assign(new Foo, { a: 1 })
Object.setPrototypeOf({ a: 1 }, Foo.prototype)
See an example below (this example uses the native JSON object). My changes are commented in CAPITALS:
function Foo(obj) // CONSTRUCTOR CAN BE OVERLOADED WITH AN OBJECT
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
// IF AN OBJECT WAS PASSED THEN INITIALISE PROPERTIES FROM THAT OBJECT
for (var prop in obj) this[prop] = obj[prop];
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
// INITIALISE A NEW FOO AND PASS THE PARSED JSON OBJECT TO IT
var fooJSON = new Foo(JSON.parse('{"a":4,"b":3}'));
alert(fooJSON.test() ); //Prints 12
Do you want to add JSON serialization/deserialization functionality, right? Then look at this:
You want to achieve this:
toJson() is a normal method.
fromJson() is a static method.
Implementation:
var Book = function (title, author, isbn, price, stock){
this.title = title;
this.author = author;
this.isbn = isbn;
this.price = price;
this.stock = stock;
this.toJson = function (){
return ("{" +
"\"title\":\"" + this.title + "\"," +
"\"author\":\"" + this.author + "\"," +
"\"isbn\":\"" + this.isbn + "\"," +
"\"price\":" + this.price + "," +
"\"stock\":" + this.stock +
"}");
};
};
Book.fromJson = function (json){
var obj = JSON.parse (json);
return new Book (obj.title, obj.author, obj.isbn, obj.price, obj.stock);
};
Usage:
var book = new Book ("t", "a", "i", 10, 10);
var json = book.toJson ();
alert (json); //prints: {"title":"t","author":"a","isbn":"i","price":10,"stock":10}
var book = Book.fromJson (json);
alert (book.title); //prints: t
Note: If you want you can change all property definitions like this.title, this.author, etc by var title, var author, etc. and add getters to them to accomplish the UML definition.
A blog post that I found useful:
Understanding JavaScript Prototypes
You can mess with the __proto__ property of the Object.
var fooJSON = jQuery.parseJSON({"a":4, "b": 3});
fooJSON.__proto__ = Foo.prototype;
This allows fooJSON to inherit the Foo prototype.
I don't think this works in IE, though... at least from what I've read.
Am I missing something in the question or why else nobody mentioned reviver parameter of JSON.parse since 2011?
Here is simplistic code for solution that works:
https://jsfiddle.net/Ldr2utrr/
function Foo()
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse(`{"a":4, "b": 3}`, function(key,value){
if(key!=="") return value; //logic of course should be more complex for handling nested objects etc.
let res = new Foo();
res.a = value.a;
res.b = value.b;
return res;
});
// Here you already get Foo object back
alert(fooJSON.test() ); //Prints 12
PS: Your question is confusing: >>That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (i.e. with a certain prototype)?
contradicts to the title, where you ask about JSON parsing, but the quoted paragraph asks about JS runtime object prototype replacement.
The currently accepted answer wasn't working for me. You need to use Object.assign() properly:
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
greet(){
return `hello my name is ${ this.name } and i am ${ this.age } years old`;
}
}
You create objects of this class normally:
let matt = new Person('matt', 12);
console.log(matt.greet()); // prints "hello my name is matt and i am 12 years old"
If you have a json string you need to parse into the Person class, do it like so:
let str = '{"name": "john", "age": 15}';
let john = JSON.parse(str); // parses string into normal Object type
console.log(john.greet()); // error!!
john = Object.assign(Person.prototype, john); // now john is a Person type
console.log(john.greet()); // now this works
An alternate approach could be using Object.create. As first argument, you pass the prototype, and for the second one you pass a map of property names to descriptors:
function SomeConstructor() {
};
SomeConstructor.prototype = {
doStuff: function() {
console.log("Some stuff");
}
};
var jsonText = '{ "text": "hello wrold" }';
var deserialized = JSON.parse(jsonText);
// This will build a property to descriptor map
// required for #2 argument of Object.create
var descriptors = Object.keys(deserialized)
.reduce(function(result, property) {
result[property] = Object.getOwnPropertyDescriptor(deserialized, property);
}, {});
var obj = Object.create(SomeConstructor.prototype, descriptors);
I like adding an optional argument to the constructor and calling Object.assign(this, obj), then handling any properties that are objects or arrays of objects themselves:
constructor(obj) {
if (obj != null) {
Object.assign(this, obj);
if (this.ingredients != null) {
this.ingredients = this.ingredients.map(x => new Ingredient(x));
}
}
}
For the sake of completeness, here's a simple one-liner I ended up with (I had no need checking for non-Foo-properties):
var Foo = function(){ this.bar = 1; };
// angular version
var foo = angular.extend(new Foo(), angular.fromJson('{ "bar" : 2 }'));
// jquery version
var foo = jQuery.extend(new Foo(), jQuery.parseJSON('{ "bar" : 3 }'));
I created a package called json-dry. It supports (circular) references and also class instances.
You have to define 2 new methods in your class (toDry on the prototype and unDry as a static method), register the class (Dry.registerClass), and off you go.
While, this is not technically what you want, if you know before hand the type of object you want to handle you can use the call/apply methods of the prototype of your known object.
you can change this
alert(fooJSON.test() ); //Prints 12
to this
alert(Foo.prototype.test.call(fooJSON); //Prints 12
I've combined the solutions that I was able to find and compiled it into a generic one that can automatically parse a custom object and all it's fields recursively so you can use prototype methods after deserialization.
One assumption is that you defined a special filed that indicates it's type in every object you want to apply it's type automatically (this.__type in the example).
function Msg(data) {
//... your init code
this.data = data //can be another object or an array of objects of custom types.
//If those objects defines `this.__type', their types will be assigned automatically as well
this.__type = "Msg"; // <- store the object's type to assign it automatically
}
Msg.prototype = {
createErrorMsg: function(errorMsg){
return new Msg(0, null, errorMsg)
},
isSuccess: function(){
return this.errorMsg == null;
}
}
usage:
var responseMsg = //json string of Msg object received;
responseMsg = assignType(responseMsg);
if(responseMsg.isSuccess()){ // isSuccess() is now available
//furhter logic
//...
}
Type assignment function (it work recursively to assign types to any nested objects; it also iterates through arrays to find any suitable objects):
function assignType(object){
if(object && typeof(object) === 'object' && window[object.__type]) {
object = assignTypeRecursion(object.__type, object);
}
return object;
}
function assignTypeRecursion(type, object){
for (var key in object) {
if (object.hasOwnProperty(key)) {
var obj = object[key];
if(Array.isArray(obj)){
for(var i = 0; i < obj.length; ++i){
var arrItem = obj[i];
if(arrItem && typeof(arrItem) === 'object' && window[arrItem.__type]) {
obj[i] = assignTypeRecursion(arrItem.__type, arrItem);
}
}
} else if(obj && typeof(obj) === 'object' && window[obj.__type]) {
object[key] = assignTypeRecursion(obj.__type, obj);
}
}
}
return Object.assign(new window[type](), object);
}
A very simple way to get the desired effect is to add an type attribute while generating the json string, and use this string while parsing the string to generate the object:
serialize = function(pObject) {
return JSON.stringify(pObject, (key, value) => {
if (typeof(value) == "object") {
value._type = value.constructor.name;
}
return value;
});
}
deSerialize = function(pJsonString) {
return JSON.parse(pJsonString, (key, value) => {
if (typeof(value) == "object" && value._type) {
value = Object.assign(eval('new ' + value._type + '()'), value);
delete value._type;
}
return value;
});
}
Here a little example of use:
class TextBuffer {
constructor() {
this.text = "";
}
getText = function() {
return this.text;
}
setText = function(pText) {
this.text = pText;
}
}
let textBuffer = new TextBuffer();
textBuffer.setText("Hallo");
console.log(textBuffer.getText()); // "Hallo"
let newTextBuffer = deSerialize(serialize(textBuffer));
console.log(newTextBuffer.getText()); // "Hallo"
Here is a solution using typescript and decorators.
Objects keep their methods after deserialization
Empty objects and their children are default-initialized
How to use it:
#SerializableClass
class SomeClass {
serializedPrimitive: string;
#SerializableProp(OtherSerializedClass)
complexSerialized = new OtherSerializedClass();
}
#SerializableClass
class OtherSerializedClass {
anotherPrimitive: number;
someFunction(): void {
}
}
const obj = new SomeClass();
const json = Serializable.serializeObject(obj);
let deserialized = new SomeClass();
Serializable.deserializeObject(deserialized, JSON.parse(json));
deserialized.complexSerialized.someFunction(); // this works!
How it works
Serialization:
Store the type name in the prototype (__typeName)
Use JSON.stringify with a replacer method that adds __typeName to the JSON.
Deserialization:
Store all serializable types in Serializable.__serializableObjects
Store a list of complex typed properties in every object (__serializedProps)
Initialize an object theObject via the type name and __serializableObjects.
Go through theObject.__serializedProps and traverse over it recursively (start at last step with every serialized property). Assign the results to the according property.
Use Object.assign to assign all remaining primitive properties.
The code:
// #Class decorator for serializable objects
export function SerializableClass(targetClass): void {
targetClass.prototype.__typeName = targetClass.name;
Serializable.__serializableObjects[targetClass.name] = targetClass;
}
// #Property decorator for serializable properties
export function SerializableProp(objectType: any) {
return (target: {} | any, name?: PropertyKey): any => {
if (!target.constructor.prototype?.__serializedProps)
target.constructor.prototype.__serializedProps = {};
target.constructor.prototype.__serializedProps[name] = objectType.name;
};
}
export default class Serializable {
public static __serializableObjects: any = {};
private constructor() {
// don't inherit from me!
}
static serializeObject(typedObject: object) {
return JSON.stringify(typedObject, (key, value) => {
if (value) {
const proto = Object.getPrototypeOf(value);
if (proto?.__typeName)
value.__typeName = proto.__typeName;
}
return value;
}
);
}
static deserializeObject(typedObject: object, jsonObject: object): object {
const typeName = typedObject.__typeName;
return Object.assign(typedObject, this.assignTypeRecursion(typeName, jsonObject));
}
private static assignTypeRecursion(typeName, object): object {
const theObject = new Serializable.__serializableObjects[typeName]();
Object.assign(theObject, object);
const props = Object.getPrototypeOf(theObject).__serializedProps;
for (const property in props) {
const type = props[property];
try {
if (type == Array.name) {
const obj = object[property];
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; ++i) {
const arrItem = obj[i];
obj[i] = Serializable.assignTypeRecursion(arrItem.__typeName, arrItem);
}
} else
object[property] = [];
} else
object[property] = Serializable.assignTypeRecursion(type, object[property]);
} catch (e) {
console.error(`${e.message}: ${type}`);
}
}
return theObject;
}
}
Comments
Since I am a total js/ts newby (< 10 days), I am more than happy to receive any input/comments/suggestions. Here are some of my thoughts so far:
It could be cleaner: Unfortunately I did not find a way to get rid of the redundant parameter of #SerializableProp.
It could be more memory friendly: After you call serializeObject() every object stores __typeName which could massively blow up memory footprint. Fortunately __serializedProps is only stored once per class.
It could be more CPU friendly: It's the most inefficient code I've ever written. But well, it's just for web apps, so who cares ;-) Maybe one should at least get rid of the recursion.
Almost no error handling: well that's a task for another day
class A {
constructor (a) {
this.a = a
}
method1 () {
console.log('hi')
}
}
var b = new A(1)
b.method1() // hi
var c = JSON.stringify(b)
var d = JSON.parse(c)
console.log(d.a) // 1
try {
d.method1() // not a function
} catch {
console.log('not a function')
}
var e = Object.setPrototypeOf(d, A.prototype)
e.method1() // hi
Olivers answers is very clear, but if you are looking for a solution in angular js, I have written a nice module called Angular-jsClass which does this ease, having objects defined in litaral notation is always bad when you are aiming to a big project but saying that developers face problem which exactly BMiner said, how to serialize a json to prototype or constructor notation objects
var jone = new Student();
jone.populate(jsonString); // populate Student class with Json string
console.log(jone.getName()); // Student Object is ready to use
https://github.com/imalhasaranga/Angular-JSClass

Crockford-style prototypal pattern gotcha; looking for an elegant solution

I often use Crockford's prototypal pattern when writing JavaScript programs. I thought I understood all the "gotchas" involved, but I discovered one I didn't think about before. I'd like to know if anyone has a best practice for handling it.
Here's a simple example:
// Here's the parent object
var MyObject = {
registry: {},
flatAttribute: null,
create: function () {
var o, F = function () {};
F.prototype = this;
o = new F();
return o;
}
};
// instance is an empty object that inherits
// from MyObject
var instance = MyObject.create();
// Attributes can be set on instance without modifying MyObject
instance.flatAttribute = "This is going to be applied to the instance";
// registry doesn't exist on instance, but it exists on
// instance.prototype. MyObject's registry attribute gets
// dug up the prototype chain and altered. It's not possible
// to tell that's happening just by examining this line.
instance.registry.newAttribute = "This is going to be applied to the prototype";
// Inspecting the parent object
// prints "null"
console.log(MyObject.flatAttribute);
// prints "This is going to be applied to the prototype"
console.log(MyObject.registry.newAttribute);
I want to feel safe that any changes that appear to be made to the instance don't propagate up the inheritance change. This is not the case when the attribute is an object and I'm setting a nested property.
A solution is to re-initialize all object attributes on the instance. However, one of the stated advantages of using this pattern is removing re-initialization code from the constructor. I'm thinking about cloning all the object attributes of the parent and setting them on the instance within the create() function:
{ create: function () {
var o, a, F = function () {};
F.prototype = this;
o = new F();
for (a in this) {
if (this.hasOwnProperty(a) && typeof this[a] === 'object') {
// obviously deepclone would need to be implemented
o[a] = deepclone(this[a]);
}
}
return o;
} };
Is there a better way?
There is a very simple solution to ensuring that they are instance variables only, which is to use the this keyword in the constructor.
var MyObject = {
flatAttribute: null,
create: function () {
var o, F = function () {
this.registry = {}
};
F.prototype = this;
o = new F();
return o;
}
};
this ensures that all properties of "instance.registry.*" are local to the instance because the lookup order for javascript opjects is as follows.
object -> prototype -> parent prototype ...
so by adding a variable to the instance in the constructor function named "registry" that will always be found first.
another solution, which I think is more elegant is to not use crockford's (java style) constructors and use a layout that reflects javascripts object system more naturally. most of those gotchas are from the misfit between practice and language.
// instance stuff
var F = function () {
this.registry = {}
};
F.prototype = {
// static attributes here
flatAttribute: null,
methodA: function(){
// code here 'this' is instance object
this.att = 'blah';
}
};
var instanceA = new F();
instanceA.registry['A'] = 'hi';
var instanceB = new F();
instanceB.registry['B'] = 'hello';
instanceA.registry.A == 'hi'; // true
instanceB.registry.B == 'hello'; // true
F.prototype.registry == undefined; // true
Will this give you the expected result? Here I am not using an Object literal, but an instantly instantiated constructor function for the parent object (Base):
var Base = ( function(){
function MyObject(){
this.registry = {},
this.flatAttribute = null;
if (!MyObject.prototype.create)
MyObject.prototype.create = function(){
return new this.constructor();
};
}
return new MyObject;
} )(),
// create 2 instances from Base
instance1 = Base.create(),
instance2 = Base.create();
// assign a property to instance1.registry
instance1.registry.something = 'blabla';
// do the instance properties really belong to the instance?
console.log(instance1.registry.something); //=> 'blabla'
console.log(instance2.registry.something === undefined); //=> true
But it's all a bit virtual. If you don't want to use the new operator (I think that was te whole idea of it), the following offers you a way to do that without the need for a create method :
function Base2(){
if (!(this instanceof Base2)){
return new Base2;
}
this.registry = {},
this.flatAttribute = null;
if (!Base2.prototype.someMethod){
var proto = Base2.prototype;
proto.someMethod = function(){};
//...etc
}
}
//now the following does the same as before:
var instance1 = Base2(),
instance2 = Base2();
// assign a property to instance1.registry
instance1.registry.something = 'blabla';
// do the instance properties really belong to the instance?
console.log(instance1.registry.something); //=> 'blabla'
console.log(instance2.registry.something === undefined); //=> true
Example in a jsfiddle
I always like to keep in mind that object.Create is one option, and not the only way of achieving non-classical inheritance in javascript.
For myself, I always find that Object.create works best when I want to inherit elements from the parent objects prototype chain (i.e. methods that I'd like to be able to apply to the inheriting object).
--
For simple "Own Property" inheritance, Object.create is largely unnecessary. When I want to inherit own properties, i prefer to use the popular Mixin & Extend patterns (which simply copy one object's own properties to another, without worrying about prototype or "new").
In the Stoyan Stefanov book "Javascript Patterns" he gives an example of a deep extend function that does what you're looking for recursively, and includes support for properties that are arrays as well as standard key/value objects:
function extendDeep(parent, child){
var i,
toStr = Object.prototype.toString,
astr = "[object Array]";
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
if (typeof parent[i] === "object") {
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}
If you're using jQuery, jQuery.extend() has an optional "deep" argument that lets you extend an object in near-identical fashion.
i think you're using prototypal inheritance to simulate a classic, Object Oriented inheritance.
What are you trying to do is to stop the prototype method lookup which limits its expressiveness, so why using it? You can achieve the same effect by using this functional pattern:
var MyObject = function() {
// Declare here shared vars
var global = "All instances shares me!";
return {
'create': function() {
var flatAttribute;
var register = {};
return {
// Declare here public getters/setters
'register': (function() {
return register;
})(),
'flatAttribute': (function() {
return flatAttribute;
})(),
'global': (function() {
return global;
})()
};
}
};
}();
var instance1 = MyObject.create();
var instance2 = MyObject.create();
instance1.register.newAttr = "This is local to instance1";
instance2.register.newAttr = "This is local to instance2";
// Print local (instance) var
console.log(instance1.register.newAttr);
console.log(instance2.register.newAttr);
// Print global var
console.log(instance1.global);
console.log(instance2.global);
Code on jsFiddle

Categories

Resources