I'm building a faceted/filtering search for a project I'm currently working on and I'm looking for a function (or jquery plugin) to help me do this. Let's say I have an object like the following:
var options = {
"country": 2,
"Gender": 1
}
I then have a function called filter(). I pass filter the key and value of the option I want to toggle/add/update. My current function is causing me nothing but trouble and I'm too far down the debugging rabbit hole to make it worth posting. Essentially I have this:
filter('country', 3);
would update the 'country' key to have '3' as its new value.
filter('Age Group', 4);
would add that key/value pair to the options object.
filter('Gender', 1);
would remove the 'Gender' key from the object completely.
How about this obvious implementation:
function filter(o, key, val) {
if (o.hasOwnProperty(key) && o[key] == val) {
delete o[key];
} else {
o[key] = val;
}
}
?
I'd suggest this:
function filter(o, key, val) {
if (o[key] === val) {
delete o[key];
} else {
o[key] = val;
}
}
If the key does not exist as a property of the object, then o[key] will be undefined which will not match val so it will add the key/value.
If the key exists, but the val does not match, it will set the key/value
If the key exists and the val matches, it will remove the key.
Note: it's important to use === to make sure there's no auto-type conversion in the comparison.
Or, if you want a function that automatically works with your options variable, then you could use this:
function filter(key, val) {
if (options[key] === val) {
delete options[key];
} else {
options[key] = val;
}
}
Personally, I think code is more readable if you make explicit setKey and removeKey functions rather than this variable operation based on whether the key previously exists or whether it matches.
Related
I have the following function which needs to traverse an incredibly complicated json file. This json file goes in and out of arrays and objects:
removeRecords(data, id)
{
let k;
for (k of Object.keys(data)) {
if ((data[k] != null))
{
if ((data[k].hasOwnProperty('type')) && (data[k].hasOwnProperty('characterId')))
{
if ((data[k].type == "enableEscortingItem") || (data[k].type == "disableEscortingItem"))
{
if ((data[k].param1 == id))
{
delete data[k];
}
}
}
else
{
if ((typeof data[k] != 'string'))
{
this.removeRecords(data[k], id);
}
}
}
}
}
But when I delete and it's removing an object from an array I get: [null] whereas I want [] (if it's the only object in the array of course). I'm aware the appropriate way would be to use splice, but I can't use that since I don't know whether I'll be in an array or not at that time. I've also tried using .pop() and .shift().
Yes, when you delete an item from an array it is getting null instead of old value.
If you want replace null with something else use Array.map method.
If you want throw away element from the array - use Array.filter.
If you want to create completely new data type instead of you array (string, number, object etc) - use Array.reduce
I'm working on a way that takes advantage of the replacer function argument in JSON.Stringify in JavaScript in order to change the word-case (toUpper /toLower case), the problem is my JSON is not straight key:value, some values are keys also and they have values themselves, so I need to go through all keys and values, check if the value is also a key and make sure I change the case (toUpper or toLower) for all keys and values.
I know that the replacer function in JSON.Stringify(object,ReplacerFunction) iterates through all keys and values and makes the modifications inside then return keys and values, but although I've been reading about this for a wWhile I can't apply it, and I am not sure if I should apply recursion inside the replacer function or how, any help is appreciated.
Code I had:
function _replacer(name,val){
if(typeof val != "object"){
return val.toString().toUpperCase()
}
if(typeof name != "object"){
return name.toString().toUpperCase()
}
console.log("key = "+name+" type: "+typeof name);
console.log("value ="+val+" type: "+typeof val);
}
Also:
function _replacer(name,val){
if(typeof val != "object" &&typeof val ==="string"){
return val=val.toUpperCase()
}
if(typeof name != "object" &&typeof name ==="string"){
name=name.toUpperCase()
}
return val;
}
Also , i eventually got to this stage :
var res = JSON.parse(JSON.stringify(j, function(key, value) {
return typeof value === "string" ? value.toUpperCase() : value
}));
but this code only capitalizes the very lower level values, not all the keys/values, the reason is because i can only return one value from the replacer function, which is in this case the value.
The replacer function in JSON.stringify, does not allow you to replace keys, as documented in MDN. It allows you to transform values, or to omit key/value pairs altogether (by returning undefined).
Your best bet is probably to transform the object before stringifying it. Underscore or Lodash would make this pretty easy, but you can do it natively without too much trouble like this:
const xform = obj => {
return Object.keys(obj).reduce((xformed, key) => {
let value = obj[key]
if (typeof value ==='string') value = value.toUpperCase()
else if (typeof value === 'object') value = xform(value)
xformed[key.toUpperCase()] = value
return xformed
}, {})
}
console.log(JSON.stringify(xform({a: 'b', c: 1, d: {e: 'f'}})))
// {"A":"B","C":1,"D":{"E":"F"}}
You could also use RegEx and replace after it is stringified if you are so inclined. The code is certainly shorter, but perhaps less readable:
const stringified = JSON.stringify({a: 'b', c: 1, d: {e: 'f'}})
console.log(stringified.replace(/".+?"/g, s => s.toUpperCase()))
// {"A":"B","C":1,"D":{"E":"F"}}
I am trying to check if an object is already in an array, following this answer here: How to determine if object is in array
I adjusted the function to suit my needs, and now it looks like this:
var _createDatesArray, _objInArray;
_objInArray = function(array, obj) {
var i;
i = 0;
while (i < array.length) {
console.log("array[i] == obj is ", array[i] === obj, " array[i] is ", array[i], " and obj is ", obj);
if (array[i] === obj) {
return true;
}
i++;
}
};
_createDatesArray = function(val) {
var result;
if (val != null) {
result = {
text: val
};
if (!_objInArray(scope.datesQuestion.dates, result)) {
scope.datesQuestion.dates.push(result);
}
return console.log(scope.datesQuestion.dates);
}
};
What I need to do, is basically see if the object is already in the array, and if is,t return true.
When debugging, the result of the console log is the following:
array[i] == obj is false array[i] is {text: "10/08/17"} and obj is
{text: "10/08/17"}
and the function says they are different (array[i] == obj is false) but they look the same to me.
I also checked the type of both, which is this:
typeof array[i] is "object"
typeof obj is "object"
can you help me with this? Why are they different? what can be different?
_createDatesArray is called when $scope of my angular app changes its value based on a ng-model, but I don't think this is relevant
They're two different objects with the same content. Comparing them with == or === will yield false.
Since you're using AngularJS, you can use angular.equals() instead to perform a deep comparison of the object's properties.
The objects you are comparing don't have the same reference, so == is returning false. See Object comparison in JavaScript for a more detailed explanation.
In this particular case, you could simply compare the text of dates to see if they are equivilant. However this wouldn't work for all objects like the function name suggests.
if (arr[i].text === obj.text)
Alternatively, you could create a method specific for checking if your array includes a given date and simplify it greatly using Array.prototype.some:
dateInArray = function (array, date) {
return array.some(function (arrayDate) {
return arrayDate.text === date.text
})
}
Or, more succinctly using ES6 arrow functions:
dateInArray = (array, date) => array.some(arrayDate => arrayDate.text === date.text)
array[i] === obj will return true ONLY if its the same object. In the link that you have referred the object being checked is the same object that is inserted in the array, thats why it returns true. In your case, you are creating a new object 'result' and adding the value in there. So the array does not contain the exact same object and hence returns false.
If 'text' is the only property in the object, instead of checking for the entire object you could check if the 'text' property in both the objects is same.
_objInArray = function(array, obj) {
var i;
i = 0;
while (i < array.length) {
if (array[i].text === obj.text) {
return true;
}
i++;
}
};
This happens because objects in JS are compared by reference, but not by values they have. But you need to compare objects by their value. So you need to get some third-party function or to write your own. One more option is to use angular built-in equals function.
angular.equals($scope.user1, $scope.user2);
For a better understanding you can read a good article on this subject here.
The following function takes an object, loops through each value and returns false if the object or its children have an empty or undefined property web. Otherwise, it returns true:
hasNoCategories (object) {
for (let key in object) {
const value = object[key]
for (let i = 0; i < value.length; i++) {
const item = value[i]
if (item.web !== undefined && item.web !== '') return false
}
if (key === 'web' && value !== '') {
return false
}
}
return true
},
Example input:
{
"livingroom": [],
"garage": [],
"outdoors": [],
"other": [],
"id": "ZI4hteKxgr",
"name": "Cuiti",
"description": "",
"user": "",
"date": "2016/5/13",
}
How to rewrite this function without using for loops?
I'm not 100% sure what you expect the code to do, because your existing code and your description differ.
Your description is, rephrased, that this function checks whether object.web or any object.XXX.web are undefined. Your code however assumes that all members are arrays and checks whether object.web or object.XXX[YYY].web are undefined. (Note that it also doesn't do it correctly and accesses .length even though the member in question might be undefined.)
Since I'm not sure which of those is right, I'm providing two answers.
Functionality as per your textual description:
function hasNoCategories(object) {
if(!object.web) return false;
return Object.keys(object).every(function(key) {
if(typeof object[key] !== 'object') return true;
return !!object[key].web;
});
}
Functionality as per your existing code: (but with the length property access fixed)
function hasNoCategories(object) {
if(!object.web) return false;
return Object.keys(object).every(function(key) {
if(!Array.isArray(object[key])) return true;
return object[key].every(function(el) {
if(typeof object[key] !== 'object') return true;
return !!el.web;
});
});
}
To understand how this works, check out the documentation on Object.keys (which returns an array with the names of all keys in your object) and Array.prototype.every (which runs a callback function for every element in an array and returns true only if the callback returned true for every element).
Note that I'm assuming that your "empty or undefined" should reject all kinds of falsy values including null and the number (not string) zero. If not, then all the checks like if(!something) and return !!something would need to be changed to if(typeof something === "undefined" || something === '') and return typeof something !== "undefined" && something !== '', respectively.
Side note to prevent nitpicking: Of course there are still loops going on. But it was specifically asked "without for loop" and there is no for in this code.
I assume this is what you are looking for:
var hasNoCategories = function(object) {
if (!object.web) {
return false;
}
for (let key in object) {
var value = object[key];
if (!value.web) {
return false;
}
}
return true;
};
I got rid of 1 loop. But this cannot be done without loops because you have to loop over all children. You can hide this loop inside some another function but you cannot get rid of it.
If you really don't want to use loops (I don't know why), one of your options is to serialize the object and phrase the string for the word "web".
var s = JSON.stringify(object);
var webIndex = s.indexOf('web');
Now perform some checks around this index to ascertain if that has the value 'undefined' or ''. Please keep in mind that the word "web" can match as a part of another property name too. So, you need to include this possibility too to your checks.
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