Purpose of moving data through JSON - javascript

JSON.stringify(value) will convert a JavaScript value to JSON string. JSON.parse(value) will convert a valid JSON string into a JavaScript value (object, array or other JSON-available primitive).
Why would you take a JavaScript value and move it through JSON ? Means take a value then stringify it and then parse it back. It serves no purpose to my knowledge and just wastes machine resources.
I'm asking this is because I've come across this function:
function ser(value) {
return value == null ? null : JSON.parse(JSON.stringify(value))
}
it's found in crow-tech.js in sandbox of Chapter 11 of book Eloquent JavaScript by Marijn Heverbeke and I'm wondering why would he wanna do that!

This is a cheap way to deep clone an object in JavaScript. Consider the following:
function a() {
const obj = {name: "fred"};
b(obj);
console.log(obj);
}
function b(obj) {
obj.age = 42;
}
a();
The function a passes an object to b and when it gets modified. Sometimes that is what you want, other times, you want to preserve the original object from modifications, so you have to clone it. JavaScript doesn't have any facility to do that but JSON.stringify -> JSON.parse will create a new object for you so it be used for cloning:
function a() {
const obj = {name: "fred"};
b(JSON.parse(JSON.stringify(obj)));
console.log(obj);
}
function b(obj) {
obj.age = 42;
}
a();
This is a good illustration of what can get wrong, but in the real world, it's not always so simple. The above can also be done through a shallow clone of the object
function a(){
const obj = {name: "fred"};
b(Object.assign({}, obj)); //shallow clone
console.log(obj);
}
function b(obj) {
obj.age = 42;
}
a();
However, that will fail in the following scenario:
const obj = {
name: "fred",
child: { name: "pebbles" }
};
const objClone = Object.assign({}, obj)
objClone.age = 42;
objClone.child.age = 2;
console.log('the "cloned" object was modified', objClone);
console.log("so was the original nested object", obj);
This is because the nested object child was not cloned, so we modified the top-level clone of the parent but then modified the original child. Here is when deep cloning is useful:
const obj = {
name: "fred",
child: { name: "pebbles" }
};
const objClone = JSON.parse(JSON.stringify(obj));
objClone.age = 42;
objClone.child.age = 2;
console.log('the cloned object was modified', objClone);
console.log("none of the original was", obj);
This doesn't solve all problems. JSON.stringify and then JSON.parse only works with very simple objects anywhere, it will not copy prototypes or functions. Some examples:
const obj = { name: "Alice" };
const proto = { age: 42 };
Object.setPrototypeOf(obj, proto);
console.log("assembled object", obj);
const clone = JSON.parse(JSON.stringify(obj));
console.log("cloned object", clone);
const objWithFunction = { name: "Bob", getAge: function() { return 42; } };
console.log("object with function assigned to property", objWithFunction);
console.log("getAge from original", objWithFunction.getAge());
const cloneWithFunction = JSON.parse(JSON.stringify(objWithFunction));
console.log("cloned object with function assigned to property", cloneWithFunction);
console.log("getAge from clone", cloneWithFunction.getAge());
function Person(name, age) {
this.name = name;
this.age = age;
}
const p = new Person("Carol", 42);
console.log("what is p:", p);
console.log("is p a Person?", p instanceof Person);
const clone = JSON.parse(JSON.stringify(p));
console.log("what is clone:", clone);
console.log("is clone a Person?", clone instanceof Person);
console.log("the clone is actually a plain object:", Object.getPrototypeOf(clone) == Object.prototype);

JSON.parse(JSON.stringify(value))
This is mostly used to clone an object. You cannot directly copy a object to another variable because objects are copied by reference.
let user = {name : "John"}
let newUser = user; // newUser and user refer to same object (ie same memory location.) .
If you modify user then newUser will also get modified. This is because objects are copied via reference not by value.
So to do a object copy we use the method you specified.
Example
let value = {"name": "John"}; // this is a object.
JSON.stringify(value) // were are changing the type object to string
Strings can be copied by value.
Now are we need to again convert string to Object, for that we use
let newValue = JSON.parse(JSON.stringify(value));
This returns a object - a clone of value
Now value and newValue are two different objects.

