Recursively remove object which contains empty array in nest json object - javascript

I want to dynamically delete json object which contains empty array. I've found this link similar question here. But it doesn't work for me in my case.
Suppose I have a JSON object:
{"op":"1","parameters":[{"op":"2-1","parameters":[]},{"op":"2-2","parameters":[1,2]}]}
I've wrote a sample code to do the stuff recursively:
function removeEmptyArray(cJSON){
if(!cJSON)
return cJSON;
for(var i=cJSON.parameters.length-1;i>=0;i--){
if(!(cJSON.parameters[i].parameters instanceof Array))
continue;
if(cJSON.parameters[i].parameters.length==0){
cJSON.parameters.splice(i,1);
}else{
cJSON.parameters[i] = removeEmptyArray(cJSON.parameters[i]);
}
}
return cJSON;
}
the expect result is {"op":"1","parameters":[{"op":"2-2","parameters":[1,2]}]}, the code works fine.
but when I have this obj:
{"op":"1","parameters":[{"op":"2-1","parameters":[{"op":"3-1","parameters":[]}]},{"op":"2-2","parameters":[1,2,3]}]}
The output is {"op":"1","parameters":[{"op":"2-1","parameters":[]},{"op":"2-2","parameters":[1,2,3]}]}
Obviously it does not dynamically remove the json obj whose "op" is "2-1".
So how to solve it in an elegant way, using pure javascript?

You could use a breadth first algoritm, which look first in the depth and then deletes, if necessary.
function isNotEmpty(object) {
if (Array.isArray(object.parameters)) {
object.parameters = object.parameters.filter(isNotEmpty);
return object.parameters.length;
}
return true;
}
var object = { "op": "1", "parameters": [{ "op": "2-1", "parameters": [{ "op": "3-1", "parameters": [] }] }, { "op": "2-2", "parameters": [1, 2, 3] }] };
isNotEmpty(object);
console.log(object);

Related

Stringify and parse complex object with nested arrays from localStorage

I have a very complex dictionary object consisting of very deeply nested combinations of objects and arrays.
I use a custom deepCopyObject function to make a deep copy of my object throughout my code. That works fine, I'm only including it because it's the only way I'm able to copy the object without a shallow reference.
The issue comes when I try to store and retrieve it from localStorage with parse and stringify. The below code returns:
nextQuestion local_user_dict is [object Object]
test.html:1 Uncaught SyntaxError: Unexpected token o in JSON at position 1
The code below was structured by copying the object from the console in the browser and editing the keys/values. I left all of the structure intact including the "null" values which I didn't include but I assume is due to it being an array nested within the object.
I'm told that stringify and parse should work with deeply nested and complex objects including with arrays (Though I've read elsewhere online that's not true). How can I correctly pass this object and retrieve it from localStorage?
If I use my deep copy function on the object it works fine and displays as it should, the problem only occurs when stringifying and parsing from localStorage.
var test_dict = {
"questions": {
"obj1": {
"words": [
null,
{
"test1": {
"test2": "test7",
"test3": "test6"
},
"test4": "test5"
},
{
"test8": {
"test9": 0,
"test10": "2",
},
"test11": [
null,
{
"test12": {
"no": 0,
"yes": 1
},
"test13": "test14"
},
{
"test15": {
"no": 0,
"yes": 1
},
"test16": "test17"
},
{
"test18": {
"no": 0,
"yes": 1
},
"test19": "test20"
}
]
}
]
}
}
}
localStorage.setItem('user_dict', deepCopyObject(test_dict))
let local_user_dict = localStorage.getItem('user_dict')
console.log('nextQuestion local_user_dict is ', local_user_dict)
let parsed_local_user_dict = JSON.parse(local_user_dict)
console.log('nextQuestion parsed_local_user_dict is ', parsed_local_user_dict)
user_dict = deepCopyObject(parsed_local_user_dict)
console.log('nextQuestion user_dict is ', user_dict)
function deepCopyObject(inObject) {
let outObject, value, key
if (typeof inObject !== "object" || inObject === null) {
return inObject
}
outObject = Array.isArray(inObject) ? [] : {}
for (key in inObject) {
value = inObject[key]
outObject[key] = deepCopyObject(value)
}
return outObject
}
localstorage stores strings. not objects.
change
localStorage.setItem('user_dict', deepCopyObject(test_dict))
to
localStorage.setItem('user_dict', JSON.stringify(test_dict))
and ditch the copy. you can actually use JSON parse and stringify to copy objects.

