best way to attach properties of an object to another object - javascript

Is there an easy way to copy the properties of one object into another object.
for example
var a = {
afunc: function(bla) {
alert('hello ' + bla);
},
avalue: 'aaa'
};
var b = {
bfunc: function(blb) {
alert('hi ' +blb);
},
bvalue: 'bbb'
};
magically_copy_a_to_b(a,b);
b.afunc('world');
should print hello world

Enumerate through the properties of a, then set the properties at b. Note: Objects are passed by reference, and not copied.
Copying:
Numbers, strings, booleans - COPIED
Arrays: Use copiedArray = array.slice() to copy Array array
Simple functions (which do not refer to the scope, such as in your example):copiedFunc = eval(originalFunc.toString())
function magically_copy_a_to_b(a,b){
for(var prop in a){
/* Example: copying all string properties*/
if(typeof prop[a] == "string"){
b[prop] = prop[a];
}
}
}

You can use underscore.js which has a built-in extend function:
_.extend(b, a); // merge a into b
Then you can do:
b.afunc('hello');

i think you are making a lot of confusion, anyway i corrected your objects (no var and use :), and this works by enumerating the properties of one object and adding them to the other (avoiding the prototype chain)
function magically_copy_a_to_b(org, dest){
for (var prop in org){
if (org.hasOwnProperty(prop)){
dest[prop] = org[prop];
}
}
return dest;
}
var a = {
afunc: function(bla) {
alert('hello ' + bla);
},
avalue: 'aaa'
};
var b = {
bfunc: function(blb) {
alert('hi ' +blb);
},
bvalue: 'bbb'
};
b = magically_copy_a_to_b(a,b);
b.afunc('world');

This method permanently adds the properties of a json object to another. Because JS is a scripting language, the "eval" function can save the day with a shorter code! Finally, I'm using json because in the "for (var obj1 in obj2)" it returns every keys defined into obj1, and it is basically more funny to deal with json in a bigger code than with a simple array. For example, it's really easy to sort an array of json objects, and you don't have to use the evil "i" to get something into it.
function magically_copy_properties(jsonFrom,jsonTo) {
for (var key in jsonFrom) {
eval([
"jsonTo.",key,"=jsonFrom.",key
].join(""));
}
}
var a = {
"a1": function() {
return "hello";
},
"a2": function() {
return "hey";
},
"aSpace": function() {
return " ";
}
}
var b = {
"b1": function() {
return "ho";
},
"b2": function() {
return "world";
}
}
magically_copy_properties(a,b);
alert(b.a1() + b.aSpace() + b.b2());

Related

Why the strange duplication in Javascript instance objects?

While using Javascript for some fun, I ran into a morass of difficulties surrounding classes and objects. I thought I got that it is not Java, nor c++. Finally, I took my problems into a smallish example to study this.
Running the example produces output:
>>>>>> keys aprop,bprep,cprep,dprop,bprop,cprop.
I expected output:
>>>>>> keys aprop,bprep,cprep,dprop.
If duplication, why are not aprop and dprop also duplicated? I had thought that by using getOwnPropertyNames I prevented prototype keys. Is there a way to fix this short of comparing each key to eliminate duplicates?
<script>
var objList = [];
class TestObj {
constructor(aprop, bprop, cprop) {
this.aprop = aprop;
this.bprep = bprop;
this.cprep = cprop;
this.dprop = 0;
}
getAprop() { return aprop; }
getBprop() { return bprop; }
getCprop() { return cprop; }
getDprop() { return dprop; }
setAprop(arg) { aprop = arg; }
setBprop(arg) { bprop = arg; }
setCprop(arg) { cprop = arg; }
setDprop(arg) { dprop = arg; }
}
function create() {
putList();
var t1 = objList[1];
alert("fromlist "+t1);
var keys = Object.getOwnPropertyNames(t1);
console.log(">>>>>> keys "+keys);
}
function putList() {
onlist(1, "one" [1]);
onlist(2, "two", [2, 2]);
onlist(3, "three", [3, 3, 3]);
}
function onlist(a, b, c) {
var item = new TestObj();
item.aprop = a;
item.bprop = b;
item.cprop = c;
objList.push(item);
}
create();
</script>
I hope this is actually your question. Anyway, here goes:
First, a minimal example:
function Foo() {
this.bar = 0;
}
Object.keys(new Foo)
The result of this expression is ["bar"] instead of [] because setting properties in a constructor is no different from setting them anywhere else. They’re not on the prototype just by virtue of being in a constructor. This would work:
function Foo() {}
Foo.prototype.bar = 0;
Object.keys(new Foo)
Now bar is on the prototype, and Object.keys, which only lists own property names, returns [] for a Foo instance. This isn’t usually what you want, though; assigning to bar on an instance will still create a new own property, and if it were mutable, then mutating it would affect all instances of Foo.
Is there a way to fix this short of comparing each key to eliminate duplicates?
Don’t loop over properties in the first place.
… and on the off-chance you really mean “duplicates”, note that you spelled them differently: “bprep” and “cprep” vs. “bprop” and “cprop”.