Related

Is there a way to force a javascript element to redefine itself based on how it was originally defined?

I was playing around with objects and constructors and stuff like that, and I was wondering if there was a way to bind a value to a variable based on how it was originally defined. I have the following code:
typescript
let cr = "create",
ap = "apply",
$this = {
set: (prop, value) => {
this[prop] = value;
}
};
function creator() {
this.$ = (array: Object[]) => {
array.forEach((kp: Object) => {
let key = Object.keys(kp)[0];
let val = kp[Object.keys(kp)];
$this[key] = val;
creator.create(key, { value: val });
});
};
this.apply = (...objects: Object[]) => {
objects.forEach((obj: Object) => {
creator.call(obj);
});
};
}
function create(obj) {
function createValues(arr) {
let instance: Object = new obj();
let vals: any[] = [];
arr.forEach(name => {
vals.push(instance[name]);
});
return vals;
}
let names: string[] = Object.getOwnPropertyNames(new obj());
let values: string[] = createValues(names);
return combineArrays(names, values);
}
function combineArrays(arr1, arr2): { $?: any } { // the question mark removes an IDE error
let newObj: Object = {};
arr1.forEach(prop => {
newObj[prop] = arr2[arr1.indexOf(prop)];
});
return newObj;
}
Object.prototype.create = function(prop, options) {
return Object.defineProperty(this, prop, options);
};
create(creator).$([
{ hi: "hi" },
{ bye: $this["hi"] } // this is the important stuff
]);
I was wondering if there is a way, inside the set function of the $this variable, to detect how it is being set and therefore determine if that value has changed and so it's value should to, if that makes any sense? Let's say you had this:
let $this = {
set: function(prop, value) {
this[prop] = value;
}
}
let name = 'Ezra';
$this['name'] = name;
// then :
name = 'Bob';
// change $this.name too, so then:
console.log($this.name);
// >> 'Bob'
I believe this is called Data-Binding but I am unsure how to do it without creating endless numbers of proxies.
What you're describing is not really "data-binding" but pass-by-reference. In your example you expect an update to name to be reflected in $this['name']. That would only be possible if you were passing a reference (or a pointer) to the variable.
However, in this case the variable is a string, and strings in JavaScript are immutable:
no string methods change the string they operate on, they all return new strings. The reason is that strings are immutable – they cannot change, we can only ever make new strings.
So, going step-by-step through your example:
This creates a new string named 'Ezra', and assigns a variable called name to reference that string.
let name = 'Ezra';
This creates (or sets) a property in $this called 'name' that references the string in name (which is 'Ezra').
$this['name'] = name;
This creates a new string called 'Bob' and stores it in the variable called name. The variable already exists. This does not mutate the value that was previously held in there. Instead, name is being updated to point to a new reference.
// then :
name = 'Bob';
However, if you were to pass an object, you'll notice that you can actually mutate it. This is because objects are passed-by-reference and you can mutate the value at that reference.
For example:
// Create a new value that stores an object, with a property 'firstName'
let name = { firstName: 'Ezra' };
// Assign myObject to $this['name']. Now $this['name'] and name both point to the same reference.
$this['name'] = name;
// Change the value in the object pointed-to by name
name.firstName = 'Bob'
console.log($this['name'].firstName); // <- This will output 'Bob'

Why are getter/setter no longer working after copying an object with the spread syntax?

