Setting a nested object value [duplicate] - javascript

This question already has answers here:
Javascript: how to dynamically create nested objects using object names given by an array
(25 answers)
Closed 6 years ago.
I have the following object:
var object = {
"property1": "value1",
"property2": "value2",
"subobject": {
"property1": "value1",
"property2": "value2",
"subobject": {
"property1": "value1",
"property2": "value2",
"subobject": {...
}
}
}
}
I am trying to set one of the nested subobject properties, but the nested level is dynamic.
How can I dynamically set one of these nested properties without doing something like this: object.subobject.subobject = { ... }?
Edit:
So to be more specific, I am trying to set one of the nested subobjects, but I won't know which one each time.

Using recursion - refactor (thanks Rocket Hazmat)
This function works for me!:
/**
* #obj: the json object to change
* #access: string dot separates route to value
* #value: new valu
*/
function setValue(obj,access,value){
if (typeof(access)=='string'){
access = access.split('.');
}
if (access.length > 1){
setValue(obj[access.shift()],access,value);
}else{
obj[access[0]] = value;
}
}
Having an object:
var jsonObject = {
'name' : 'pepe',
'links' :
{
'link1' : 'one',
'link2' : 'two',
'link3' :
{
'link31' : '3url',
'link32' : '3url',
'link33' : '3url'
}
}
}
We can change a value easy with:
setValue(jsonObject,'links.link3.link32','new value!!!');
Thanks

Let's use recursion!
function setNest(obj, level, val){
if(level > 0){
setNest(obj.subobject, level-1, val);
}
else{
obj.subobject = val;
}
}
Then call it like:
setNest(object, 2, {a: 12});

You can define your own Object methods; also I'm using underscore for brevity:
var _ = require('underscore');
// a fast get method for object, by specifying an address with depth
Object.prototype.pick = function(addr) {
if (!_.isArray(addr)) return this[addr]; // if isn't array, just get normally
var tmpo = this;
while (i = addr.shift())
tmpo = tmpo[i];
return tmpo;
};
// a fast set method for object, put value at obj[addr]
Object.prototype.put = function(addr, val) {
if (!_.isArray(addr)) this[addr] = val; // if isn't array, just set normally
this.pick(_.initial(addr))[_.last(addr)] = val;
};
Sample usage:
var obj = {
'foo': {
'bar': 0 }}
obj.pick('foo'); // returns { bar: 0 }
obj.pick(['foo','bar']); // returns 0
obj.pick('foo.bar'.split('.')); // equivalent as above, returns 0
obj.put(['foo', 'bar'], -1) // obj becomes {'foo': {'bar': -1}}

You could try using Immutable.js, like so:
var immutableJsObject = Immutable.fromJS({
inputs: {
firstDepthNumberOne: {
secondDepth: 'secondDepthValue'
},
firstDepthNumberTwo: 'hello'
}
});
var newImmutableJsObject = immutableJsObject.setIn('inputs.firstDepthNumberOne.secondDepth'.split('.'), 'newValue');
newImmutableJsObject.toJS();

Related

In Javascript what would be the simplest function to get the namespace of a multi depth object [duplicate]

