How to parse (infinite) nested object notation? - javascript

I am currently breaking my head about transforming this object hash:
"food": {
"healthy": {
"fruits": ['apples', 'bananas', 'oranges'],
"vegetables": ['salad', 'onions']
},
"unhealthy": {
"fastFood": ['burgers', 'chicken', 'pizza']
}
}
to something like this:
food:healthy:fruits:apples
food:healthy:fruits:bananas
food:healthy:fruits:oranges
food:healthy:vegetables:salad
food:healthy:vegetables:onions
food:unhealthy:fastFood:burgers
food:unhealthy:fastFood:chicken
food:unhealthy:fastFood:pizza
In theory it actually is just looping through the object while keeping track of the path and the end result.
Unfortunately I do not know how I could loop down till I have done all nested.
var path;
var pointer;
function loop(obj) {
for (var propertyName in obj) {
path = propertyName;
pointer = obj[propertyName];
if (pointer typeof === 'object') {
loop(pointer);
} else {
break;
}
}
};
function parse(object) {
var collection = [];
};
There are two issues which play each out:
If I use recurse programming it looses the state of the properties which are already parsed.
If I do not use it I cannot parse infinite.
Is there some idea how to handle this?
Regards

The reason your recursive function doesn't work is you're storing the state outside it. You want the state inside it, so that each invocation tracks its state.
Something like this:
var obj = /* ... the object ... */;
var lines = loop([], "", obj);
function loop(lines, prefix, obj) {
var key, sawOne = false;
// Is it an array?
if (Object.prototype.toString.call(obj) === "[object Array]") {
// Yes, in your example these are all just strings to put
// at the end, so do that
for (key = 0; key < obj.length; ++key) {
lines.push(prefix + ":" + obj[key]);
}
}
else {
// No, it's an object. Recurse for each property, adding the
// property to the prefix we use on each line
for (key in obj) {
loop(lines, prefix ? (prefix + ":" + key) : key, obj[key]);
}
}
return lines;
}
Completely off-the-cuff and untested, but you get the idea.
Edit: But apparently it works, as Michael Jasper was kind enough to make a live demo (source) which I've tweaked slightly.

Related

How to slice() return values from JSON API [duplicate]

