Reputation: 1263
I'm trying to replicate the json stringify method but instead use recursion. I've been able to pass alot of test cases but when it comes to nested arrays I seem to be having issues. if there's any empty array inside an array ('[]'), i get something like [,7,9] instead of [[],7,9]. Also if I pass in:
stringifyJSON([[["test","mike",4,["jake"]],3,4]])
"[[test,mike,4,jake,3,4]]"
I thought I was close in getting this to work, but I might have to start over. Do you guys have any ideas on what I might be able to change to make this work for nested examples? Here's the code I have now:
var testarray = [9,[[],2,3]] //should return '[9,[[],2,3]]'
var count = 0
var stringifyJSON = function(obj,stack) {
var typecheck = typeof obj;
var resarray = stack;
if(resarray == null){ //does resarray exist? Is this the first time through?
var resarray = [];
}
if(typeof obj === "string"){ //Is obj a string?
return '"' + String(obj) + '"';
}
if((Array.isArray(obj)) && (obj.length > 0)){ //If not a string, is it an object?
for(var i = 0; i<obj.length;i++){
if(Array.isArray(obj[i])){
var arraytemp = []
stringifyJSON(arraytemp.push(obj[i]),resarray) // this is probably incorrect, this is how i handle a nested array situation
}
if(typeof obj[i] === 'number'){ //if the number is inside of the array, don't quote it
resarray.push(obj[i]);
}
else if(typecheck === 'object' && Array.isArray(obj[0])){
resarray.push('[' + obj[i] + ']');
}
else{
resarray.push('"' + obj[i] + '"');
}
obj.shift() //delete the first object in the array and get ready to recurse to get to the second object.
stringifyJSON(obj,resarray); //remember the new array when recursing by passing it into the next recursive instance
}
}
if(obj !== null && typeof obj === 'object'){ //is obj an object?
for(var key in obj){
stringifyJSON(resarray.push(key + '"' + ':' + obj[key]),resarray)
}
}
if(typeof obj === "number" || obj == null || obj === true || obj === false){ //special cases and if it's a number
return '' + obj + ''
}
if(typecheck === 'object'){ //a special case where you have an empty array that needs to be quoted.
return '['+resarray+']'
}
return '' + resarray.join('') + '';
};
//JSON values cannot be a function, a date, or undefined
Upvotes: 1
Views: 794
Reputation: 135277
Do you guys have any ideas on what I might be able to change to make this work for nested examples?
Sure, but it's going to scrap you entire function, so I hope you don't mind. I'll provide a bullet-list of points why this approach is essential and yours is essentially flawed from the get-go :(
Corner cases
This function does a simple case analysis on a non-null data's constructor
property and encodes accordingly. It manages to cover a lot of corner cases that you're unlikely to consider, such as
JSON.stringify(undefined)
returns undefined
JSON.stringify(null)
returns 'null'
JSON.stringify(true)
returns 'true'
JSON.stringify([1,2,undefined,4])
returns '[1,2,null,4]'
JSON.stringify({a: undefined, b: 2})
returns '{ "b": 2 }'
JSON.stringify({a: /foo/})
returns { "a": {} }
So to verify that our stringifyJSON
function actually works properly, I'm not going to test the output of it directly. Instead, I'm going to write a little test
method that ensures the JSON.parse
of our encoded JSON actually returns our original input value
// we really only care that JSON.parse can work with our result
// the output value should match the input value
// if it doesn't, we did something wrong in our stringifier
const test = data => {
return console.log(JSON.parse(stringifyJSON(data)))
}
test([1,2,3]) // should return [1,2,3]
test({a:[1,2,3]}) // should return {a:[1,2,3]}
Disclaimer: it should be obvious that the code I'm about to share is not meant to be used as an actual replacement for
JSON.stringify
– there's countless corner cases we probably didn't address. Instead, this code is shared to provide a demonstration for how we could go about such a task. Additional corner cases could easily be added to this function.
Runnable demo
Without further ado, here is stringifyJSON
in a runnable demo that verifies excellent compatibility for several common cases
const stringifyJSON = data => {
if (data === undefined)
return undefined
else if (data === null)
return 'null'
else if (data.constructor === String)
return '"' + data.replace(/"/g, '\\"') + '"'
else if (data.constructor === Number)
return String(data)
else if (data.constructor === Boolean)
return data ? 'true' : 'false'
else if (data.constructor === Array)
return '[ ' + data.reduce((acc, v) => {
if (v === undefined)
return [...acc, 'null']
else
return [...acc, stringifyJSON(v)]
}, []).join(', ') + ' ]'
else if (data.constructor === Object)
return '{ ' + Object.keys(data).reduce((acc, k) => {
if (data[k] === undefined)
return acc
else
return [...acc, stringifyJSON(k) + ':' + stringifyJSON(data[k])]
}, []).join(', ') + ' }'
else
return '{}'
}
// round-trip test and log to console
const test = data => {
return console.log(JSON.parse(stringifyJSON(data)))
}
test(null) // null
test('he said "hello"') // 'he said "hello"'
test(5) // 5
test([1,2,true,false]) // [ 1, 2, true, false ]
test({a:1, b:2}) // { a: 1, b: 2 }
test([{a:1},{b:2},{c:3}]) // [ { a: 1 }, { b: 2 }, { c: 3 } ]
test({a:[1,2,3], c:[4,5,6]}) // { a: [ 1, 2, 3 ], c: [ 4, 5, 6 ] }
test({a:undefined, b:2}) // { b: 2 }
test([[["test","mike",4,["jake"]],3,4]]) // [ [ [ 'test', 'mike', 4, [ 'jake' ] ], 3, 4 ] ]
"So why is this better?"
null
s and undefined
s, and so on – you get the ideastringifyJSON
object is like a little program that tells us exactly how to encode each type (eg String
, Number
, Array
, Object
etc)typeof
type checking – after we check for the undefined
and null
cases, we know we can try to read the constructor
property.if
conditions using &&
, ||
, !
, or checking things like x > y.length
etcobj[0]
or obj[i]
that stresses our brain outlength
propertyresarray
or what state it's in after push
calls happen at various stages in the program Custom objects
JSON.stringify
allows us to set a toJSON
property on our custom objects so that when we stringify them, we will get the result we want.
const Foo = x => ({
toJSON: () => ({ type: 'Foo', value: x })
})
console.log(JSON.stringify(Foo(5)))
// {"type":"Foo","value":5}
We could easily add this kind of functionality to our code above – changes in bold
const stringifyJSON = data => {
if (data === undefined)
return undefined
else if (data === null)
return 'null'
else if (data.toJSON instanceof Function)
return stringifyJSON(data.toJSON())
...
else
return '{}'
}
test({toJSON: () => ({a:1, b:2})}) // { a: 1, b: 2 }
Upvotes: 2
Reputation: 1
So after playing around with your code, I found that if you replace:
resarray.push('[' + obj[i] + ']');
with:
resarray.push(stringifyJSON(obj[i]))
it works well with the arrays and still satisfies your recursive process.
Also I found a few quirks with running it through objects like { hello: 5, arr: testarray, arr2: [1, 2,3, "hello"], num: 789, str: '4444', str2: "67494" };
and found that by changing the stringifying of objects from:
stringifyJSON(resarray.push(key + '"' + ':' + obj[key]),resarray)
to something more like:
stringifyJSON(resarray.push('"' + key + '"' + ':' + stringifyJSON(obj[key])),resarray);
it should workout more of how you want. This is really cool though and I had fun playing around with it!
Upvotes: 0