How do you recursively remove nested objects that contain an empty array? - javascript

I initially receive an AJAX response of {"B":{"1":"100","3":{"AA":256}},"A":100} and converted to a javascript object:
var jsonOBJ = {};
jsonOBJ = jQuery.parseJSON(data);
Future responses can be subsets or supersets of the initial response. If the value of a table is unchanged at the server, the stagnant data is replaced with an empty array. Example:
{"B":{"1":"90","2":200,"3":[]}}
{"B":[],"A":20}
Everytime an AJAX response is received, the object is updated with:
jQuery.extend(true, jsonOBJ, jQuery.parseJSON(data));
But I need the javascript object to keep the unchanged portions, so I need to end up with an object that would be equivalent to the following with the example responses above:
jsonOBJ = jQuery.parseJSON('{"B":{"1":"90","2":200,"3":{"AA":256}},"A":20}');
My preferred option would be to remove the empty objects from the converted response. Is there an existing function or a modification to the jQuery extend function that would do this?

You can remove the elements in your response with empty arrays with this code.
It cycles through the top level, looking for any empty arrays and removing them. Any objects it finds, it recurses into to also remove empty arrays in them:
// make sure the ES5 Array.isArray() method exists
if(!Array.isArray) {
Array.isArray = function (arg) {
return Object.prototype.toString.call(arg) == '[object Array]';
};
}
function removeEmptyArrays(data) {
for (var key in data) {
var item = data[key];
// see if this item is an array
if (Array.isArray(item)) {
// see if the array is empty
if (item.length == 0) {
// remove this item from the parent object
delete data[key];
}
// if this item is an object, then recurse into it
// to remove empty arrays in it too
} else if (typeof item == "object") {
removeEmptyArrays(item);
}
}
}
var jsonOBJ = {};
jsonOBJ = jQuery.parseJSON(data);
removeEmptyArrays(jsonOBJ);
You can see it work here: http://jsfiddle.net/jfriend00/U6qMH/

Not really what I asked for, but removing the empty arrays from the JSON string is a solution:
jQuery.extend(true, jsonOBJ, jQuery.parseJSON(data.replace(/"[A-Za-z0-9_]*":\[\]/,'').replace(/{,/,'{').replace(/,}/,'}')));

this will complete the function ;)
removeEmptyArrays(data) {
for (var key in data) {
var item = data[key];
// see if this item is an array
if (Array.isArray(item)) {
// see if the array is empty
if (item.length == 0) {
// remove this item from the parent object
delete data[key];
} else {
this.removeEmptyArrays(item);
}
// if this item is an object, then recurse into it
// to remove empty arrays in it too
} else if (typeof item == "object") {
this.removeEmptyArrays(item);
}
}
},

Related

Javascript: Dictionary 'key' value becomes null outside for loop

I have a function which takes a list of dictionaries [{}] as an argument. It manipulates this list of dicts by adding a new key: value pair to it where value is again a list of dictionaries. This is what the function looks like, I've added comments to explain it.
function addFilesToProjects(nonUniqueArray, lists) {
var fileList = [{}]; //this will contain the list of dictionaries that I want to add as a key to the array 'nonUniqueArray'
var filesArray = []; //this was just for testing purposes because I want to access the modified version of nonUniqueArray outside the function, which I'm not able to (it shows undefined for the new key:value pair)
for (var i = 0; i < nonUniqueArray.length; i++) {
lists.forEach(function (list) {
fileNameString = JSON.stringify(list['name']).slice(2, -2);
if (fileNameString.indexOf(nonUniqueArray[i]['title']) !== -1 && fileNameString !== nonUniqueArray[i]['title']) {
fileList.push({
'name': fileNameString
});
}
});
nonUniqueArray[i]['files'] = fileList;
//this logs out the right key:value pair to the console
console.log(nonUniqueArray[i]);
filesArray.push(nonUniqueArray[i]);
while (fileList.length > 0) {
fileList.pop();
}
}
//however, now I get everything as before except the new 'files' key has empty list [] as its value :(
console.log(nonUniqueArray);
return filesArray;
}
I have no clue why is this happening, can someone help?
You seem to think that you are adding a copy of fileList into each dictionary, but in fact are adding the same fileList into each (that is, each is a reference to the same object) so that, as #vlaz points out, when you empty out the original, you are in fact emptying out what appears in each dictionary.

How do I get JSON data from one HTML Apps Script file in another HTML file?