This question already has answers here:
Javascript reflection: Get nested objects path
(3 answers)
Closed 6 years ago.
Sorry if the title is a bit miss leading, I'll give an example of what I wish to do.
If I have a object say:
{
testable: {
some: {
id: 10
},
another: {
some: {
id: 20
}
},
an : {
arrayExample: ['test']
}
}
}
and I want an easy way to get an array of all the keys for non object values like so:
['testable.some.id','testable.another.some.id', 'testable.an.arrayExample.0']
How would I do it? I assume I'd need a recursive function to build such an array as I'd have to do a for loop through each object and grab the key and add it to the previous namespace before pushing it into an array.
Also is this a bit to costly as a function? Especially if I'm going to use the array afterwards to grab the values.
You could use an iterative an recursive approach to get all keys of the object and array.
For all nested keys, you nedt to iterate over all items. There is no short circuit or other way around to skipt that task.
If you have some fancy names like 'item.0' you may get the wrong path to it.
Edit
Added a function getValue for getting a value from a path to a property/item. This works for falsy values as well.
function getPath(object) {
function iter(o, p) {
if (Array.isArray(o) ){
o.forEach(function (a, i) {
iter(a, p.concat(i));
});
return;
}
if (typeof o === 'object') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k));
});
return;
}
path.push(p.join('.'));
}
var path = [];
iter(object, []);
return path;
}
function getValue(object, path) {
return path.split('.').reduce(function (r, a) {
return (Array.isArray(r) || typeof r === 'object') ? r[a] : undefined;
}, object);
}
var obj = { testable: { some: { id: 10, number: 0 }, another: { some: { id: 20 } }, an: { arrayExample: ['test'] } } },
path = getPath(obj);
document.write('<pre>' + JSON.stringify(getPath(obj), 0, 4) + '</pre>');
path.forEach(function (a) {
document.write(getValue(obj, a) + '<br>');
});
//console.log(getPath(obj));
//path.forEach(function (a) {
// console.log(getValue(obj, a));
//});
That would simply be done by the invention of a "reusable" Object method. I call it Object.prototype.getNestedValue() It dynamically fetches the value resides at deeply nested properties. You have to provide the properties as arguments in an ordered fashion. So for example if you would like to get the value of myObj.testable.some.id you should invoke the function as var myId = myObj.getNestedValue("testable","some","id") // returns 10 So lets see how it works.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myObj = {
testable: {
some: {
id: 10
},
another: {
some: {
id: 20
}
},
an : {
arrayExample: ['test']
}
}
},
myId = myObj.getNestedValue("testable","some","id");
console.log(myId);
//or if you receive the properties to query in an array can also invoke it like
myId = myObj.getNestedValue(...["testable","some","id"]);
console.log(myId);
So far so good. Let's come to your problem. Once we have this nice tool in our hand what you want to do becomes a breeze. This time i insert your each query in an array and the spread operator is our friend. Check this out.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myObj = {
testable: {
some: {
id: 10
},
another: {
some: {
id: 20
}
},
an : {
arrayExample: ['test']
}
}
},
idCollection = [["testable","some","id"],["testable","another","some","id"],["testable","an","arrayExample",0]],
ids = idCollection.reduce((p,c) => p.concat(myObj.getNestedValue(...c)),[]);
console.log(ids);
Of course the the argument we use for querying can be dynamic like
var a = "testable",
b = "another",
c = "some",
d = "id",
myId = myObj.getNestedValue(a,b,c,d);
and you can reuse this Object method everywhere including arrays since in JS arrays are objects and the have perfect access to the Object.prototype. All you need is to pass the index of the array instead of the property of an object.

jQuery - how to find a specific JavaScript object inside an array within an object?

I have an object in javaScript:
var stuffObject = {
stuffArray1 : [object1, object2, object3],
stuffArray2 : [object4, object5, object6]
}
object1 to 6 look like this:
object1 = {
dataStuff : {
stuffId: "foobar"
}
}
My question: given the key "foobar", how do I retrieve object1 from the stuffObject using jQuery? The key "stuffId" always has a unique value.
You won't get around iterating over the set to find the object you are looking for. jQuery can't really help with that. Its purpose is DOM manipulation. If you want functionality to deal with objects, sets, lists, etc., check out lodash.
I wrote a function to deal with the problem. I hope it's understandable.
var stuffObject = {
stuffArray1 : [{dataStuff: {stuffId: 'foobar'}}, {dataStuff: {stuffId: 'foo'}}, {}],
stuffArray2 : [{}, {dataStuff: {stuffId: 'bar'}}, {}]
}
function getObjByStuffId(stuffObject, stuffId) {
var key, arr, i, obj;
// Iterate over all the arrays in the object
for(key in stuffObject) {
if(stuffObject.hasOwnProperty(key)) {
arr = stuffObject[key];
// Iterate over all the values in the array
for(i = 0; i < arr.length; i++) {
obj = arr[i];
// And if it has the value we are looking for
if(typeof obj.dataStuff === 'object'
&& obj.dataStuff.stuffId === stuffId) {
// Stop searching and return the object.
return obj;
}
}
}
}
}
console.log('foobar?', getObjByStuffId(stuffObject, 'foobar') );
console.log('foo?', getObjByStuffId(stuffObject, 'foo') );
console.log('bar?', getObjByStuffId(stuffObject, 'bar') );
Thanks for the help guys, using the input of other people I have solved it myself:
getStuffById: function(id){
for (stuffArray in stuffObject) {
for (stuff in stuffObject[stuffArray]) {
if (stuffObject[stuffArray][stuff].dataStuff.stuffId == id) {
return stuffObject[stuffArray][stuff];
}
}
}
return null;
}
This also works better than the (now deleted) answer that uses .grep(), as this function terminates as soon as it finds the correct object.

best way to attach properties of an object to another object

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());

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.

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