var obj = {
name: "Simon",
age: "20",
clothing: {
style: "simple",
hipster: false
}
}
for(var propt in obj){
console.log(propt + ': ' + obj[propt]);
}
How does the variable propt represent the properties of the object? It's not a built-in method or property. Why does it come up with every property in the object?
Iterating over properties requires this additional hasOwnProperty check:
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// do stuff
}
}
It's necessary because an object's prototype contains additional properties for the object which are technically part of the object. These additional properties are inherited from the base object class, but are still properties of obj.
hasOwnProperty simply checks to see if this is a property specific to this class, and not one inherited from the base class.
It's also possible to call hasOwnProperty through the object itself:
if (obj.hasOwnProperty(prop)) {
// do stuff
}
But this will fail if the object has an unrelated field with the same name:
var obj = { foo: 42, hasOwnProperty: 'lol' };
obj.hasOwnProperty('foo'); // TypeError: hasOwnProperty is not a function
That's why it's safer to call it through Object.prototype instead:
var obj = { foo: 42, hasOwnProperty: 'lol' };
Object.prototype.hasOwnProperty.call(obj, 'foo'); // true
As of JavaScript 1.8.5 you can use Object.keys(obj) to get an Array of properties defined on the object itself (the ones that return true for obj.hasOwnProperty(key)).
Object.keys(obj).forEach(function(key,index) {
// key: the name of the object key
// index: the ordinal position of the key within the object
});
This is better (and more readable) than using a for-in loop.
Its supported on these browsers:
Firefox (Gecko): 4 (2.0)
Chrome: 5
Internet Explorer: 9
See the Mozilla Developer Network Object.keys()'s reference for futher information.
Girls and guys we are in 2019 and we do not have that much time for typing... So lets do this cool new fancy ECMAScript 2016:
Object.keys(obj).forEach(e => console.log(`key=${e} value=${obj[e]}`));
In up-to-date implementations of ES, you can use Object.entries:
for (const [key, value] of Object.entries(obj)) { }
or
Object.entries(obj).forEach(([key, value]) => ...)
If you just want to iterate over the values, then use Object.values:
for (const value of Object.values(obj)) { }
or
Object.values(obj).forEach(value => ...)
It's the for...in statement (MDN, ECMAScript spec).
You can read it as "FOR every property IN the obj object, assign each property to the PROPT variable in turn".
It's just a for...in loop. Check out the documentation at Mozilla.
if (typeof obj === 'object' && obj !== null) {
Object.keys(obj).forEach(key => {
console.log("\n" + key + ": " + obj[key]);
});
}
// *** Explanation line by line ***
// Explaining the bellow line
// It checks if obj is neither null nor undefined, which means it's safe to get its keys.
// Otherwise it will give you a "TypeError: Cannot convert undefined or null to object" if obj is null or undefined.
// NOTE 1: You can use Object.hasOwnProperty() instead of Object.keys(obj).length
// NOTE 2: No need to check if obj is an array because it will work just fine.
// NOTE 3: No need to check if obj is a string because it will not pass the 'if typeof obj is Object' statement.
// NOTE 4: No need to check if Obj is undefined because it will not pass the 'if type obj is Object' statement either.
if (typeof obj === 'object' && obj !== null) {
// Explaining the bellow line
// Just like in the previous line, this returns an array with
// all keys in obj (because if code execution got here, it means
// obj has keys.)
// Then just invoke built-in javascript forEach() to loop
// over each key in returned array and calls a call back function
// on each array element (key), using ES6 arrow function (=>)
// Or you can just use a normal function ((key) { blah blah }).
Object.keys(obj).forEach(key => {
// The bellow line prints out all keys with their
// respective value in obj.
// key comes from the returned array in Object.keys(obj)
// obj[key] returns the value of key in obj
console.log("\n" + key + ": " + obj[key]);
});
}
If your environment supports ES2017 then I would recommend Object.entries:
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key} ${value}`);
});
As shown in Mozillas Object.entries() documentation:
The Object.entries() method returns an array of a given object's own
enumerable property [key, value] pairs, in the same order as that
provided by a for...in loop (the difference being that a for-in loop
enumerates properties in the prototype chain as well).
Basically with Object.entries we can forgo the following extra step that is required with the older for...in loop:
// This step is not necessary with Object.entries
if (object.hasOwnProperty(property)) {
// do stuff
}
Dominik's answer is perfect, I just prefer to do it that way, as it's cleaner to read:
for (var property in obj) {
if (!obj.hasOwnProperty(property)) continue;
// Do stuff...
}
jquery allows you to do this now:
$.each( obj, function( key, value ) {
alert( key + ": " + value );
});
The for...in loop represents each property in an object because it is just like a for loop. You defined propt in the for...in loop by doing:
for(var propt in obj){
alert(propt + ': ' + obj[propt]);
}
A for...in loop iterates through the enumerable properties of an object. Whichever variable you define, or put in the for...in loop, changes each time it goes to the next property it iterates. The variable in the for...in loop iterates through the keys, but the value of it is the key's value. For example:
for(var propt in obj) {
console.log(propt);//logs name
console.log(obj[propt]);//logs "Simon"
}
You can see how the variable differs from the variable's value. In contrast, a for...of loop does the opposite.
I hope this helps.
To add ES2015's usage of Reflect.ownKeys(obj) and also iterating over the properties via an iterator.
For example:
let obj = { a: 'Carrot', b: 'Potato', Car: { doors: 4 } };
can be iterated over by
// logs each key
Reflect.ownKeys(obj).forEach(key => console.log(key));
If you would like to iterate directly over the values of the keys of an object, you can define an iterator, just like JavaScipts's default iterators for strings, arrays, typed arrays, Map and Set.
JS will attempt to iterate via the default iterator property, which must be defined as Symbol.iterator.
If you want to be able to iterate over all objects you can add it as a prototype of Object:
Object.prototype[Symbol.iterator] = function*() {
for(p of Reflect.ownKeys(this)){ yield this[p]; }
}
This would enable you to iterate over the values of an object with a for...of loop, for example:
for(val of obj) { console.log('Value is:' + val ) }
Caution: As of writing this answer (June 2018) all other browsers, but IE, support generators and for...of iteration via Symbol.iterator
The above answers are a bit annoying because they don't explain what you do inside the for loop after you ensure it's an object: YOU DON'T ACCESS IT DIRECTLY! You are actually only delivered the KEY that you need to apply to the OBJ:
var obj = {
a: "foo",
b: "bar",
c: "foobar"
};
// We need to iterate the string keys (not the objects)
for(var someKey in obj)
{
// We check if this key exists in the obj
if (obj.hasOwnProperty(someKey))
{
// someKey is only the KEY (string)! Use it to get the obj:
var myActualPropFromObj = obj[someKey]; // Since dynamic, use [] since the key isn't literally named "someKey"
// NOW you can treat it like an obj
var shouldBeBar = myActualPropFromObj.b;
}
}
This is all ECMA5 safe. Even works in the lame JS versions like Rhino ;)
let obj = {"a": 3, "b": 2, "6": "a"}
Object.keys(obj).forEach((item) => {console.log("item", obj[item])})
// a
// 3
// 2
You can access the nested properties of the object using the for...in and forEach loop.
for...in:
for (const key in info) {
console.log(info[key]);
}
forEach:
Object.keys(info).forEach(function(prop) {
console.log(info[prop]);
// cities: Array[3], continent: "North America", images: Array[3], name: "Canada"
// "prop" is the property name
// "data[prop]" is the property value
});
You can use Lodash. The documentation
var obj = {a: 1, b: 2, c: 3};
_.keys(obj).forEach(function (key) {
...
});
Object.keys(obj).forEach(key =>
console.log(`key=${key} value=${obj[key]}`)
);
Nowadays you can convert a standard JS object into an iterable object just by adding a Symbol.iterator method. Then you can use a for of loop and acceess its values directly or even can use a spread operator on the object too. Cool. Let's see how we can make it:
var o = {a:1,b:2,c:3},
a = [];
o[Symbol.iterator] = function*(){
var ok = Object.keys(this);
i = 0;
while (i < ok.length) yield this[ok[i++]];
};
for (var value of o) console.log(value);
// or you can even do like
a = [...o];
console.log(a);
Your for loop is iterating over all of the properties of the object obj. propt is defined in the first line of your for loop. It is a string that is a name of a property of the obj object. In the first iteration of the loop, propt would be "name".
Objects in JavaScript are collections of properties and can therefore be looped in a for each statement.
You should think of obj as an key value collection.
If running Node I'd recommend:
Object.keys(obj).forEach((key, index) => {
console.log(key);
});
While the top-rated answer is correct, here is an alternate use case i.e if you are iterating over an object and want to create an array in the end. Use .map instead of forEach
const newObj = Object.keys(obj).map(el => {
//ell will hold keys
// Getting the value of the keys should be as simple as obj[el]
})
I want to add to the answers above, because you might have different intentions from Javascript. A JSON object and a Javascript object are different things, and you might want to iterate through the properties of a JSON object using the solutions proposed above, and then be surprised.
Suppose that you have a JSON object like:
var example = {
"prop1": "value1",
"prop2": [ "value2_0", "value2_1"],
"prop3": {
"prop3_1": "value3_1"
}
}
The wrong way to iterate through its 'properties':
function recursivelyIterateProperties(jsonObject) {
for (var prop in Object.keys(example)) {
console.log(prop);
recursivelyIterateProperties(jsonObject[prop]);
}
}
You might be surprised of seeing the console logging 0, 1, etc. when iterating through the properties of prop1 and prop2 and of prop3_1. Those objects are sequences, and the indexes of a sequence are properties of that object in Javascript.
A better way to recursively iterate through a JSON object properties would be to first check if that object is a sequence or not:
function recursivelyIterateProperties(jsonObject) {
for (var prop in Object.keys(example)) {
console.log(prop);
if (!(typeof(jsonObject[prop]) === 'string')
&& !(jsonObject[prop] instanceof Array)) {
recursivelyIterateProperties(jsonObject[prop]);
}
}
}
What for..in loop does is that it creates a new variable (var someVariable) and then stores each property of the given object in this new variable(someVariable) one by one. Therefore if you use block {}, you can iterate. Consider the following example.
var obj = {
name:'raman',
hobby:'coding',
planet:'earth'
};
for(var someVariable in obj) {
//do nothing..
}
console.log(someVariable); // outputs planet
Here I am iterating each node and creating meaningful node names. If you notice, instanceOf Array and instanceOf Object pretty much does the same thing (in my application, i am giving different logic though)
function iterate(obj,parent_node) {
parent_node = parent_node || '';
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
var node = parent_node + "/" + property;
if(obj[property] instanceof Array) {
//console.log('array: ' + node + ":" + obj[property]);
iterate(obj[property],node)
} else if(obj[property] instanceof Object){
//console.log('Object: ' + node + ":" + obj[property]);
iterate(obj[property],node)
}
else {
console.log(node + ":" + obj[property]);
}
}
}
}
note - I am inspired by Ondrej Svejdar's answer. But this solution has better performance and less ambiguous
Also adding the recursive way:
function iterate(obj) {
// watch for objects we've already iterated so we won't end in endless cycle
// for cases like var foo = {}; foo.bar = foo; iterate(foo);
var walked = [];
var stack = [{obj: obj, stack: ''}];
while(stack.length > 0)
{
var item = stack.pop();
var obj = item.obj;
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
// check if we haven't iterated through the reference yet
var alreadyFound = false;
for(var i = 0; i < walked.length; i++)
{
if (walked[i] === obj[property])
{
alreadyFound = true;
break;
}
}
// new object reference
if (!alreadyFound)
{
walked.push(obj[property]);
stack.push({obj: obj[property], stack: item.stack + '.' + property});
}
}
else
{
console.log(item.stack + '.' + property + "=" + obj[property]);
}
}
}
}
}
Usage:
iterate({ foo: "foo", bar: { foo: "foo"} });
You basically want to loop through each property in the object.
JSFiddle
var Dictionary = {
If: {
you: {
can: '',
make: ''
},
sense: ''
},
of: {
the: {
sentence: {
it: '',
worked: ''
}
}
}
};
function Iterate(obj) {
for (prop in obj) {
if (obj.hasOwnProperty(prop) && isNaN(prop)) {
console.log(prop + ': ' + obj[prop]);
Iterate(obj[prop]);
}
}
}
Iterate(Dictionary);
To further refine the accepted answer it's worth noting that if you instantiate the object with a var object = Object.create(null) then object.hasOwnProperty(property) will trigger a TypeError. So to be on the safe side, you'd need to call it from the prototype like this:
for (var property in object) {
if (Object.prototype.hasOwnProperty.call(object, property)) {
// do stuff
}
}
Check type
You can check how propt represent object propertis by
typeof propt
to discover that it's just a string (name of property). It come up with every property in the object due the way of how for-in js "build-in" loop works.
var obj = {
name: "Simon",
age: "20",
clothing: {
style: "simple",
hipster: false
}
}
for(var propt in obj){
console.log(typeof propt, propt + ': ' + obj[propt]);
}
If you just want to iterate to map property values then lodash has _.mapValues
const obj = {
a: 2,
b: 3
}
const res = _.mapValues(obj, v => v * 2)
console.log(res)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>

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.

how to access a multilevel key/value from an object within another object

So for instance if I the following two objects
var person = {};
person.name = 'Austin';
person.personality = 'Awsome';
var job = {};
job.title = 'space cowboy';
job.pay = 10,000;
person.job = job;
and I want to programmatically list each key and value par in a for loop I have the following
for(var key in person){
console.log(key)
console.log(person.key)
}
How would I be able to detect in this loop when key reaches job without hardcoding if person.key == job. Also how would i list out all of jobs keys from within this for loop?
function trace(ob){
for (var item in ob){
if (typeof ob[item] == 'object') trace(ob[item]);
console.log("key", item);
console.log("value", ob[item]);
}
}
You could obviously do a lot about the formatting, but here's a simple example of walking through the object recursively. Maybe it'll help show you what you need to do:
function iterate(object) {
for(var key in object) {
if (object[key] instanceof Object) {
iterate(object[key]);
}
else {
console.log(key + ": " + object[key]);
}
}
}
Here's an example if it helps: http://jsbin.com/AmUkIPO/2/
You could this instead:
how would i list out all of jobs keys
var keys = Object.keys(person.job); // get the all the properties keys as array.
keys.map(function(prop, index){
//prop is a propery
// index: the index position in the array
// prop === "property named"
});
How to get the values and compared them:
for (prop in person) {
if (person[prop] === someValue){
..
}
}
*someValue: means a value in case you want to search for something else other than just getting the value type.
All together:
for (prop in person) {
if (person[prop] === someValue){
var keys = Object.keys(person[prop]); // get the all the properties keys as array.
}
}

Pluck specific javascript value from an object based on an array of indexes

Given a nested object like this:
var cars = {
"bentley": {
"suppliers": [
{
"location": "England",
"name": "Sheffield Mines"}
]
// ...
}
};
and an array like this ["bentley", "suppliers", "0", "name"], is there an existing function that will pluck the deepest element, i.e. pluck_innards(cars, ['bentley', "suppliers", "0", "name"]) and that returns "Sheffield Mines".
In other words, is there a function (which I will name deep_pluck) where
deep_pluck(cars, ['bentley', 'suppliers', '0', 'name'])
=== cars['bentley']['suppliers']['0']['name']
It seems to me that this is simple, yet common enough, to have probably been done in one of the Javascript utility libraries such as jQuery or lo-dash/underscore - but I have not seen it.
My thought is something trivial, along the lines of:
function deep_pluck(array, identities) {
var this_id = identities.shift();
if (identities.length > 0) {
return deep_pluck(array[this_id], identities);
}
return array[this_id];
}
Which I have posted on jsFiddle.
It would be helpful of course if the function were smart enough to identify when numerical indexes in arrays are needed. I am not sure offhand what other caveats may be a concern.
This is all a fairly long question for something I imagine has already been cleverly solved, but I thought to post this as I would interested in seeing what solutions are out there.
I don't think you'll have problems with Array indexes if you pass them as number 0.
Here's alternative version of your function without recursion:
function deep_pluck(object, identities) {
var result = object;
for(var i = 0; i < identities.length; i++) {
result = result[identities[i]];
}
return result;
}
Working example here: http://jsfiddle.net/AmH2w/1/
dotty.get(obj, pathspec) does it, accepting either an array or a dotted string as the pathspec.
Dotty is open source, and also has an exists method, and a putter.
The methodology is recursion and very similar to your idea, except that dotty includes a test for null/undefined objects so that it doesn't throw exceptions for trying to access an element of something that doesn't exist.
The dotty.get() source from the docs is posted below:
var get = module.exports.get = function get(object, path) {
if (typeof path === "string") {
path = path.split(".");
}
if (!(path instanceof Array) || path.length === 0) {
return;
}
path = path.slice();
var key = path.shift();
if (typeof object !== "object" || object === null) {
return;
}
if (path.length === 0) {
return object[key];
}
if (path.length) {
return get(object[key], path);
}
};
Although not a generic library, it seems that CasperJS has something of this kind with its utils.getPropertyPath function.
/**
* Retrieves the value of an Object foreign property using a dot-separated
* path string.
*
* Beware, this function doesn't handle object key names containing a dot.
*
* #param Object obj The source object
* #param String path Dot separated path, eg. "x.y.z"
*/
function getPropertyPath(obj, path) {
if (!isObject(obj) || !isString(path)) {
return undefined;
}
var value = obj;
path.split('.').forEach(function(property) {
if (typeof value === "object" && property in value) {
value = value[property];
} else {
value = undefined;
}
});
return value;
}
Edit:
I have come across implementations to solve this a couple times since, including:
the getObject plugin by Ben Alman (on Github).
one I rolled - see gist
Edit (2014)
I would also note the relatively new lodash.deep.
Here's a short ES6 implementation using reduce:
function get(obj, keyPath) {
return keyPath
.split(".")
.reduce((prev, curr) => prev[curr], obj);
}
Usage:
get(cars, "bentley.suppliers.0.name") // -> "Sheffield Mines"

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