In the following snippet, the spread syntax works in a manner that I don't quite understand:
let obj = {
set setName(name){
obj.name = name
},
get myName() {
return obj.name
}
}
obj.setName = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.setName = 'Marion Luke'
console.log('spread_obj name', spread_obj.myName) // spread_obj name Jon Doe
let create_obj = Object.create(obj)
create_obj.setName = 'Marion Luke'
console.log('create_obj name', create_obj.myName) // create_obj name Marion Luke
Can you explain why the reassignment of the name does not work in such a case?
Spreading an object does not copy getter and setter methods - rather, the spread operation only gets the property (that is, it calls the getter, if there is one), and assigns the result to the resulting object (the spread_obj, here), just like a normal object, without any getters or setters. You can see this if you put log statements inside the setter and getter. The resulting spread_obj has a setName property of undefined because setName is only a setter method, which doesn't return anything.
let obj = {
set setName(name){
console.log('setting ' + name);
this.name = name
},
get myName() {
console.log('getting');
return this.name
}
}
obj.setName = 'Jon Doe'
console.log('About to spread');
let spread_obj = {...obj}
console.log('Done spreading. spread_obj contents is:');
console.log(spread_obj);
spread_obj.setName = 'Marion Luke' // No "setting" log statement
Also note that if you want to use the second part of your code, you should probably change the setter and getter methods to refer to this rather than to obj, otherwise the results could be unintuitive; your getter and setter methods area always referring to the .name property on obj, rather than the .name property on the standard calling context (that is, to spread_obj or to create_obj). When you try to assign to spread_obj.setName, you actually change the original obj's .name property:
let obj = {
set setName(name){
obj.name = name
},
get myName() {
return obj.name
}
}
obj.setName = 'Jon Doe'
let create_obj1 = Object.create(obj)
create_obj1.setName = 'Marion Luke1'
let create_obj2 = Object.create(obj)
create_obj2.setName = 'Marion Luke2'
// "Marion Luke2", but we're logging the property from the "first" object!
console.log('create_obj name', create_obj1.name)
Fix by referring to this instead of obj:
let obj = {
set setName(name){
this.name = name
},
get myName() {
return this.name
}
}
obj.setName = 'Jon Doe'
let create_obj1 = Object.create(obj)
create_obj1.setName = 'Marion Luke1'
let create_obj2 = Object.create(obj)
create_obj2.setName = 'Marion Luke2'
console.log(create_obj1.name)
In addition to #CertainPerformances explanation, I would add that the behavior seems more sensible when using the getters/setters a more traditionally by having one name for both get and set.
For example when your object looks like this, everything works a little better (at least on the surface):
let obj = {
set name(name){ // setter and getter are accessed with the same property
this._name = name
},
get name() {
return this._name
}
}
obj.name = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.name = 'Marion Luke'
// this seems more expected
console.log('spread_obj name', spread_obj.name)
// but this is still a little strange because
// Jon Doe is still there
console.log(spread_obj)
It's seems like a lot of work, but since object spread only takes enumerable properties, you can make _name non-enumerable. Then everything seems a little more sensible:
let obj = {
set name(name){
this._name = name
},
get name() {
return this._name
}
}
Object.defineProperty(obj, '_name', {
enumerable: false,
writable: true,
configurable: true
});
obj.name = 'Jon Doe'
let spread_obj = {...obj}
spread_obj.name = 'Marion Luke'
// now spread_obj only has a name prop:
console.log(spread_obj)
// and obj too:
console.log(obj)
the select answer is wrong.
getter/setter is not method, it is special properties.
...spread and object.assign will not work, just because they treat getter/setter like normal enumerable property, they
copy the 'value' of it, so getter/setter will fail.
if you want to copy it, you can refer undercore.js the extend method:
this is my assign code:
const assign = (ob, ...o) => {
o.forEach(obj=>{if (typeof obj !== 'undefined')
Object.defineProperties(ob, Object.getOwnPropertyDescriptors(obj));})
return ob;
};

Stringify (convert to JSON) a JavaScript object with circular reference