Javascript - Comparing two arrays then pushing to new array on condition met

Is there any way to compare two arrays and push to an empty array if the condition is met?
Say I have an array of objects. I need to loop through the array of objects, get a ID; then compare that ID to a different array. Then if they match push a value in that array to an empty array?
Array 1:
[{
"addon_service": {
"id": "f6f28cb5-78ad-4ec7-896d-16462b8202fd",
"name": "papertrail"
},
"app": {
"id": "199a1f26-b8e2-43f6-9bab-6e7a6c685ec2",
"name": "mdda-mobiledocdelivery-stg"
}
}]
Array 2
[{
"app": {
"id": "199a1f26-b8e2-43f6-9bab-6e7a6c685ec2"
},
"stage": "staging",
}]
I need to match Array 1 app.ID to Array 2 app.id. If they match check what stage the app is in (staging, development or production). Then push Array 1 addon_service.name to either a staging develpment or
production array depending on what stage the application is in. I'm thinking its simple just cant get my head around it.
I think this is a poorly worded question.
You could use a hash table for lookup and for the stage and use an object for collecting the matches.
var array1 = [{ "addon_service": { "id": "f6f28cb5-78ad-4ec7-896d-16462b8202fd", "name": "papertrail" }, "app": { "id": "199a1f26-b8e2-43f6-9bab-6e7a6c685ec2", "name": "mdda-mobiledocdelivery-stg" } }],
array2 = [{ "app": { "id": "199a1f26-b8e2-43f6-9bab-6e7a6c685ec2" }, "stage": "staging", }],
hash = Object.create(null),
result = {};
array2.forEach(function (a) {
hash[a.app.id] = a.stage;
});
array1.forEach(function (a) {
if (hash[a.app.id]) {
result[hash[a.app.id]] = result[hash[a.app.id]] || [];
result[hash[a.app.id]].push(a.addon_service.name);
}
})
console.log(result);
I think this will do it.
$.each(app1, function(key, value){
$.each(app2, function(k, v){
if(value.app.id == v.app.id){// find apps with the same `id`
if(v[v.stage]){// check if the `stage` array already exists.
v[v.stage].push(value.addon_service)
}else{
v[v.stage] = [value.addon_service];
}
}
});
});
Where app1 is the first array in your question and app2 the second one.

Merge Arrays Combining Matching Objects in Angular Javascript

I have 2 array objects in Angular JS that I wish to merge (overlap/combine) the matching ones.
For example, the Array 1 is like this:
[
{"id":1,"name":"Adam"},
{"id":2,"name":"Smith"},
{"id":3,"name":"Eve"},
{"id":4,"name":"Gary"},
]
Array 2 is like this:
[
{"id":1,"name":"Adam", "checked":true},
{"id":3,"name":"Eve", "checked":true},
]
I want the resulting array after merging to become this:
[
{"id":1,"name":"Adam", "checked":true},
{"id":2,"name":"Smith"},
{"id":3,"name":"Eve", "checked":true},
{"id":4,"name":"Gary"},
]
Is that possible? I have tried angular's array_merge and array_extend like this:
angular.merge([], $scope.array1, $scope.array2);
angular.extend([], $scope.array1, $scope.array2);
But the above method overlap the first 2 objects in array and doesn't merge them based on matching data. Is having a foreach loop the only solution for this?
Can someone guide me here please?
Not sure if this find of merge is supported by AngularJS. I've made a snippet which does exactly the same:
function merge(array1, array2) {
var ids = [];
var merge_obj = [];
array1.map(function(ele) {
if (!(ids.indexOf(ele.id) > -1)) {
ids.push(ele.id);
merge_obj.push(ele);
}
});
array2.map(function(ele) {
var index = ids.indexOf(ele.id);
if (!( index > -1)) {
ids.push(ele.id);
merge_obj.push(ele);
}else{
merge_obj[index] = ele;
}
});
console.log(merge_obj);
}
var array1 = [{
"id": 1,
"name": "Adam"
}, {
"id": 2,
"name": "Smith"
}, {
"id": 3,
"name": "Eve"
}, {
"id": 4,
"name": "Gary"
}, ]
var array2 = [{
"id": 1,
"name": "Adam",
"checked": true
}, {
"id": 3,
"name": "Eve",
"checked": true
}, ];
merge(array1, array2);
Genuinely, extend in Angular works with object instead of array. But we can do small trick in your case. Here is another solution.
// a1, a2 is your arrays
// This is to convert array to object with key is id and value is the array item itself
var a1_ = a1.reduce(function(obj, value) {
obj[value.id] = value;
return obj;
}, {});
var a2_ = a2.reduce(function(obj, value) {
obj[value.id] = value;
return obj;
}, {});
// Then use extend with those two converted objects
var result = angular.extend([], a1_, a2_).splice(1)
Notes:
For compatibility, reduce may not work.
The after array will replace the previous one. This is because of implementation of extend in Angular.

