Reputation:
I am working on a learning project that requires me to implement a recursive function that stringifies a passed in object, without using JSON.stringify. I have to consider all data types as parameters that my function will receive, and while I am fine with that, I seem to be getting confused when an array/object is passed, and I call the function on itself to iterate over the object contents. I am not doing it right and every change I make is impacting other areas I thought I had completed so my frustration is starting to win this battle. Areas I need help on:
Output all elements of an array/object in the same way as JSON.stringify
when I call the function on itself. An example of an issue I see is, if I pass an array like ["SO"]
, I am am getting [SO]
returned, but it looks like I have that possibility covered!
What to do when a function is passed as a param, which is not allowed.
Here is what I have so far. I appreciate any help you can offer.
var myJSONRecursiveFunc = function (obj) {
var stringed = "";
var result;
if (Number.isInteger(obj)) {
return "" + obj + "";
} else if (obj === null) {
return "" + null + "";
} else if (typeof obj === "boolean") {
return "" + obj + "";
} else if (typeof obj === "string") {
return '"' + obj + '"';
} else if (Array.isArray(obj)) {
if (obj.length === 0) {
return "[" + obj + "]";
}
for (var i = 0; i < obj.length; i++) {
if (Array.isArray(i)) {
myJSONRecursiveFunc(i);
} else {
stringed += "" + obj[i] + ""
}
}
return result = "[" + stringed + "]";
} else {
for (var val in obj) {
if (typeof val === "object") {
myJSONRecursiveFunc(val);
}
stringed += "" + val + "" + ":" + "" + obj[val] + "" + '';
}
return result = "{" + stringed + "}";
}
};
It is far from perfect as I am still learning so please let me know where I can improve along with any help in getting this to work as is.
Upvotes: 0
Views: 114
Reputation: 5286
Output all elements of an array/object in the same way as JSON.stringify when I call the function on itself. An example of an issue I see is, if I pass an array like ["SO"], I am am getting [SO] returned, but it looks like I have that possibility covered!
Your recursion of var val in obj
is only passing in val
, which is the key of obj
. You need to call myJSONRecursiveFunc(obj[val])
in order to get the right result. Additionally, this is true for your array. Your if statement needs to check to see if obj[i]
is an array, not i
which would just be an integer. In this case, you need to say:
if (Array.isArray(obj[i])) {
myJSONRecursiveFunc(obj[i])
}
What to do when a function is passed as a param, which is not allowed.
You would need to have a check to see if the function being passed in is a function, with typeof
, such as: if (typeof func === function)
This is a pretty fun exercise. I did this a few months back, and had access to the Underscore library to do so. Here's working code:
var stringifyJSON = function(obj) {
//Strings and null should be addressed here. Strings have quotes inside the string so I can't lump them together with booleans and numbers.
if (_.isString(obj)){
return '"' + obj.split('"').join('\\"') + '"';
}
if (_.isNull(obj)){
return 'null';
}
//Arrays get a temporary array that their stringified elements get pushed to, and then that temporary array is joined together and concatenated with the brackets that exist within the returned string.
if (_.isArray(obj)){
var tempArr = [];
_.each(obj, function(elem){
tempArr.push(stringifyJSON(elem));
});
return '[' + tempArr.join(',') + ']';
}
//Objects get a temporary string to add their stringified data to. Including a check for undefined values and function keys.
if (_.isObject(obj)){
var tempArr = [];
for (var k in obj){
if (_.isUndefined(obj[k]) || _.isFunction(k)){
return '{}';
} else {
tempArr.push(stringifyJSON(k) + ':' + stringifyJSON(obj[k]));
}
}
return '{' + tempArr.join(', ') + '}';
}
//Everything else = booleans, numbers
else {
return obj.toString();
}
};
Upvotes: 1
Reputation: 2645
Okay, let's do this.
function toJSON(obj) {
// There are people who will disagree with me
// but I think this variable is declared way too early.
// It's used in 2 places as a temp variable, so should
// be declared closer to where it's used.
// It should also be named more appropriately for how it's used.
// I used `arrayParts` and `keyValuePairs` instead.
var stringed = "";
// As far as I can tell, this variable is never actually
// used for anything useful.
// It's always `return result = <thing>`.
// If you're immediately returning, there's no need to save
// to a variable.
var result;
if (Number.isInteger(obj)) {
// The initial `"" + <number>` converts to a string.
// Appending another "" to the end is pointless in
// all of the below lines.
return "" + obj + "";
} else if (obj === null) {
return "" + null + "";
} else if (typeof obj === "boolean") {
return "" + obj + "";
} else if (typeof obj === "string") {
return '"' + obj + '"';
} else if (Array.isArray(obj)) {
// If the object is an array with length 0, you
// already know what it looks like.
// It's `[]`, so just return that instead of adding
// the empty array in the middle.
if (obj.length === 0) {
return "[" + obj + "]";
}
for (var i = 0; i < obj.length; i++) {
// In the top of this function, you handle all the
// different types of objects, so you should recurse and
// reuse that logic. Checking again here is wasted effort.
if (Array.isArray(i)) {
myJSONRecursiveFunc(i);
} else {
stringed += "" + obj[i] + ""
}
}
return result = "[" + stringed + "]";
// A better way to write this section would have
// looked like this.
// var arrayParts = []
// for( var i = 0; i < obj.length; i++ ){
// var stringifiedElement = toJSON( obj[ i ] )
// arrayParts.push( stringifiedElement )
// }
// return '[' + arrayParts.join( ',' ) + ']'
} else {
for (var val in obj) {
// Again, your function's start checks type and handles it.
// Use that recursively.
if (typeof val === "object") {
myJSONRecursiveFunc(val);
}
stringed += "" + val + "" + ":" + "" + obj[val] + "" + '';
}
return result = "{" + stringed + "}";
// This section could be rewritten as:
// var keyValuePairs = []
// for( var key in obj ){
// var pair = '"' + key + '":' + toJSON( obj[ key ] )
// keyValuePairs.push( pair )
// }
// return '{' + keyValuePairs.join( ',' ) + '}'
}
};
For the record, when you pass in ['SO'] into your function, this is what is happening. The isArray
block catches the object first.
} else if( Array.isArray( obj ) ){
Then inside your loop, the else
block returns "" + obj[i] + ""
, which converts to "" + "SO" + ""
, which turns into "SO"
.
When this is returned, "[" + "SO" + "]"
turns into "[SO]"
.
for (var i = 0; i < obj.length; i++) {
if (Array.isArray(i)) {
myJSONRecursiveFunc(i);
} else {
stringed += "" + obj[i] + "" // <-- the error
}
}
When you loop over obj as an actual object near the end, you do so like this.
for( var val in obj ){
// foo
}
Sidenote: val
is actually the key, so that's a bit misleading.
This is an ugly part of Javascript. If the raw Object
is modified, for example: Object.prototype.foobar = 5
, then 'foobar':5
will show up in every object in your program. It's worth noting that only developers who are very sure what they are doing should ever be doing this, but it is possible.
To make sure this does not break your program, add the following code.
for( var key in obj ){
if( ! obj.hasOwnProperty( key ) ){
continue
}
}
obj.hasOwnProperty( <name> )
checks if obj
has a direct property <name>
. If it does not, then we skip to the next loop iteration with continue
.
Upvotes: 1