I have some data in a file called data.json.html in an Apps Script project that I would like to access from JavaScript. The file looks like this:
<script>
var data = {"book1":{"1":25, "2":17}, "book2":{"1":37, "2":4, "3":12}};
</script>
I have tried to access this data by the following means:
Attempt #1
<script type="text/javascript" src="data.json"></script>
<script>
var books = JSON.parse(data);
</script>
Attempt #2
<?!= include("data.json") ?>
<script>
var books = JSON.parse(data);
</script>
Neither one of these worked. Can anyone tell me the correct way of doing this?
Edit: Sorry I forgot to include information about what "it doesn't work" means. Basically, the data doesn't make it into the variable, it's supposed to go into, and there's no error telling me what happened in the execution transcript.
If anyone wants to look at the full script, it is embedded in the Google Doc here. Just go to Tools > Script editor... to find it.
This may not be optimum but it will work.
Depending whether the html file is on your domain (then just simply access it) otherwise retrieve the data by an HTTP request. Then use this to find the son location and its length:
var string = "responseString",
substring = "book1";
var jsonStr = str.substring(string.indexOf(substring), 100);
This can be used if you know for example the length is 100 from the matching book1. If you don't know the length use something like above ton find location of "};".
Then use jsonStr in the following to get the key values.
//return an array of objects according to key, value, or key and value matching
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else
//if key matches and value matches or if key matches and value is not passed (eliminating the case where key matches but passed value does not)
if (i == key && obj[i] == val || i == key && val == '') { //
objects.push(obj);
} else if (obj[i] == val && key == ''){
//only add if the object is not already in the array
if (objects.lastIndexOf(obj) == -1){
objects.push(obj);
}
}
}
return objects;
}
//return an array of values that match on a certain key
function getValues(obj, key) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getValues(obj[i], key));
} else if (i == key) {
objects.push(obj[i]);
}
}
return objects;
}
//return an array of keys that match on a certain value
function getKeys(obj, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getKeys(obj[i], val));
} else if (obj[i] == val) {
objects.push(i);
}
}
return objects;
}
var json = '{"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossList":{"GlossEntry":{"ID":"SGML","SortAs":"SGML","GlossTerm":"Standard Generalized Markup Language","Acronym":"SGML","Abbrev":"ISO 8879:1986","GlossDef":{"para":"A meta-markup language, used to create markup languages such as DocBook.","ID":"44","str":"SGML","GlossSeeAlso":["GML","XML"]},"GlossSee":"markup"}}}}}';
var js = JSON.parse(json);
//example of grabbing objects that match some key and value in JSON
console.log(getObjects(js,'ID','SGML'));
//returns 1 object where a key names ID has the value SGML
//example of grabbing objects that match some key in JSON
console.log(getObjects(js,'ID',''));
//returns 2 objects since keys with name ID are found in 2 objects
//example of grabbing obejcts that match some value in JSON
console.log(getObjects(js,'','SGML'));
//returns 2 object since 2 obects have keys with the value SGML
//example of grabbing objects that match some key in JSON
console.log(getObjects(js,'ID',''));
//returns 2 objects since keys with name ID are found in 2 objects
//example of grabbing values from any key passed in JSON
console.log(getValues(js,'ID'));
//returns array ["SGML", "44"]
//example of grabbing keys by searching via values in JSON
console.log(getKeys(js,'SGML'));
//returns array ["ID", "SortAs", "Acronym", "str"]
In that case you can use this to access a local file:
function readTextFile(file)
{
var rawFile = new XMLHttpRequest();
rawFile.open("GET", file, false);
rawFile.onreadystatechange = function ()
{
if(rawFile.readyState === 4)
{
if(rawFile.status === 200 || rawFile.status == 0)
{
var allText = rawFile.responseText;
alert(allText);
}
}
}
rawFile.send(null);
}
and file in the first line will be the path and file name.

Most efficient way to remove a list of values from a javascript object by keyname