Turn javascript dictionary into JSON format

I have a javascript dictionary:
{
"a": {
"b": {
"c": null,
"d": null
}
}
}
How can I turn it into a JSON object which I can specify the name and children property? Is there any elegant way to do it?
The JSON object could be:
{
name:"a"
children: [{
name:"b",
children: [{
name:"c",
children: null
},{
name:"d",
children: null}]
}]
}
You could create a recursive function for generating your output:
var x = {
"a": {
"b": {
"c": null,
"d": null
}
}
};
function generate(item, key) {
var result = {
name: key,
children: []
};
for (var _ in item)
result.children.push(generate(item[_], _))
if (result.children.length == 0)
result.children = null;
return (key == undefined) ? result.children : result;
}
console.log(JSON.stringify(generate(x), null, 1));
Output:
[
{
"name": "a",
"children": [
{
"name": "b",
"children": [
{
"name": "c",
"children": null
},
{
"name": "d",
"children": null
}
]
}
]
}
]
The above generate function returns a list instead of a dictionary, because it's possible to have more than one name at the root level. But if we are sure that we have only one name at the root name, we can generate the json like this:
console.log(JSON.stringify(generate(x)[0], null, 1));
Here's my solution. It's similar to JuniorCompressor's.
function generate(obj) {
// Return primitives as-is
if (!(obj instanceof Object)) return obj;
// Loop through object properties and generate array
var result = [];
for (var key in obj) {
result.push({
name: key,
children: generate(obj[key])
});
}
// Return resulting array
return result;
}
As mentioned, the resulting object will actually be an array (in case there is more than one root-level property in the original object). If you really need the resulting object to be an object with only properties name and value, then you should access the first element of the resulting array, like this:
generate(obj)[0]
Solution
You need a recursive function, which calls itself for children. Note that in your example, there is only one top-level child (a). I instead use the assumption that the top-level 'name' refers to the name of the actual object itself. If you want to get results exactly like you demonstrate, from an object called 'obj', run toJSON(obj).children[0]. For the overall function, try something like the following:
function toJSON(obj, name) {
var subTree = {
name: name,
children: []
};
if (obj !== null && Object.keys(obj).length >= 1) {
for (var child in obj) {
subTree.children.push(toJSON(obj[child], child));
}
} else {
subTree.children = null;
}
return subTree;
}
Results of toJSON(obj).children[0]:
{
"name": "a",
"children": [{
"name": "b",
"children": [{
"name": "c",
"children": null
},{
"name": "d",
"children": null
}]
}]
}
Results of toJSON(obj, 'obj'):
{
"name": "obj",
"children": [{
"name": "a",
"children": [{
"name": "b",
"children": [{
"name": "c",
"children":null
},
{
"name": "d",
"children": null
}]
}]
}]
}
Here's a line-by-line explanation:
Declares the function, which expects two arguments: the object, and it's name. If you're going to be using toJSON(obj).children[0], though, don't bother with the second argument. It won't affect the result.
Declares the result, an object containing information about the current level and all levels below in the object. If you consider the object a tree, this result contains information about the current branch, and all it's branches.
Declares the property 'name', containing the name/key of the object at the current level. When you call the function, you need to include the name as second argument because there is no way of dynamically finding the name of a variable. They're passed into functions by value. As described above, though, if you're looking for results EXACTLY like those in your example, you're going to use toJSON(obj).children[0], instead of toJSON(obj, 'obj'), and then don't need to bother with the second argument.
Declares the children array, to be filled below
Terminates the declaration begun on Line 2
Checks if the object ISN'T null, and that it has children, using a handy method of the Object built-in object, running Lines 7, 8 and 9 if so
Iterates over the children of the object, running Line 8 for each child
Recursively runs the toJSON() function for each child, to get it's subTree. Because the children can't dynamically figure out their own names, it passes those in as well.
Terminates the for loop begun at Line 7
If there are no children, run Line 11. This is only run if Lines 7, 8 and 9 are not.
Sets children to null (only run if there are no children, as checked by Line 6)
Terminates the else started at line 10
Returns the current subTree, either to the function if called recursively by the function, or to you if you called it yourself
Terminates the function
Information about the Previous Version, Pre-edit
The original function only used one argument, whereas that above has another argument for 'name'. This is because the original tried to figure out the name of each level within that same level, which I have since realized isn't possible in Javascript. Basically, the original didn't work, and an extra argument had to be added to make it work. For records' sake, though, here was the original function:
// THIS FUNCTION DOESN'T WORK. IT'S HERE ONLY FOR HISTORICAL ACCURACY:
function toJSON(obj) {
var subTree = {
name: obj.constructor.name, // This should get the object key
children: []
};
if (Object.keys(obj).length >= 1) { // If there is at least one child
for (var child in obj) {
subTree.children.push(toJSON(obj[child]));
}
} else {
subTree.children = null;
}
return subTree;
}