I've got a JavaScript object definition which contains a circular reference: it has a property that references the parent object.
It also has functions that I don't want to be passed through to the server. How would I serialize and deserialize these objects?
I've read that the best method to do this is to use Douglas Crockford's stringify. However, I'm getting the following error in Chrome:
TypeError: Converting circular structure to JSON
The code:
function finger(xid, xparent){
this.id = xid;
this.xparent;
//other attributes
}
function arm(xid, xparent){
this.id = xid;
this.parent = xparent;
this.fingers = [];
//other attributes
this.moveArm = function() {
//moveArm function details - not included in this testcase
alert("moveArm Executed");
}
}
function person(xid, xparent, xname){
this.id = xid;
this.parent = xparent;
this.name = xname
this.arms = []
this.createArms = function () {
this.arms[this.arms.length] = new arm(this.id, this);
}
}
function group(xid, xparent){
this.id = xid;
this.parent = xparent;
this.people = [];
that = this;
this.createPerson = function () {
this.people[this.people.length] = new person(this.people.length, this, "someName");
//other commands
}
this.saveGroup = function () {
alert(JSON.stringify(that.people));
}
}
This is a test case that I created for this question. There are errors within this code but essentially I have objects within objects, and a reference passed to each object to show what the parent object is when the object is created. Each object also contains functions, which I don't want stringified. I just want the properties such as the Person.Name.
How do I serialize before sending to the server and deserialize it assuming that the same JSON is passed back?
Circular structure error occurs when you have a property of the object which is the object itself directly (a -> a) or indirectly (a -> b -> a).
To avoid the error message, tell JSON.stringify what to do when it encounters a circular reference.
For example, if you have a person pointing to another person ("parent"), which may (or may not) point to the original person, do the following:
JSON.stringify( that.person, function( key, value) {
if( key == 'parent') { return value.id;}
else {return value;}
})
The second parameter to stringify is a filter function. Here it simply converts the referred object to its ID, but you are free to do whatever you like to break the circular reference.
You can test the above code with the following:
function Person( params) {
this.id = params['id'];
this.name = params['name'];
this.father = null;
this.fingers = [];
// etc.
}
var me = new Person({ id: 1, name: 'Luke'});
var him = new Person( { id:2, name: 'Darth Vader'});
me.father = him;
JSON.stringify(me); // so far so good
him.father = me; // time travel assumed :-)
JSON.stringify(me); // "TypeError: Converting circular structure to JSON"
// But this should do the job:
JSON.stringify(me, function( key, value) {
if(key == 'father') {
return value.id;
} else {
return value;
};
});
BTW, I'd choose a different attribute name to "parent" since it is a reserved word in many languages (and in DOM). This tends to cause confusion down the road...
No-lib
Use below replacer to generate json with string references (similar to json-path) to duplicate/circular referenced objects
let s = JSON.stringify(obj, refReplacer());
function refReplacer() {
let m = new Map(), v= new Map(), init = null;
return function(field, value) {
let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
let isComplex= value===Object(value)
if (isComplex) m.set(value, p);
let pp = v.get(value)||'';
let path = p.replace(/undefined\.\.?/,'');
let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;
!init ? (init=value) : (val===init ? val="#REF:$" : 0);
if(!pp && isComplex) v.set(value, path);
return val;
}
}
// ---------------
// TEST
// ---------------
// gen obj with duplicate references
let a = { a1: 1, a2: 2 };
let b = { b1: 3, b2: "4" };
let obj = { o1: { o2: a }, b, a }; // duplicate reference
a.a3 = [1,2,b]; // circular reference
b.b3 = a; // circular reference
let s = JSON.stringify(obj, refReplacer(), 4);
console.log(s);
And following parser function to regenerate object from such "ref-json"
function parseRefJSON(json) {
let objToPath = new Map();
let pathToObj = new Map();
let o = JSON.parse(json);
let traverse = (parent, field) => {
let obj = parent;
let path = '#REF:$';
if (field !== undefined) {
obj = parent[field];
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
}
objToPath.set(obj, path);
pathToObj.set(path, obj);
let ref = pathToObj.get(obj);
if (ref) parent[field] = ref;
for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
}
traverse(o);
return o;
}
// ------------
// TEST
// ------------
let s = `{
"o1": {
"o2": {
"a1": 1,
"a2": 2,
"a3": [
1,
2,
{
"b1": 3,
"b2": "4",
"b3": "#REF:$.o1.o2"
}
]
}
},
"b": "#REF:$.o1.o2.a3[2]",
"a": "#REF:$.o1.o2"
}`;
console.log('Open Chrome console to see nested fields:');
let obj = parseRefJSON(s);
console.log(obj);
It appears that dojo can represent circular references in JSON in the form : {"id":"1","me":{"$ref":"1"}}
Here is an example:
http://jsfiddle.net/dumeG/
require(["dojox/json/ref"], function(){
var me = {
name:"Kris",
father:{name:"Bill"},
mother:{name:"Karen"}
};
me.father.wife = me.mother;
var jsonMe = dojox.json.ref.toJson(me); // serialize me
alert(jsonMe);
});​
Produces:
{
"name":"Kris",
"father":{
"name":"Bill",
"wife":{
"name":"Karen"
}
},
"mother":{
"$ref":"#father.wife"
}
}
Note: You can also de-serialize these circular referenced objects using the dojox.json.ref.fromJson method.
Other Resources:
How to serialize DOM node to JSON even if there are circular references?
JSON.stringify can't represent circular references
I found two suitable modules to handle circular references in JSON.
CircularJSON https://github.com/WebReflection/circular-json whose output can be used as input to .parse(). It also works in Browsers & Node.js Also see: http://webreflection.blogspot.com.au/2013/03/solving-cycles-recursions-and-circulars.html
Isaacs json-stringify-safe https://github.com/isaacs/json-stringify-safe which maybe more readable but can't be used for .parse and is only available for Node.js
Either of these should meet your needs.
Happened upon this thread because I needed to log complex objects to a page, since remote debugging wasn't possible in my particular situation. Found Douglas Crockford's (inceptor of JSON) own cycle.js, which annotates circular references as strings such that they can be reconnected after parsing. The de-cycled deep copy is safe to pass through JSON.stringify. Enjoy!
https://github.com/douglascrockford/JSON-js
cycle.js: This file contains two functions, JSON.decycle and
JSON.retrocycle, which make it possible to encode cyclical structures
and dags in JSON, and to then recover them. This is a capability that
is not provided by ES5. JSONPath is used to represent the links.
I used the following to eliminate the circular references:
JS.dropClasses = function(o) {
for (var p in o) {
if (o[p] instanceof jQuery || o[p] instanceof HTMLElement) {
o[p] = null;
}
else if (typeof o[p] == 'object' )
JS.dropClasses(o[p]);
}
};
JSON.stringify(JS.dropClasses(e));