I need to find the most efficient way to remove values from a arbitrarily nested javascript object based on a list of 'keys-to-remove'. i.e.
var obj = {a:1, b:2, c:{d:1, e:1}};
var ignoreList = ["a","e"] (could also be ['a', 'c.e'])
removeIgnoredValues(obj, ignoreList) => {b:2, c:{d:1}}.
Now obviously this is easy enough to do if you don't care about efficiency, and my current implementation has been serving me well up till now. But now I'm having to deal with objects that have 6 levels and large arrays of data.
If anyone has a solution or link to one that would be awesome :)
Cheers
EDIT: Current implementation looks like this. It works (and deals with circular references). But is too slow.
/**
* Returns a sanitised string of an object, removing any functions and unwanted properties.
* #param {int} obj. The object to be stringified
* #param {Array[]} ignoreList. A array of object properties that should be removed.
*/
function sanitise(obj, ignoreList){
if(obj == undefined){
throw "Can't sanitise an undefined object"
}
var entry = JSON.parse(JSON.stringifyOnce(obj));
for(var i in entry){
if(entry.hasOwnProperty(i)){
if(contains(ignoreList, i)){
delete entry[i];
} else if(typeof(entry[i]) == "object" && entry[i] != null){
entry[i] = sanitise(entry[i], ignoreList);
}
}
}
return entry;
}
JSON.stringifyOnce = function(obj, replacer, indent){
var printedObjects = [];
var printedObjectKeys = [];
function printOnceReplacer(key, value){
var printedObjIndex = false;
printedObjects.forEach(function(obj, index){
if(obj===value){
printedObjIndex = index;
}
});
if ( key == ''){ //root element
printedObjects.push(obj);
printedObjectKeys.push("root");
return value;
}
else if(printedObjIndex+"" != "false" && typeof(value)=="object"){
if ( printedObjectKeys[printedObjIndex] == "root"){
return "(pointer to root)";
}else{
return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase() : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
}
}else{
var qualifiedKey = key || "(empty key)";
printedObjects.push(value);
printedObjectKeys.push(qualifiedKey);
if(replacer){
return replacer(key, value);
}else{
return value;
}
}
}
return JSON.stringify(obj, printOnceReplacer, indent);
};
Okay, figured out a pretty nice way. You just make an ignore list with roughly the same object structure as the object to be ignored.
function ignore(obj, list){
for(var i in list){
var type = Object.prototype.toString.call(list[i]);
if(type == "[object String]"){
delete obj[i];
}
else if (type == "[object Object]"){
ignore(obj[i], list[i])
}
else if (type == "[object Array]"){
var objList = obj[i];
var subList = list[i][0];
for(var n in objList){
ignore(objList[n], subList)
}
}
}
}
x = {a:1, b:[{c:1, d:1}, {c:1, d:1}, {c:1, d:1}], e:1}
ignoreList = {'e':'e', 'b':[{'c':'c'}]}
ignore(x, ignoreList) => {a:1, b:[{d:1}, {d:1}, {d:1}]}
We can create a look-up table object for the original object, to delete any given key in O(1) time. The implementation will involve you adding custom functions to add/remove from the object.
(function() {
var lookUpTable = {};
myObj.prototype.insert = function(key, value) {
// add key to the myObj
// insert an Entry for parent of key in lookUpTable
// lookUpTable = { "a" : [myObj.b, myObj, myObj.c.d.e] }
}
myObj.prototype.ignore = function(ignoreList) {
for( key in ignoreList ) {
for( parent in lookUpTable[key] )
delete parent[key];
delete lookUpTable [key];
}
}
}());
Now you can call the insert function to insert the key-value:
myObj.insert('a.b.c.d', ['p', 'g']);
and call the ignore function to delete the object:
myObj.ignore(['d', 'e']);
Sorry for just giving the incomplete code. But, you should be able to implement the details quiet easily. Hope you get the idea.
For the example given by you:
obj = {a:[{b:1, c:1}, {b:1, c:1}, {b:1, c:1}]
and you want to ignore all the 'b's. Note that the lookup table entry values were arrays and not just a single value. This is where the power of ignoring multiple entries with the same name, comes in. In this case, the entry for 'b' would be something like this.
lookupTable = {
b : [ // The parent objects of 'b'
obj['a'][0],
obj['a'][1],
obj['a'][2]
]
}
Basically, the lookuptable holds an array of references to all the objects that contain the key 'b'. So, you iterate through each of these parent objects and delete their 'b' entry.
$.each(lookupTable['b'], function( parent ) {
delete parent['b']; // Deletes 'b' inside of every parent object
});
You populate this lookup table entry while inserting into obj or while obj is loaded for the first time. If obj is hard-coded, you could also generate the lookupTable once and hard-code it. Probably in along with your minify Javascript scripts. Although populating it at run-time is also quiet fine.

append values to array inside object based on key in JS

I have an object and want to append values to arrays inside it based on key.
For eg.
var A = {};
A[room1] = ["1"];
A[room2] = ["2"];
A[room3] = ["3"];
which will be looking like
A = {"room1":["1"], "room2":["2"], "room3":["3"]};
But what I want is that whenever a user gives some value corresponding to the already added key, instead of overwriting the previous value I want to append in it.
If, for example, value came as 101 and I want to add it to key room1 in such a way that later these values can be retrieved easily.
So the whole object this time becomes
A = {"room1":["1","101"], "room2":["2"], "room3":["3"]};
Now if I want to add 201 to key room2 , it will be:
A = {"room1":["1","101"], "room2":["2","201"], "room3":["3"]};
What I have I tried?
I have an array. I don't want to use many arrays.
var arr = [];
whenever value cam I push it to the array
arr.push(value);
But pushing it to the array leads to adding values to all not to the corresponding key
[[The first part of this answer is based on a previous version of the OP's question which has now been edited to a different problem. See the second part of this answer for the solution which applies to the current edit of the question (It really messes things up when the whole question gets changed to something else.]]
Original Answer
You just have to test if the key already exists and examine if there's already an array there. If the key doesn't exist, add it. If the key exists and it's already an array, just push another value into the array. If the key exists, but it's not an array, grab the value and then reset the key to an array with the first two values in it.
Here's code to do that:
function addValue(obj, key, value) {
if (obj.hasOwnProperty(key)) {
// check if it's already an array using the recommended way of detecting an array
if (Object.prototype.toString.call(obj[key]) === "[object Array]")
obj[key].push(value);
} else {
var firstVal = obj[key];
obj[key] = [firstVal, value];
}
} else {
obj[key] = value;
}
}
Latest Answer
FYI, your data structure choice is difficult to both read and write because both reader and writer have to check the type of a value before they can operate on it. It would be much easier if items were just always arrays with one or more elements in them like this.
// one item for each key
A = {"room1":["1"], "room2":["2"], "room3":["3"]};
// add 101 to room 1
A = {"room1":["1","101"], "room2:["2"], "room3":["3"]};
// add 201 to room 2
A = {"room1":["1","101"], "room2":["2","201"], "room3":["3"]};
Then, you would need any special code to read and to write, you'd just check if the key exists and if so, push a new value into it. If not, add the array.
In this case, adding a value would just be this
function addValue(obj, key, value) {
if (obj.hasOwnProperty(key)) {
obj[key].push(value);
} else {
obj[key] = [value];
}
}
try this
function pushValue(obj, key, value)
{
if(obj.hasOwnProperty(key) {
var currentVal = obj[key];
if(currentVal instanceof Array)
obj[key].push(value);
else
obj[key] = [currentVal, value];
} else {
alert("No such key.");
}
}
Your requirement can be achieved in this way too.
function pushValue(obj, key, value)
{
if(obj[key])
{
if(obj[key].push)
{
obj[key][obj[key].length] = value;
}
else
{
var xContainedVal = obj[key];
obj[key] =[xContainedVal, value];
}
}
else
{
alert("Key Not Found!");
}
}
Updated:
function pushValue(obj, key, value)
{
if(obj[key])
{
obj[key][obj[key].length] = value;
}
else
{
obj[key] =[value];
}
}
A working demo

How to remove null values from javascript object

I have a javascript object that contains two arrays. Sometimes one of the arrays may be empty. I'm trying to loop through the object via a recursive function but I don't want any arrays that are empty or empty strings to enter the loop. What I have so far is producing the error Typeerror: obj.filter is not a function.
NOTE: obj is in this example has two arrays inside of it, but really, it could be anything that I pass into the function.
var obj = {
selected: [ "value1", "value"2],
unselected: []
}
function clearAndSetSelectElement($elem, obj, isEmpty) {
if(isEmpty) $elem.empty(); //empty the select element if it isn't empty
$.each(obj.filter(function(v){return v != null}), function() { //filter out empty arrays or empty strings
if(this instanceof Array) clearAndSetSelectElement($elem, this, false); //if this is an array make recursive call
$elem.append("<option />").val(this).text(this)); //append value to select element
});
}
obj.filter is not a function, but obj.selected.filter should be (obj is an object, not an array).
It won't work in IE7 by default I guess, you'll either have to copy the polyfill from Mozilla Developer's Network (MDN) or use Modernizr.
May be you can try this (if you want to populate a select with some options from an array)
HTML
<select id="sel"></select>​
JS
function setSelect(elem, obj)
{
for(var o in obj)
{
if(obj[o] instanceof Array && obj[o].length)
{
$.each(obj[o], function(key, value) {
elem.append($("<option></option>").attr("value",value).text(value));
});
}
}
}
Populate the selct using setSelect function
var obj = {
selected: [ "value1", "value2"],
unselected: []
}
var elem=$('#sel');
setSelect(elem, obj);
DEMO.

Categories

Resources