I have a JSON data which can be an object/array of recursively nested object or array. The value of an array can not be null, but an value in a object can be null. I would like to return all combination of these keys and values through depth first search.
For example:
var data = {
"title": {
"original": "Hitchhiker",
"more": ["HGTTG"],
"link": null
},
"date": ["20150101", "20160101"]
}
The data's length and depth is arbitrary, and the combination result I want is something like this:
["title-original-Hitchhiker", "title-more-HTTG", "title-link", "date-20150101", "date-20160101"]
What I came up with is recursive:
function nestedObjectToArray(obj) {
if (typeof(obj) != "object"){
return [obj];
}
var result = [];
if (obj.constructor == Array){
for (var i = 0; i <obj.length; i++){
if (obj[i]){
var temp = nestedObjectToArray(obj[i]);
for (var j = 0; j < temp.length; j++){
result.push(temp[j]);
}
}
}
} else {
for (var i in obj){
if (obj.hasOwnProperty(i)) {
if (obj[i] == null){
result.push(i);
} else {
var temp = nestedObjectToArray(obj[i]);
for (var j = 0; j < temp.length; j++){
result.push(i+"-"+temp[j]);
}
}
}
}
}
return result;
}
Would you have a better/more elegant solution then this chunk of conditions, loops and recursion? I can use any lib if it's more convenient.
1 Answer 1
Your code is clean and works fine.
The only little point I've noticed is that you didn't follow this rule:
- instead of (for instance)
for (var i = 0; i <obj.length; i++)
- you should write
for (var i = 0, n = obj.length; i < n; i++)
, so theobj.length
is evaluated only once rather than for each iteration
That's said, it may be possible that it doesn't matter any longer in modern browsers, due to optimizers.
Beyond that, I was interested by your challenge to find "a better/more elegant solution".
Then I found one that uses reduced code, and probably works faster:
var data = {
"title": {
"original": "Hitchhiker",
"more": ["HGTTG"],
"link": null
},
"date": ["20150101", "20160101"]
}
function nestedObjectToArray(obj, prefix) {
prefix = prefix ? prefix : '';
var result = [], isArray = Array.isArray(obj), value, nextPrefix;
obj = typeof obj == 'object' ? obj : JSON.parse('{"' + obj + '": null}');
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
value = obj[key];
nextPrefix = prefix + (isArray ? '' : (key + '-'));
result = result.concat(
value === null ? (prefix + key) : nestedObjectToArray(value, nextPrefix)
);
}
}
return result;
}
console.log(nestedObjectToArray(data));
The main points of interest are:
- The function gets a second argument
prefix
which is recursively transmitted, growing as depth increases: this allows to always add the complete prefix at each level, instead of having to re-process the complete result returned to prepend each item with the key of the ancestor level. - There is a unique loop to process arrays and objects: since the only difference is the absence/presence of a key, the test (is array ?) is made at this precise point, inside the generation for the current item:
nextPrefix = prefix + (isArray ? '' : (key + '-'));
. - More over, also a final "leaf" is not processed separately, like:
if (typeof(obj) != "object") {return [obj];}
in your version;
instead, the leaf is transformed into an object:
obj = typeof obj == 'object' ? obj : JSON.parse('{"' + obj + '": null}');
so it can be processed in the loop, where it falls into the (already planned by design, following your initial needs) case ofnull
value: the trick is that the value became a key.
Explore related questions
See similar questions with these tags.