JavaScript: How to pass object by value?

When passing objects as parameters, JavaScript passes them by reference and makes it hard to create local copies of the objects.
var o = {};
(function(x){
var obj = x;
obj.foo = 'foo';
obj.bar = 'bar';
})(o)
o will have .foo and .bar.
It's possible to get around this by cloning; simple example:
var o = {};
function Clone(x) {
for(p in x)
this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
}
(function(x){
var obj = new Clone(x);
obj.foo = 'foo';
obj.bar = 'bar';
})(o)
o will not have .foo or .bar.
Question
Is there a better way to pass objects by value, other than creating a local copy/clone?
Not really.
Depending on what you actually need, one possibility may be to set o as the prototype of a new object.
var o = {};
(function(x){
var obj = Object.create( x );
obj.foo = 'foo';
obj.bar = 'bar';
})(o);
alert( o.foo ); // undefined
So any properties you add to obj will be not be added to o. Any properties added to obj with the same property name as a property in o will shadow the o property.
Of course, any properties added to o will be available from obj if they're not shadowed, and all objects that have o in the prototype chain will see the same updates to o.
Also, if obj has a property that references another object, like an Array, you'll need to be sure to shadow that object before adding members to the object, otherwise, those members will be added to obj, and will be shared among all objects that have obj in the prototype chain.
var o = {
baz: []
};
(function(x){
var obj = Object.create( x );
obj.baz.push( 'new value' );
})(o);
alert( o.baz[0] ); // 'new_value'
Here you can see that because you didn't shadow the Array at baz on o with a baz property on obj, the o.baz Array gets modified.
So instead, you'd need to shadow it first:
var o = {
baz: []
};
(function(x){
var obj = Object.create( x );
obj.baz = [];
obj.baz.push( 'new value' );
})(o);
alert( o.baz[0] ); // undefined
Check out this answer https://stackoverflow.com/a/5344074/746491 .
In short, JSON.parse(JSON.stringify(obj)) is a fast way to copy your objects, if your objects can be serialized to json.
Here is clone function that will perform deep copy of the object:
function clone(obj){
if(obj == null || typeof(obj) != 'object')
return obj;
var temp = new obj.constructor();
for(var key in obj)
temp[key] = clone(obj[key]);
return temp;
}
Now you can you use like this:
(function(x){
var obj = clone(x);
obj.foo = 'foo';
obj.bar = 'bar';
})(o)
Use Object.assign()
Example:
var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.
A cleaner example that does the same thing:
var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Use this
x = Object.create(x1);
x and x1 will be two different object,change in x will not change x1
You're a little confused about how objects work in JavaScript. The object's reference is the value of the variable. There is no unserialized value. When you create an object, its structure is stored in memory and the variable it was assigned to holds a reference to that structure.
Even if what you're asking was provided in some sort of easy, native language construct it would still technically be cloning.
JavaScript is really just pass-by-value... it's just that the value passed might be a reference to something.
ES6
Using the spread operator like obj2 = { ...obj1 }
Will have same values but different references
ES5
Use Object.assign obj2 = Object.assign({}, obj1)
Javascript always passes by value. In this case it's passing a copy of the reference o into the anonymous function. The code is using a copy of the reference but it's mutating the single object. There is no way to make javascript pass by anything other than value.
In this case what you want is to pass a copy of the underlying object. Cloning the object is the only recourse. Your clone method needs a bit of an update though
function ShallowCopy(o) {
var copy = Object.create(o);
for (prop in o) {
if (o.hasOwnProperty(prop)) {
copy[prop] = o[prop];
}
}
return copy;
}
As a consideration to jQuery users, there is also a way to do this in a simple way using the framework. Just another way jQuery makes our lives a little easier.
var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy = jQuery.extend(true, {}, o);
references :
http://api.jquery.com/jquery.extend/
https://stackoverflow.com/a/122704/1257652
and to dig into the source.. http://james.padolsey.com/jquery/#v=1.8.3&fn=jQuery.extend
Actually, Javascript is always pass by value. But because object references are values, objects will behave like they are passed by reference.
So in order to walk around this, stringify the object and parse it back, both using JSON. See example of code below:
var person = { Name: 'John', Age: '21', Gender: 'Male' };
var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object
var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects
I needed to copy an object by value (not reference) and I found this page helpful:
What is the most efficient way to deep clone an object in JavaScript?. In particular, cloning an object with the following code by John Resig:
//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
With the ES6 syntax:
let obj = Object.assign({}, o);
When you boil down to it, it's just a fancy overly-complicated proxy, but maybe Catch-All Proxies could do it?
var o = {
a: 'a',
b: 'b',
func: function() { return 'func'; }
};
var proxy = Proxy.create(handlerMaker(o), o);
(function(x){
var obj = x;
console.log(x.a);
console.log(x.b);
obj.foo = 'foo';
obj.bar = 'bar';
})(proxy);
console.log(o.foo);
function handlerMaker(obj) {
return {
getOwnPropertyDescriptor: function(name) {
var desc = Object.getOwnPropertyDescriptor(obj, name);
// a trapping proxy's properties must always be configurable
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
getPropertyDescriptor: function(name) {
var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
// a trapping proxy's properties must always be configurable
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
getOwnPropertyNames: function() {
return Object.getOwnPropertyNames(obj);
},
getPropertyNames: function() {
return Object.getPropertyNames(obj); // not in ES5
},
defineProperty: function(name, desc) {
},
delete: function(name) { return delete obj[name]; },
fix: function() {}
};
}
If you are using lodash or npm, use lodash's merge function to deep copy all of the object's properties to a new empty object like so:
var objectCopy = lodash.merge({}, originalObject);
https://lodash.com/docs#merge
https://www.npmjs.com/package/lodash.merge

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

Categories

Resources