Using jQuery $.each with Json erroring with 1 result

Basically I am transforming a JSON result into html and using $.each it iterate through multiple keys. For example, I am pulling back facebook posts and iterating through the likes in that post.
The problem lies in the fact that when there are multiple "likes" everything works great! although when there is only 1 "like" the "source" key is removed from the result set and my javascript breaks because I expect it to be there. Any idea why the $.each is skipping a level for single nodes? The following is my code:
* JQUERY **
$.each(post.likes.item, function(i, like){
$(currentpost).find('div.cc_likes').append(like + ',');
console.log(like)
});
* JSON RESULT **
* Single Like
likes": {
"item": {
"source": {
"cta": "Mary Smith",
"url": "http:\/\/www.facebook.com\/",
"photo": {
"image": "https:\/\/graph.facebook.com\/"
}
}
},
Result in console:
Object
cta: "MaryAnn Smith"
photo: Object
url: "http://www.facebook.com/"
* Multiple Likes
"likes": {
"item": [
{
"source": {
"cta": "Bobby Carnes Sr.",
"url": "http:\/\/www.facebook.com",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
},
{
"source": {
"cta": "Jenna Purdy",
"url": "http:\/\/www.facebook.com\",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
},
{
"source": {
"cta": "Kevin Say",
"url": "http:\/\/www.facebook.com\",
"photo": {
"image": "https:\/\/graph.facebook.com\"
}
}
}
],
"count": "10",
"count_display": "10"
},
Result in console:
Object
source: Object
cta: "Kevin Smith"
photo: Object
url: "http://www.facebook.com/"
Since $.each() needs an array or array like object as argument, before using the object post.likes.item check if it is an array of not.
Following code will always pass an array to jQuery -
$.each([].concat(post.likes.item), function(i, like){
$(currentpost).find('div.cc_likes').append(like + ',');
console.log(like)
});
Explanation
[] is an empty array in JavaScript. Every array in JavaScript has a concat method.
[].concat(obj) concats obj to the empty array and returns an array.
if obj is not an array, result is [obj] which is an array with one item.
if obj is an array, then result is a deep copy of obj which is already an array.
More about concat method
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
}
That is the jquery code being run on your JSON return. What's happening is, when you are looking at multiple results, it is looping through the array, return each base level object. However, when you are running it on a single return, it is looping through the object properties(in this case, "source"), and returning the value of that property.
You have two choices here. You can either make sure single items are still put in an array, or you can do a check for single items on the client side. The way Moazzam Khan suggests is the best way to do it in most cases.

Categories