Javascript function accepts both arrays and strings as parameter - javascript

I have this code:
var showRegion = function(key) {
if (key in regionOptions) {
var entry = regionOptions[key];
var builder = entry.builder;
var layoutObj = entry.layoutObj;
var viewStarter = entry.viewStarter;
var view = new builder();
logger.info('Controller.' + key + ' => CreateAccountLayoutController');
Controller.layout[layoutObj].show(view);
view[viewStarter]();
}
};
What I need is that the parameter should be able to accept an array or a string, and should work either way.
Sample function calls:
showRegion('phoneNumberRegion');
showRegion(['phoneNumberRegion', 'keyboardRegion', 'nextRegion']);

This post is old, but here is a pretty good tip:
function showRegions(keys) {
keys = [].concat(keys)
return keys
}
// short way
const showRegions = key => [].concat(keys)
showRegions(1) // [1]
showRegions([1, 2, 3]) // [1, 2, 3]

var showRegion = function(key) {
if (typeof key === 'string')
key = [key];
if (key in regionOptions) {
...
No need to make a code for each case, just convert key string into an array of one element and the code for arrays will do for both.

you could use typeof to check for the type of your argument and proceed accordingly, like
var showRegion = function(key) {
if( typeof key === 'object') {
//its an object
}
else {
//not object
}
}

You can use the fact that string.toString() always returns the same string and Array.toString() returns a comma-delimited string in combination with string.split(',') to accept three possible inputs: a string, an array, a comma-delimited string -- and reliably convert to an array (provided that you're not expecting commas to be part of the values themselves, and you don't mind numbers becoming strings).
In the simplest sense:
x.toString().split(',');
So that
'a' -> ['a']
['a','b'] -> ['a','b']
'a,b,c' -> ['a','b','c']
1 -> ['1']
Ideally, you may want to tolerate null, undefined, empty-string, empty-array (and still keep a convenient one-liner):
( (x || x === 0 ) && ( x.length || x === parseFloat(x) ) ? x.toString().split(',') : []);
So that also
null|undefined -> []
0 -> ['0']
[] -> []
'' -> []
You may want to interpret null/empty/undefined differently, but for consistency, this method converts those to an empty array, so that downstream code does not have to check beyond array-having-elements (or if iterating, no check necessary.)
This may not be terribly performant, if that's a constraint for you.
In your usage:
var showRegion = function(key) {
key = ( (key || key === 0 ) && ( key.length || key === parseFloat(key) ) ? key.toString().split(',') : []);
/* do work assuming key is an array of strings, or an empty array */
}

Related

How to check if a value in object is a primitive?

So basically i want to check if my data (which is in JSON Format) has a value which is a primitive. So let's take an Example: I get data that looks like this: {name: Artikel, value: {"ArtNr": 1234}} and i want to check if 1234 is primitive or not. I also want to differentiate if the result is an Array with Primitives in it or an Object. Is that possible?
function isObjectContainingPrimitiveValues(test) {
let values = Object.values(test);
for (let i of values) {
console.log(i);
return (typeof i === 'string' || typeof i === 'number' || typeof i === 'boolean' || typeof i === null || typeof i === undefined);
}
}
UPDATE
So with the awesome help of MaxK i have built a isResultContainingPrimitiveValues() Function which checks my data for Primitive/ Primitive Arrays and or Objects. The following part is the trickiest at least with my understanding. The following Example will hopefully help you understand my problems better.
So my let namesSplit = treeNode.name.split('.'); variable splits the data it gets and has as a result of nameSplit : Artikel,Artnr. Next i defined a key variable let key = namesSplit[0]; which has key : Artikel as a result. Than i define a contextEntry variable let contextEntry = exprData.contextEntry.find(_x => _x.name === key); and has contextEntry : {"name":"Artikel","value":{"ArtNr":123}} as a result. Now i want to check: if there's another split namesSplit.length > 1 check isResultContainingPrimitiveValues(). If it is primitive, throw an error, if it is an object -> get values from it and if it is an array -> get values form there. I know it's a lot but from all the confusing stuff i can't seem to think clear, so i appreciate every help i can get.
You are returning from your function on the first iteration. You should only return false if you found an non-primitive and if you were able to loop over all values you can return true because all values are primitives:
function isObjectContainingPrimitiveValues(testObj) {
let values = Object.values(testObj);
for(let i of values){
if (typeof i === 'object') {
return false;
}
}
return true;
};
Update:
After reading your comment i changed the code to check for arrays with primitives as well. The idea is, to create a new function which only checks if a single value is a primitive.Now if we find an array, we can simply check - with the help
of the arrays some function - if some element, inside the array is not primitive. If so return false,otherwise we do the same checks as before:
function isObjectContainingPrimitiveValues(testObj) {
let values = Object.values(testObj);
for (let i of values) {
if (Array.isArray(i)) {
if (i.some(val => !isPrimitive(val)))
return false;
} else {
if (!isPrimitive(i))
return false;
}
}
return true;
};
function isPrimitive(test) {
return typeof test !== 'object'
}
Array and object types all return a 'typeof' 'object'. so you can check against an object instead of checking against multiple conditions.
So the return statement will be:
return (typeof i === 'object').
Number, string, undefined, null will all return false on the statement above.

How do you check if a property value is an array within an object?

If we have:
var obj = {
key: [1000, 10, 50, 10]
};
-If the array at the given key is empty, it should return 0.
-If the property at the given key is not an array, it should return 0.
-If there is no property at the given key, it should return 0.
I'm trying to get the average of the elements at the property (key) with a function, getAverageOfElementsAtProperty(obj, 'key').. I managed that part with exception of the 3 points above.
I tried this:
if (obj[key].constructor != Array || !obj.hasOwnProperty(key) ||
obj[key] == []) {
return 0;
}
But I'm unsure if using three or operational is the correct move...
You can check multiple conditions like this
if (
typeof obj === 'object' && // you have an object
'key' in object && // it contains a "key"
Array.isArray( obj['key'] ) // it is an array
)
So for each condition you mentioned it can be done as follows
If the array at the given key is empty, it should return 0.
obj.key.length === 0
If the property at the given key is not an array, it should return 0.
!Array.isArray(obj[key])
If there is no property at the given key, it should return 0.
!obj.hasOwnProperty("key").
You can directly check for a falsy value to check the existence. Also, check if the the value is an array by using the Array.isArray function and then for checking the length of array, use the length property.
if(!obj.hasOwnProperty("key") || !Array.isArray(obj[key]) || obj.key.length === 0)){
return 0;
}
I like to break up my if into the business logic that it needs to fulfill. I find it easier to understand when I get back to it later on. So I would do it this way
if (!obj.hasOwnProperty(key)) return 0; // If there is no property at the given key
if (!Array.isArray(obj[key])) return 0; // If the property at the given key is not an array
if (obj[key].length === 0) return 0; // If the array at the given key is empty
// add this point we know there is a valid array
You can check, first, [1] if the key exists, then [2] if it's array, and, then, [3] if it's not empty.
Order matters: due to Short-circuit evaluation, if it evaluates true in the first condition, it skips nexts checks (then, you can safely assume that the key exists, that it's an array and so on...)
var obj = {
key: [1000, 10, 50, 10],
key2: [],
key3: "abc"
};
function isValid(obj, key) {
if (
(typeof obj[key] === 'undefined') || //If there is no property at the given key (first)
(!Array.isArray(obj[key])) || //If the property at the given key is not an array (IE9>=)
(obj[key].length == 0) //If the array at the given key is empty,
) {
return 0;
} else {
return 1; /* or whathever you want */
}
}
console.log(isValid(obj, "key")); //1
console.log(isValid(obj, "key2")); //0
console.log(isValid(obj, "key3")); //0
console.log(isValid(obj, "key4")); //0
Note that (typeof obj[key] === 'undefined') is not necessary here, because Array.isArray(undefined) === false (check docs), so, you can skip this check.
if (obj[key].constructor != Array || ... will throw an error if obj[key] is undefined because, then, you'll be accessing constructor of undefined which will throw an error. What you need to do is check if a the value obj[key] exist then if it is an array like this:
if(obj[key] !== undefined || obj[key].constructor != Array)
return 0;
In general: The or (||) operator will stop checking (evaluating) after the first valid check. Consider check0 || check1 || ... || checkN, if checki is true then checki+1 ... checkN won't be evaluated at all. Because only one valid check is sufficient. Here is an example:
var n = 5;
if(n == 5 || console.log("Here 1"))
;
n = 4;
if(n == 5 || console.log("Here 2"))
;
As you can see, Here 1 is never loged because the code console.log("Here 1") is never reached.

Efficient way to compare arrays in javascript

I need to compare the elements from two arrays as follows:
arr1[0] ? arr2[0]
arr1[1] ? arr2[1]
arr1[2] ? arr2[2]
etc.
I wrote some code but it seems to be slow when I try to compare 1000 objects like this on each array :
{
"id":"event707",
"name":"Custom707",
"type":"disabled",
"default_metric":false,
"participation":"disabled",
"serialization":"always_record"
}
This is how my function looks like (just an example for two arrays with hard coded data).
function compare() {
var step = 0;
var fruits1 = [{"apple":25},{"bannana":36},{"orange":6}];
var fruits2 = [{"apple":25},{"bannana":36},{"orange":6}];
for(var i=0;i<fruits1.length;i++) {
for(var j=step;j<fruits2.length;j++) {
console.log("FRUIT1");
console.log(JSON.stringify(fruits1[i]));
console.log("FRUIT2");
console.log(JSON.stringify(fruits2[j]));
console.log("----------------------");
if(JSON.stringify(fruits1[i])!== JSON.stringify(fruits2[j])) {
//do something
}
step = step + 1;
break;
}
}
}
With an invention of Object.prototype.compare() and Array.prototype.compare() this job becomes a very simple task. Array compare can handle both primitive and reference type items. Objects are compared shallow. Let's see how it works;
Object.prototype.compare = function(o){
var ok = Object.keys(this);
return typeof o === "object" && ok.length === Object.keys(o).length ? ok.every(k => this[k] === o[k]) : false;
};
Array.prototype.compare = function(a){
return this.every((e,i) => typeof a[i] === "object" ? a[i].compare(e) : a[i] === e);
};
var fruits1 = [{"apple":25},{"bannana":36},{"orange":6}],
fruits2 = [{"apple":25},{"bannana":36},{"orange":6}];
console.log(fruits1.compare(fruits2));
Simple function without library:
var arr1 = [1,2,3];
var arr2 = [1,2,4];
//This function takes one item, the index of the item, and another array to compare the item with.
function compare(item, index, array2){
return array2[index] == item;
}
// the forEach method gives the item as first parameter
// the index as second parameter
// and the array as third parameter. All are optional.
arr1.forEach(function(item, index){
console.log(compare(item, index, arr2));
});
Combine this with the answer Abdennour TOUMI gave, and you have an object comparison method :)
For simple objects you could use JSON.stringify(obj1) === JSON.stringify(obj2).
More info on object comparison can be found in this answer
Use underscore array functions. I would go with intersection
http://underscorejs.org/#intersection
You can use the following static method for Object class : Object.equals
Object.equals=function(a,b){if(a===b)return!0;if(!(a instanceof Object&&b instanceof Object))return!1;if(a.valueOf()===b.valueOf())return!0;if(a.constructor!==b.constructor)return!1;for(var c in a)if(a.hasOwnProperty(c)){if(!b.hasOwnProperty(c))return!1;if(a[c]!==b[c]){if("object"!=typeof a[c])return!1;if(!Object.equals(a[c],b[c]))return!1}}for(c in b)if(b.hasOwnProperty(c)&&!a.hasOwnProperty(c))return!1;return!0};
console.log(
`"[1,2,3] == [1,2,3]" ?`,Object.equals([1,2,3],[1,2,3])
);
console.log(
`"[{"apple":25},{"bannana":36},{"orange":6}] == [{"apple":25},{"bannana":36},{"orange":6}]" ?`,Object.equals([{"apple":25},{"bannana":36},{"orange":6}], [{"apple":25},{"bannana":36},{"orange":6}])
);

Infer correct data type from query string parameters

I'd like to infer the types from parameters of a query string (using Javascript).
Example URL:
http://example.com/some/path/here/?someString=Hey&someNumber=20
I've used a couple of packages (query-string and simple-query-string) like so:
queryString.parse(request.url)
Where request.url is the url above. This gives the following:
{
"someString":"Hey",
"someNumber":"20"
}
Where someNumber above is type string. The desired result is:
{
"someString":"Hey",
"someNumber":20
}
Where someNumber is type integer.
The reason I want integer type is because I am performing JSONSchema validation on the parameters which specify the types which are allowed per named parameter.
I am aware that I can cast these instances, but I'd prefer if it was available from a package since it feels a lot like reinventing the wheel.
This should do the trick. You could potentially add more/different regex tests to supplement the simple number check that's in place now (for instance, detect dates, dollar amounts, telephone numbers etc.)
var exampleurl = "http://example.com/some/path/here/?someString=Hey&someNumber=20"
function getQueryStringData(url){
var data = url.split('?')[1].split('&'),
numberRgx = /^\d+(\.\d+)?$/
return data.reduce(function(obj, t){
var pair = t.split('='),
key = pair[0],
val = pair[1]
obj[key] = numberRgx.test(val) ? parseFloat(val) : val
return obj
}, {})
}
var obj = getQueryStringData(exampleurl)
console.log(obj)
document.getElementById('output').textContent = JSON.stringify(obj)
<div id="output"></div>
Here is one method of typecasting an object's properties. if the string itself is one of the special matches in the expression, JSON.parse it, else try Number, else leave it as-is.
function cast (o) {
var result = {};
for(var prop in o) { if(o.hasOwnProperty(prop)) {
var val = o[prop];
if (typeof val === 'object') return o[prop] = cast(val);
result[prop] = /^(true|false|null|undefined)$/.test(val) ? JSON.parse(val) : Number(val) || val;
}}
return result;
}
cast( { a: '123', b: 'null', c: 'abc' } ) returns {a: 123, b: null, c: "abc"}

Comparing 2 JSON objects structure in JavaScript

I am using angular-translate for a big application. Having several people committing code + translations, many times the translation objects are not in sync.
I am building a Grunt plugin to look at both files' structure and compare it (just the keys and overall structure, not values).
The main goals are:
Look into each file, and check if the structure of the whole object
(or file, in this case) is the exact same as the translated ones;
On error, return the key that doesn't match.
It turns out it was a bit more complicated than I anticipated. So i figured I could do something like:
Sort the object;
Check the type of data the value contains (since they are translations, it will only have strings, or objects for the nestings) and store it in another object, making the key equal to the original key and the value would be a string 'String', or an object in case it's an object. That object contains the children elements;
Recursively repeat steps 1-2 until the whole object is mapped and sorted;
Do the same for all the files
Stringify and compare everything.
A tiny example would be the following object:
{
key1: 'cool',
key2: 'cooler',
keyWhatever: {
anotherObject: {
key1: 'better',
keyX: 'awesome'
},
aObject: 'actually, it\'s a string'
},
aKey: 'more awesomeness'
}
would map to:
{
aKey: 'String',
key1: 'String',
key2: 'String',
keyWhatever: {
aObject: 'String',
anotherObject: {
key1: 'String',
keyX: 'String'
}
}
}
After this, I would stringify all the objects and proceed with a strict comparison.
My question is, is there a better way to perform this? Both in terms of simplicity and performance, since there are many translation files and they are fairly big.
I tried to look for libraries that would already do this, but I couldn't find any.
Thank you
EDIT: Thank you Jared for pointing out objects can't be sorted. I am ashamed for saying something like that :D Another solution could be iterating each of the properties on the main translation file, and in case they are strings, compare the key with the other files. In case they are objects, "enter" them, and do the same. Maybe it is even simpler than my first guess. What should be done?
Lets say you have two JSON objects, jsonA and jsonB.
function compareValues(a, b) {
//if a and b aren't the same type, they can't be equal
if (typeof a !== typeof b) {
return false;
}
// Need the truthy guard because
// typeof null === 'object'
if (a && typeof a === 'object') {
var keysA = Object.keys(a).sort(),
keysB = Object.keys(b).sort();
//if a and b are objects with different no of keys, unequal
if (keysA.length !== keysB.length) {
return false;
}
//if keys aren't all the same, unequal
if (!keysA.every(function(k, i) { return k === keysB[i];})) {
return false;
}
//recurse on the values for each key
return keysA.every(function(key) {
//if we made it here, they have identical keys
return compareValues(a[key], b[key]);
});
//for primitives just use a straight up check
} else {
return a === b;
}
}
//true if their structure, values, and keys are identical
var passed = compareValues(jsonA, jsonB);
Note that this can overflow the stack for deeply nested JSON objects. Note also that this will work for JSON but not necessarily regular JS objects as special handling is needed for Date Objects, Regexes, etc.
Actually you do need to sort the keys, as they are not required to be spit out in any particular order. Write a function,
function getComparableForObject(obj) {
var keys = Object.keys(obj);
keys.sort(a, b => a > b ? 1 : -1);
var comparable = keys.map(
key => key + ":" + getValueRepresentation(obj[key])
).join(",");
return "{" + comparable + "}";
}
Where getValueRepresentation is a function that either returns "String" or calls getComparableForObject. If you are worried about circular references, add a Symbol to the outer scope, repr, assign obj[repr] = comparable in the function above, and in getValueRepresentation check every object for a defined obj[repr] and return it instead of processing it recursively.
Sorting an array of the keys from the object works. However, sorting has an average time complexity of O(nâ‹…log(n)). We can do better. A fast general algorithm for ensuring two sets A and B are equivalent is as follows:
for item in B
if item in A
remove item from A
else
sets are not equivalent
sets are equivalent iff A is empty
To address #Katana31, we can detect circular references as we go by maintaining a set of visited objects and ensuring that all descendents of that object are not already in the list:
# poorly written pseudo-code
fn detectCycles(A, found = {})
if A in found
there is a cycle
else
found = clone(found)
add A to found
for child in A
detectCycles(child, found)
Here's a complete implementation (you can find a simplified version that assumes JSON/non-circular input here):
var hasOwn = Object.prototype.hasOwnProperty;
var indexOf = Array.prototype.indexOf;
function isObjectEmpty(obj) {
for (var key in obj) {
return false;
}
return true;
}
function copyKeys(obj) {
var newObj = {};
for (var key in obj) {
newObj[key] = undefined;
}
return newObj;
}
// compares the structure of arbitrary values
function compareObjectStructure(a, b) {
return function innerCompare(a, b, pathA, pathB) {
if (typeof a !== typeof b) {
return false;
}
if (typeof a === 'object') {
// both or neither, but not mismatched
if (Array.isArray(a) !== Array.isArray(b)) {
return false;
}
if (indexOf.call(pathA, a) !== -1 || indexOf.call(pathB, b) !== -1) {
return false;
}
pathA = pathA.slice();
pathA.push(a);
pathB = pathB.slice();
pathB.push(b);
if (Array.isArray(a)) {
// can't compare structure in array if we don't have items in both
if (!a.length || !b.length) {
return true;
}
for (var i = 1; i < a.length; i++) {
if (!innerCompare(a[0], a[i], pathA, pathA)) {
return false;
}
}
for (var i = 0; i < b.length; i++) {
if (!innerCompare(a[0], b[i], pathA, pathB)) {
return false;
}
}
return true;
}
var map = copyKeys(a), keys = Object.keys(b);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!hasOwn.call(map, key) || !innerCompare(a[key], b[key], pathA,
pathB)) {
return false;
}
delete map[key];
}
// we should've found all the keys in the map
return isObjectEmpty(map);
}
return true;
}(a, b, [], []);
}
Note that this implementation directly compares two objects for structural equivalency, but doesn't reduce the objects to a directly comparable value (like a string). I haven't done any performance testing, but I suspect that it won't add significant value, though it will remove the need to repeatedly ensure objects are non-cyclic. For that reason, you could easily split compareObjectStructure into two functions - one to compare the structure and one to check for cycles.

Categories