How to access a nested property, defined as a string, without using eval?

I have an object which may or may not have nested objects and properties, and I want to access them using a string. Here's an example...
var obj = {
inside: {
value: 10,
furtherInside: {
value: 100
}
}
// may contain other objects, properties, etc.
};
function getObjProperty(str) {
return eval("obj." + str);
}
getObjProperty("inside.value"); // returns 10
getObjProperty("inside.furtherInside.value"); // returns 100
...But I'd like a solution that doesn't use eval.
How can this be done without using eval? I'm looking for the best/optimal/fastest solution.
How about something like
function getObjectProperty(obj, str) {
var props = str.split('.')
var result = obj;
for(var i = 0; i < props.length; i++)
result = result[props[i]];
return result;
}
This code assumes your strings are always valid and the object passed into getObjectProperty has properties that nest to the level you target, but it avoids eval. You could make it more robust with checks for undefined, but that may be overkill for what you need.
Test code, using your example:
var a = {
inside: {
value: 10,
furtherInside: {
value: 100
}
}
// may contain other objects, properties, etc.
};
console.log(getObjProperty(a, "inside.value")); // prints 10
console.log(getObjProperty(a, "inside.furtherInside.value")); // prints 100
You can use the brackets notation:
var obj = {
inside: {
value: 10,
furtherInside: {
value: 100
}
}
// may contain other objects, properties, etc.
};
alert(obj['inside']['furtherInside']['value']);
Then you may even use string properties like "my property":
var obj = {
"my property": 10
};
obj["my property"];
EDIT:
This is an approach (using brackets notation) to what you are asking for:
String.prototype.getVal = function(elem) {
var segments = this.split('.');
for (var i = 0; i < segments.length; i++) {
elem = elem[segments[i]];
}
return elem;
}
var obj = {
inside: {
value: 10,
furtherInside: {
value: 100
}
}
// may contain other objects, properties, etc.
};
console.log("inside.furtherInside.value".getVal(obj));
console.log("inside.value".getVal(obj));
http://jsfiddle.net/luismartin/kphtqd54
Since this method getVal() is being assigned to the String prototype, you may use it anywhere, and I think the implementation is pretty neat and fast. I hope this approach also helps getting rid of the negative vote :/
This is what I came up with, using some recursiveness...
function getObjProperty(obj, props) {
if (typeof props === 'string') {
if (props.indexOf('.') == -1) {
return obj[props];
} else {
props = props.split('.');
}
}
if (props.length == 1) {
return obj[props[0]];
} else if (props.length > 1) {
var top = props.shift();
return getObjProperty(obj[top], props);
} else {
return obj;
}
}
http://jsfiddle.net/0em2f6k6/
...But it's not as fast as a simple for-loop. http://jsperf.com/0em2f6k6
Although not vanilla JavaScript, another possibility is to use lodash's _.get function: https://lodash.com/docs#get.
_.get(obj, "inside.furtherInside.value");
It essentially does the same as #analytalica 's solution, except using a while loop (see the baseGet function in the lodash code), but it also allows strings or arrays (using the toPath function), and allows you to include a default.

Set of objects in javascript

I'd like to have a set of objects in Javascript. That is, a data structure that contains only unique objects.
Normally using properties is recommended, e.g. myset["key"] = true. However, I need the keys to be objects. I've read that Javascript casts property names to strings, so I guess I can't use myset[myobject] = true.
I could use an array, but I need something better than O(n) performance for adding, finding and removing items.
It needs to be able to tell objects apart by reference only, so given:
var a = {};
var b = {};
then both a and b should be able to be added, because they're separate objects.
Basically, I'm after something like C++'s std::set, that can store Javascript objects. Any ideas?
ES6 provides a native Set:
let s = new Set();
let a = {};
let b = {};
s.add(a);
console.log(s.has(a)); // true
console.log(s.has(b)); // false
Here's a mad suggestion ... key it on the result of JSON.stringify(object)
It's not possible for all objects, but if your object has a .toString() method implemented, it is:
var x = {toString: function(){ return 'foo'; }};
var y = {toString: function(){ return 'bar'; }};
var obj = {};
obj[x] = 'X';
obj[y] = 'Y';
console.log(obj);
// { foo: 'X', bar: 'Y' }
If you want to make this easier, make it a class:
function myObj(name){
this.name = name;
}
myObj.prototype.toString = function(){ return this.name; }
var obj = {};
obj[new myObj('foo')] = 'X';
obj[new myObj('bar')] = 'Y';
I'm answering my own question, but I came up with an alternative solution I thought was interesting and thought it would be useful to share it.
cwolves' answer gave me an idea. Providing an object's toString() method uniquely identifies the instance, properties of an object can be used to store a set of objects. Essentially, to store object x, you can use items[x.toString()] = x;. Note that the value is the object itself, so then the set of objects can be extracted by looking at all item's properties and dumping all the values in to an array.
Here's the class, which I call ObjectSet, in full. It requires objects are uniquely identified by their toString() method, which is OK for my purposes. add, remove and contains should all run in better than O(n) time - whatever javascript's property access efficiency is, which hopefully is either O(1) or O(n log n).
// Set of objects. Requires a .toString() overload to distinguish objects.
var ObjectSet = function ()
{
this.items = {};
this.item_count = 0;
};
ObjectSet.prototype.contains = function (x)
{
return this.items.hasOwnProperty(x.toString());
};
ObjectSet.prototype.add = function (x)
{
if (!this.contains(x))
{
this.items[x.toString()] = x;
this.item_count++;
}
return this;
};
ObjectSet.prototype.remove = function (x)
{
if (this.contains(x))
{
delete this.items[x.toString()];
this.item_count--;
}
return this;
};
ObjectSet.prototype.clear = function ()
{
this.items = {};
this.item_count = 0;
return this;
};
ObjectSet.prototype.isEmpty = function ()
{
return this.item_count === 0;
};
ObjectSet.prototype.count = function ()
{
return this.item_count;
};
ObjectSet.prototype.values = function ()
{
var i, ret = [];
for (i in this.items)
{
if (this.items.hasOwnProperty(i))
ret.push(this.items[i]);
}
return ret;
};
I used Map, solved my case
const objectsMap = new Map();
const placesName = [
{ place: "here", name: "stuff" },
{ place: "there", name: "morestuff" },
{ place: "there", name: "morestuff" },
];
placesName.forEach((object) => {
objectsMap.set(object.place, object);
});
console.log(objectsMap);
For what you're trying to do (sets of objects), there is no native Javascript implementation. You would have to implement this on your own. One way to do this would be to implement a hashing function for your objects. The backing data-type of the set would be an associative array, where the key of the array is the value you get from calling the object's hash function, and the value of the array is the object itself.
Of course, this doesn't address the issue that you highlighted, so you will need to take equality into account as well (implement an equals function perhaps)?
Instead of making the hash function a property of the object itself, you can have a standalone hash function that takes in an object as input and generates a hash value (presumably by iterating over its properties).
Using this method you should be able to get O(1) for insertion, searching, and removing (not counting the order of the hash function, which shouldn't be any worse than O(n), especially if you are iterating over its properties to create your hashed value).
ECMAScript6 Set should behave like that:
Standard: http://www.ecma-international.org/ecma-262/6.0/#sec-set-o-p-v-throw
Unofficial ES6 cheat sheet: https://github.com/lukehoban/es6features#map--set--weakmap--weakset
Working example on Firefox 32 (but not implemented in Chromium 37):
if (Set) {
var s = new Set()
var a = {}
var b = {}
var c = {}
s.add(a)
s.add(b)
s.add(b)
assert(s.size === 2)
assert(s.has(a))
assert(s.has(b))
assert(!s.has(c))
}
This is not surprising since {} != {}: equality compares object addresses by default.
A module that implements it for browsers without support: https://github.com/medikoo/es6-set
Javascript Set's don't do deep object comparison.
Using lodash, this is a unique array with deep object comparison:
const objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
_.uniqWith(objects, _.isEqual);
Just typed this up, it's only briefly tested:
var Set = function Set()
{
var list = [];
var contains;
this.contains = contains = function(x) {
return list.indexOf(x) >= 0;
}
var put;
this.put = put = function(x) {
if (!contains(x))
list.push(x);
return this;
}
var remove;
this.remove = remove = function(x)
{
var idx = list.indexOf(x);
if (idx >= 0)
list.splice(idx,1);
return this;
}
var all;
this.all = all = function()
{
return list.concat();
}
return this;
}
It seems that the inner call of function works when prefixed with this.
Exemple:
var put;
this.put = put = function(x) {
if (!this.contains(x))
list.push(x);
return this;
}
Please use this code as a reference.
const fruits = [
{name: 'apple', price: 100},
{name: 'apple', price: 100},
{name: 'orange', price: 200},
{name: 'grapes', price: 300}
];
const hasFruitDuplicated = () => {
const duplicatedDeleteFruits = fruits.filter((fruit, index) =>
fruits.findIndex(item => item.name === fruit.name && item.price === fruit.price) === index
);
return duplicatedDeleteFruits;
};
Given an array of the following type:
Array<{ foo: T1, bar: T2 }>
You can build a corresponding dictionary of type:
{ [foo: T1]: Set<T2> }
The look-up for { foo: fooValue, bar: barValue } can be performed as follows:
if (fooValue in dictionary && dictionary[fooValue].has(barValue))
This way we can build what would be an ObjectSet<T1, T2>
.
If you now have three elements, you can build the following dictionary:
{ [foo: T1]: ObjectSet<T2, T3> }
and extend your ObjectSet to any number of properties by induction.
That is assuming your types can be used as index signatures.

Merge two object literals in javascript

I have two object literals:
var animal = {
eat: function() {
console.log("eating...");
}
}
var dog = {
eat: "this has to be replaced when merged",
nrOfLegs: 4
}
Need a merging function like this:
dog = someMergingFunction(animal, dog);
That produces:
{
eat: function() {
console.log("eating...");
},
nrOfLegs: 4
}
One of the object literals has to replace identical properties.
How do I do this in Javascript?
The following should work:
function merge(obj1, obj2) {
var obj = {};
for (var x in obj1)
if (obj1.hasOwnProperty(x))
obj[x] = obj1[x];
for (var x in obj2)
if (obj2.hasOwnProperty(x))
obj[x] = obj2[x];
return obj;
}
If both objects have the same property, the value in obj2 takes precedence.
I highly recommend jQuery's extend method, as it will provide a full browser support.
var object = $.extend({}, object1, object2, ..., objectN);
Remember that the first argument is the target. The good point about usage of extend is that by following code, you can make it extend recursively:
var object = $.extend(object, object1, object2, ..., objectN);
See the jQuery's documentation for more info: jQuery Docs for Extend method
// usage merged = someMergingFunction(a, b, c, d, ...)
// keys in earlier args override keys in later args.
// someMergingFunction({foo:"bar"}, {foo:"baz"})
// ==> {foo:"bar"}
function someMergingFunction () {
var o = {}
for (var i = arguments.length - 1; i >= 0; i --) {
var s = arguments[i]
for (var k in s) o[k] = s[k]
}
return o
}
Assume properties of the first parameter will override properties of the 2nd parameter (as your example), this will do:
function merge(obj1, obj2) {
for(attr in obj1)
obj2[attr]=obj1[attr];
return obj2;
}
I recommend using underscore.js as it contains functionality for this and a whole load of related things:
_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}
http://underscorejs.org/#extend
As of 2017, I would use Object.assign(foo, bar)
What about spread syntax ?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }
This might be swatting a fly with a buick, but you might be interested to see how Dojo does basically the same thing in dojo.mixin (at least if I understood the question correctly).
https://github.com/dojo/dojo/blob/0dddc5a0bfe3708e4ba829434602da51cbb041b7/_base/_loader/bootstrap.js#L277-366
The basic functionality is in dojo._mixin, while dojo.mixin makes it work iteratively for multiple objects progressively in one shot.
Note that dojo.mixin operates in the opposite direction to what you hinted at in your example.
There are some good suggestions here.
I know this is a really old question, but for future visitors looking for a slightly more flexible solution, I have a similar function that I wrote that accepts any number of objects in an array and merges them all together and returns a single object with the properties of all of the object literals in the array.
Note: the order of precedence is determined by the array. Each subsequent object will overwrite identical properties if they exist in previous objects. Otherwise, new properties are simply added to the single object that is returned.
I hope this will help future visitors to this question. Here's the function, very short and sweet:
var mergeObjects = function (objectsArray) {
var result = {};
for (var i = 0; i < objectsArray.length; i++) {
for (var obj in objectsArray[i]) {
if (objectsArray[i].hasOwnProperty(obj)) {
result[obj] = objectsArray[i][obj];
};
};
};
return result;
};
You can use it like this:
// Define the mergeObjects function
var mergeObjects = function (objectsArray) {
var result = {};
for (var i = 0; i < objectsArray.length; i++) {
for (var obj in objectsArray[i]) {
if (objectsArray[i].hasOwnProperty(obj)) {
result[obj] = objectsArray[i][obj];
};
};
};
return result;
};
// Define some objects to merge, keeping one property consistent so you can
// see it overwrite the old ones
var obj1 = { test1: "test", overwrite: "overwrite1" };
var obj2 = { test2: "test2", overwrite: "overwrite2" };
var obj3 = { test3: "test3", overwrite: "overwrite3" };
// Merge the objects
var newObject = mergeObjects([obj1, obj2, obj3]);
// Test the output
for (var obj in newObject){
if (newObject.hasOwnProperty(obj)){
document.body.innerHTML += newObject[obj] + "<br />";
}
}

Easiest way to convert json data into objects with methods attached?

What's the quickest and easiest way to convert my json, containing the data of the objects, into actual objects with methods attached?
By way of example, I get data for a fruitbowl with an array of fruit objects which in turn contain an array of seeds thus:
{"fruitbowl": [{
"name": "apple",
"color": "red",
"seeds": []
},{
"name": "orange",
"color": "orange",
"seeds": [
{"size":"small","density":"hard"},
{"size":"small","density":"soft"}
]}
}
That's all nice and good but down on the client we do stuff with this fruit, like eat it and plant trees...
var fruitbowl = []
function Fruit(name, color, seeds){
this.name = name
this.color = color
this.seeds = seeds
this.eat = function(){
// munch munch
}
}
function Seed(size, density){
this.size = size
this.density = density
this.plant = function(){
// grow grow
}
}
My ajax's success routine currently is currently looping over the thing and constructing each object in turn and it doesn't handle the seeds yet, because before I go looping over seed constructors I'm thinking
Is there not a better way?
success: function(data){
fruitbowl.length = 0
$.each(data.fruitbowl, function(i, f){
fruitbowl.push(new Fruit(f.name, f.color, f.seeds))
})
I haven't explored looping over the objects as they are and attaching all the methods. Would that work?
Yes, it would work, but it's not desirable. Apart from appearing slightly hacky IMO, you're attaching methods to each instance of your fruit and seeds, where you should instead be using the prototype chain. If you're going to be using instanceof in the future, this method won't work anyway.
What you're currently doing is the best solution; and you'll be able to use instanceof.
If you're feeling adventurous, you can use JSONP instead of AJAX, with the JSONP response looking something like:
buildFruitbowl([new Fruit("orange", "blue", [new Seed("small", "hard"), new Seed("big", "soft")]), new Fruit("banana", "yellow", [new Seed("small", "hard"), new Seed("big", "soft")])]);
Which will save you having to do all your object looping, and you'll get your Fruit and Seeds how you want (and instanceof support); however I would still stick to what you're doing already.
Best of look growing your bananas.
Pass the data to the object constructor then use jquery's "extend" to combine the data and methods:
function Fruit(data){
$.extend(this, data)
this.eat = function(){
// munch munch
}
}
...
$.each(data.fruitbowl, function(i, f){
fruitbowl.push(new Fruit(f))
})
You still have loops involved; and must manually code loops for the nested objects (like seeds), but still a very simple way to get past the problem.
You could modify the JSON structure to store the type information. If you have a lot of objects to serialize and deserialize back and forth, this would save time writing custom code for each object.
Also note, this modifies the JSON structure and adds a __type__ property to each custom object. I think this is a cleaner approach than keeping separate configuration files. So without further ado, this is how it basically works:
var fruitBowl = {..};
fruitBowl[0].eat();
fruitBowl[1].seeds[0].plant();
call serialize on the object to get a JSON representation
var json = fruitBowl.serialize();
call deserialize on the JSON encoded string to reconstruct the objects
var resurrected = json.deserialize();
now you can access properties and call methods on the objects:
resurrected[0].eat();
resurrected[1].seeds[0].plant();
It works for any levels of deeply nested objects, although it might be a little buggy for now. Also it is most likely not cross-browser (only tested on Chrome). Since the deserializer is not familiar with an object's constructor function, it basically creates each custom object without passing any parameters. I've setup a working demo on jsfiddle at http://jsfiddle.net/kSATj/1/.
The constructor function had to be modified to account for the two ways it's objects could be created
Directly in Javascript
Reconstructed from JSON
All constructors would need to accommodate creation from both ends, so each property needs to be assigned a default fallback value incase nothing was passed.
function SomeObject(a, b) {
this.a = a || false; // defaultValue can be anything
this.b = b || null; // defaultValue can be anything
}
// one type of initialization that you can use in your code
var o = new SomeObject("hello", "world");
// another type of initialization used by the deserializer
var o = new SomeObject();;
o.a = "hello";
o.b = "world";
For reference, the modified JSON looks like:
{"fruitbowl":
[
{
"__type__": "Fruit",
"name": "apple",
"color": "red",
"seeds": []
},
{
"__type__": "Fruit",
"name": "orange",
"color": "orange",
"seeds":
[
{
"__type__": "Seed",
"size": "small",
"density": "hard"
},
{
"__type__": "Seed",
"size": "small",
"density": "soft"
}
]
}
]
}
This is just a helper function to identify simple types:
function isNative(object) {
if(object == null) {
return true;
}
var natives = [Boolean, Date, Number, String, Object, Function];
return natives.indexOf(object.constructor) !== -1;
}
Serializes an object into JSON (with type info preserved):
Object.prototype.serialize = function() {
var injectTypes = function(object) {
if(!isNative(object)) {
object.__type__ = object.constructor.name;
}
for(key in object) {
var property = object[key];
if(object.hasOwnProperty(key) && !isNative(property)) {
injectTypes(property);
}
}
};
var removeTypes = function(object) {
if(object.__type) {
delete object.__type__;
}
for(key in object) {
var property = object[key];
if(object.hasOwnProperty(key) && !isNative(property)) {
removeTypes(property);
}
}
}
injectTypes(this);
var json = JSON.stringify(this);
removeTypes(this);
return json;
};
Deserialize (with custom objects reconstructed):
String.prototype.deserialize = function() {
var rawObject = JSON.parse(this.toString());
var reconstruct = function(object) {
var reconstructed = {};
if(object.__type__) {
reconstructed = new window[object.__type__]();
delete object.__type__;
}
else if(isNative(object)) {
return object;
}
for(key in object) {
var property = object[key];
if(object.hasOwnProperty(key)) {
reconstructed[key] = reconstruct(property);
}
}
return reconstructed;
}
return reconstruct(rawObject);
};
Using ES5 Object.create
Simply define your objects statically then use Object.create to extend them.
It's as simple as Object.create(Bowl, transform(data));
// declare 3 Objects to use as prototypes for your data
var Fruit = {
eat: function() { }
}
var Seed = {
plant: function() { }
}
var Bowl = {};
// data object
var data = { ... };
// Transform JSON to a valid defineProperties hash.
Object.create(Bowl, transform(data));
You will need to define the transform function and more importantly tell it the object type of nested arrays of data.
// hash map of property names of arrays to the Object they should prototype from.
var collectionClassHash = {
fruitbowl: Fruit,
seeds: Seed
}
var transform = function(obj) {
// return value
var ret = {};
// for each key
Object.keys(obj).forEach(function(key) {
// value of key
var temp = obj[key];
// if array
if (Array.isArray(temp) {
// override value with an array of the correct objects
temp = obj[key].map(function(val) {
// recurse for nested objects
return Object.create(collectionClassHash[key], transform(val));
});
}
// define getter/setter for value
ret[key] = {
get: function() { return temp; },
set: function(v) { temp = v; }
}
});
return ret;
}
Using D Crockford's "json2" library, you can supply a "reviver" function to the parsing process. The reviver function is passed each key and each value, and should return the actual effective value to be used in the parsed result.
There's a corresponding optional parameter in the "stringify" method.
This actually took me a while to figure out, I'm really surprised there are not more pages on this.
As #Pointy pointed out, JSON has a reviver function that can be used to replace the parse result inline allowing you to avoid walking the tree a second time. The JSON page documents reviver (in my opinion a little weakly) - http://json.org/js.html.
Reviver is part of ECMA 5 and is supported in Firefox, WebKit (Opera/Chrome), and JSON2.js.
Here is a code example based on the JSON doc. You can see we are setting a type property on Dog and then using a reviver function that recognizes that type property.
function Dog(args) {
this.name = args.name;
this.bark = function() {
return "bark, bark, my name is " + this.name;
};
this.toJSON = function() {
return {
name: this.name,
type: 'Dog' // this.constructor.name will work in certain browsers/cases
}
}
};
var d = new Dog({name:'geti'});
var dAsJson = JSON.stringify(d);
var dFromJson = JSON.parse(dAsJson, function (key, value) {
var type;
if (value && typeof value === 'object') {
type = value.type;
if (typeof type === 'string' && typeof window[type] === 'function') {
return new (window[type])(value);
}
}
return value;
}
);
I have a couple concerns about their example. The first is that it depends on the constructor being global (on window). The second is a security concern in that rogue JSON can get us to call any constructor by adding a type property to their JSON.
I've chosen to have an explicit list of types and their constructors. This ensures only constructors I know are safe will be called and also allows me to use a custom type mapping approach if I like (rather than depending on the constructor name and it being in the global space). I also verify the JSON object has a type (some may not and they will be treated normally).
var jsonReviverTypes = {
Dog: Dog
};
var dAsJsonB = JSON.stringify(d);
var dFromJsonB = JSON.parse(dAsJsonB, function (key, value) {
var type;
if (value && typeof value === 'object' && value.type) {
type = value.type;
if (typeof type === 'string' && jsonReviverTypes[type]) {
return new (jsonReviverTypes[type])(value);
}
}
return value;
});
Note, FF 3.6 has a bug in the JSON.replacer method as #Sky pointed out and has documented here - http://skysanders.net/subtext/archive/2010/02/24/confirmed-bug-in-firefox-3.6-native-json-implementation.aspx. For the above solution I work around this by using toJSON on the object rather than using replacer.
John,
Hopefully not too late to chip in here. I had a very similar problem just last week and solved it with the following piece of js (it could easily be converted to jquery as well.).
Here's the base usage:
$(document).ready(function() {
var bowl = { "fruitbowl": [{
"name": "apple",
"color": "red",
"seeds": []
},
{
"name": "orange",
"color": "orange",
"seeds": [
{ "size": "small", "density": "hard" },
{ "size": "small", "density": "soft"}]
}
]
};
var serialized = jsonToObject.serialize(bowl);
var deserialized = jsonToObject.deserialize(serialized);
// basic tests on serialize/deserializing...
alert(deserialized.fruitbowl[0].name);
alert(deserialized.fruitbowl[1].seeds[0].density);
});
and here's the jsonToObject.js file:
jsonToObject = {
deserialize: function(_obj) {
if (typeof (JSON) === 'object' && typeof (JSON.parse) === 'function') {
// native JSON parsing is available.
//return JSON.parse(_obj);
}
// otherwise, try non-native methods
var jsonValue = new Function("return " + _obj)();
if (!jsonValue instanceof Object) {
jsonValue = eval("(" + _obj + ")");
}
return jsonValue;
},
serialize: function(_obj) {
// Let Gecko browsers do this the easy way - not working
if (_obj != undefined && typeof _obj.toSource !== 'undefined'
&& typeof _obj.callee === 'undefined') {
return _obj.toSource();
}
// Other browsers must do it the hard way
switch (typeof _obj) {
// numbers, booleans, and functions are trivial:
// just return the object itself since its default .toString()
// gives us exactly what we want
case 'number':
case 'boolean':
case 'function':
return _obj;
break;
// for JSON format, strings need to be wrapped in quotes
case 'string':
return '"' + _obj.replace(/"/mg, "'") + '"';
break;
case 'object':
var str;
if (_obj.constructor === Array || typeof _obj.callee !== 'undefined') {
str = '[';
var i, len = _obj.length;
for (i = 0; i < len - 1; i++) { str += this.serialize(_obj[i]) + ','; }
str += this.serialize(_obj[i]) + ']';
}
else {
str = '{';
var key;
for (key in _obj) { str += key + ':' + this.serialize(_obj[key]) + ','; }
str = str.replace(/\,$/, '') + '}';
}
return str;
break;
default:
return '""';
break;
}
}
}
hope this helps...
jim
[edit] - you could of course also give the two functions their prototype signatures in keeping with the excellent example above, ie..
String.prototype.deserialize = function() {...}
Object.prototype.serialize = function() {...}

Categories