How can I check the array has empty element or not?
Imagine this array,
var arr = [ 'a', 'b', , 'd'];
the arr[2] is undefined. I want to check this. If the element has empty element, return 'true' or return false. Maybe like this,
function hasEmptyElement(array){
for (var i=0; i<array.length; i++){
if (typeof arr[i] == 'undefined'){
return true;
// and then ?
// should I use double for loop or helper variable?
}
}
}
I confuse how can I do this. Please help me the clevers.
As of ES2016, you should use Array.prototype.includes:
const array = ["a", "b", , "d"];
array.includes(undefined); // true
(You don't need to write undefined, but this makes it more clear what's happening.)
Note that this method treats slots valued undefined and empty slots the same, although they're not. If you need to differentiate these two cases as well, starting from ES2017, you can use Object.values making the following expression true if there are empty slots in the array:
Object.values(array).length !== array.length; // true
First, note the difference between empty slots and slots with undefined value:
var arr = [/*empty slot*/, undefined];
Object.keys(arr); // ["1"] but not "0"
"0" in arr; // false
"1" in arr; // true
ES5 array methods skip empty slots. ES6 [].includes does not.
That means you can use
arr.includes(undefined); // has empty slot OR contains undefined value
arr.indexOf(undefined) > -1; // contains undefined value
If you want to test only if there are empty slots, you can iterate manually with a for loop and check whether all indices between 0 and the length of the array are present, e.g. with in operator.
(function() {
for(var i=0; i<arr.length; ++i) if(!(i in arr)) return true;
return false;
})(); // has empty slot
Or you can also use ES5 array methods and check if they skipped an index.
var n = 0;
arr.some((_,i) => i !== n++); // has empty slot
You can do something like that:
function hasEmptyElement(array){
for (var i=0; i<array.length; i++){
if (!(i in array)) {
return true;
}
}
return false;
}
The main point of this implementation is that it makes difference between [1,,3] and [1, undefined, 3]
try
var hasAnyEmptyElement = arr.filter(function(val){ return (typeof val) != "undefined" }).length != arr.length;
DEMO
var arr = [1,2];
arr[4] = 2;
var hasAnyEmptyElement = arr.filter(function(val){ return (typeof val) != "undefined" }).length != arr.length;
alert(hasAnyEmptyElement);
You could also use Array.prototype.findIndex()
var arr = ['a', 'b', , 'd'];
document.write(arr.findIndex(e => e === undefined) > -1);
var temp = arr.filter(item => item);
This will give you a new array with below elements:
["a", "b", "d"]
The above solution will help to remove empty as well as null values.
Simple implementation using set
var arr = [ 'a', 'b', , 'd'];
// Using Set from es6
var arraySet = new Set(arr)
arraySet.has(undefined) // returns true
Thanks!
For ES5- you can do
var arr = [ 'a', 'b', , 'd'];
arr.filter(function() { return true }).length === arr.length
That is going to return false if there is a undefined
ES2015 check includes
You can try like this:
var arr = [ 'a', 'b',, 'd'];
function myfunc(arr) {
for(var i=0; i<arr.length; i++) {
if (!(i in arr)) return false;
}
return true;
}
alert( myfunc(arr));
`Check null/undefined values in Array`
function checkNullValue(a) {
if (typeof (a) == 'undefined' || a === null) {
return false;
} else {
return true;
}
}
var arrayMonthVal = ['1','2',,'6','null', '8'];
var pass = arrayMonthVal.some(checkNullValue);
I recently needed to know if a given array element was empty and this page helped me derive the following solution:
/** Determine if an array element is empty.
* #param {*[]} arr
* #param {number} [i] If not provided, check entire arr for empty.
* #return {boolean}
*/
const hasEmpty = (arr, i) => 0<=i
? !arr.some((_, j) => j==i)
: Object.values(arr).length!==arr.length;
Making it simple you can use the below comparison for achieving the same.
function hasEmptyElement(array){
for(var i=0;i<array.length;i++){
if(my_arr[i] === "")
return false;
}
return true;
}
hello try this....
if (typeof arr[i] == 'NULL'){
return true;
}
hope this may work
Related
Other articles talk about removing strings from an array based on a search term.
But I'm trying to indentify which elements are strings and which elements are numbers in an array, and then remove all strings to return a new array.
function filter_list(l) {
let newArray = [];
for (let i = 0; i < l.length; i ++) {
if (i !== "^[a-zA-Z0-9_.-]*$") {
newArray = newArray + i;
}
}
return newArray;
}
This is returning 0123.
Why is it not returning an array?
Why is if (i !== "^[a-zA-Z0-9_.-]*$") not working? How else can I check for when an element is a string (something in quotes) within the array?
https://www.codewars.com/kata/list-filtering/train/javascript
Thanks
You can is typeof keyword. and filter(). I have tested the code its passing all tests in codewars.
Using ES6 Arrow Function
function filter_list(l) {
return l.filter(x => typeof x === "number");
}
console.log(filter_list([1,2,'a','b']))
Without Arrow Function
function filter_list(l) {
return l.filter(function(x){
return typeof x === "number"
});
}
console.log(filter_list([1,2,'a','b']))
Using Simple Loops
function filter_list(l) {
let newArr = [];
for(let i = 0;i<l.length;i++){
if(typeof l[i] === "number") newArr.push(l[i]);
}
return newArr
}
console.log(filter_list([1,2,'a','b']))
Regex is not good way to parse such table. Try isNaN
console.log(
[1,2,3,4,5, 'a', 'b', 1, 3].filter(item => !isNaN(item) ? item : '')
)
If you want less hacky way try
function filter_list(l) {
// l is very bad name, because look similar to i
let newArray = [];
for (let i = 0; i < l.length; i ++) {
!isNaN(l[i]) ? newArray.push(l[i]) : ''
}
return newArray;
}
or even
for (let i = 0; i < l.length; i ++) {
!isNaN(l[i]) ? newArray[i] = l[i] : ''
}
Hovewer, this task can be done with regexes, but I cannot recommend this solution.
[1,2,3,4,5, 'a', 'b', 1, 3].join(' ').replace(/\D/gm, '').split('')
var numberArray: any[];
numberArray.filter(Number)
Using this you can filter only numbers in an array and then can performe what you want.
function filter_list(l) {
return l.filter(x => typeof x === "number");
}
console.log(filter_list([1,2,'a','b']))
I worked out a simple answer that will work as well using the same logic required to solve your problem. I used it on an example where you have an array of temperature values, and you want to remove all the values which are strings from the existing array, then populate the new empty array.You can use typeof operator to identify the type of value in the temperatures array at position i which is the index of that array element. If the type of that value is not a string then push the value of the temperatures array at the current index position to the new array.
const temperatures = [3, -2, -6, -1, 'error', 9, 13, 17, 15, 14, 9, 5];
const cleanTemperatures = [];
for (let i = 0; i < temperatures.length; i++) {
if (typeof temperatures[i] !== 'string') {
cleanTemperatures.push(temperatures[i]);
}
}
I am trying to check if an element exists in any one of three arrays. I don't know how to return the name of the array where the element was found. Can anyone direct me into the right direction please.
I have coded a function which takes the element in search as its argument and then returns the array name:
var arr1 = ['a','b','c','d'];
var arr2 = ['e','f','g','h'];
var arr3 = ['i','j','k','l'];
function chkElem(elem)
{
var id = elem;
var isFound = null;
if(arr1.indexOf(id) || (arr2.indexOf(id) || (arr3.indexOf(id))))
{
isFound = ????
}
return isFound;
}
I am uncertain how to assign the parent array name to 'isFound' variable.
Thanks.
You should never use "variable names" in your function logic. Instead, make the arrays properties of an object and return the property name:
var arrays = {
"arr1": ['a','b','c','d'],
"arr2": ['e','f','g','h'],
"arr3": ['i','j','k','l']
};
for (var name in arrays)
if (arrays[name].indexOf(id) > -1)
return name;
return null;
Or, even better, use an array of arrays to search in and return the index:
var arrays = [
['a','b','c','d'],
['e','f','g','h'],
['i','j','k','l']
];
for (var i=0; i<arrays.length; i++)
if (arrays[i].indexOf(id) > -1)
return i;
return -1;
Test one-by-one:
if (arr1.indexOf(id) > -1) {
isFound = arr1;
} else if (arr2.indexOf(id) > -1) {
isFound = arr2;
} else if (arr3.indexOf(id) > -1) {
isFound = arr3;
}
Alternatively, create a multi-dimensional array:
var arr = [
['a','b','c','d'],
['e','f','g','h'],
['i','j','k','l']
];
var isFound = null;
for (var i = 0; i < arr.length; i++) {
if (arr[i].indexOf(elem) > -1) {
isFound = arr[i];
break;
}
}
Firstly, be careful of the indexOf() trap - if it fails to find the requested string, it will return -1 - which is a truthy - so you need to check explicitly like so:
if (arr1.indexOf(id) != -1)
not
if (arr1.indexOf(id))
The truthy/falsy concept also means that, if your string is the first element in the array, and so indexOf() returns false, that is a falsy, and so your condition will actually fail even though a match was made!
Secondly, you cannot return the name of the array - or, to be more precise, the name of the variable that references it in the JS memory. You can either:
1) return the array itself
if (arr1.indexOf(id) != -1) return arr1;
2) store your arrays in a central object and return the name of the property that you found it in
var arrs = {
'one': ['foo', 'bar']
/* two, three etc */
};
for(var i in arrs)
if (arrs[i].indexOf('foo') != -1)
return i;
When you have a group of things, store them in an array (if they are ordered) or an object (if they are named). If you spot a bunch of variables with almost identical names, you're probably doing something wrong.
var things = [
['a','b','c','d'],
['e','f','g','h'],
['i','j','k','l']
];
Then you can loop over them:
for (var i = 0; i < things.length; i++) {
var thing = things[i];
if (thing.indexOf(id) > -1) { // indexOf returns -1 if not found, and the index (starting from 0) if it is.
return i;
}
}
return null;
var arr1 = ['a', 'b', 'c', 'd'];
var arr2 = ['e', 'f', 'g', 'h'];
var arr3 = ['i', 'j', 'k', 'l'];
function chkElem(elem) {
var isFound;
(isFound = arr1).indexOf(elem) > -1 || (isFound = arr2).indexOf(elem) > -1 || (isFound = arr3).indexOf(elem) > -1;
return isFound;
}
alert(chkElem('f'));
This question already has answers here:
Get all unique values in a JavaScript array (remove duplicates)
(91 answers)
Closed 1 year ago.
How can I get a list of unique values in an array? Do I always have to use a second array or is there something similar to java's hashmap in JavaScript?
I am going to be using JavaScript and jQuery only. No additional libraries can be used.
Here's a much cleaner solution for ES6 that I see isn't included here. It uses the Set and the spread operator: ...
var a = [1, 1, 2];
[... new Set(a)]
Which returns [1, 2]
Or for those looking for a one-liner (simple and functional) compatible with current browsers:
let a = ["1", "1", "2", "3", "3", "1"];
let unique = a.filter((item, i, ar) => ar.indexOf(item) === i);
console.log(unique);
Update 2021
I would recommend checking out Charles Clayton's answer, as of recent changes to JS there are even more concise ways to do this.
Update 18-04-2017
It appears as though 'Array.prototype.includes' now has widespread support in the latest versions of the mainline browsers (compatibility)
Update 29-07-2015:
There are plans in the works for browsers to support a standardized 'Array.prototype.includes' method, which although does not directly answer this question; is often related.
Usage:
["1", "1", "2", "3", "3", "1"].includes("2"); // true
Pollyfill (browser support, source from mozilla):
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function(searchElement, fromIndex) {
// 1. Let O be ? ToObject(this value).
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If len is 0, return false.
if (len === 0) {
return false;
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
var n = fromIndex | 0;
// 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(searchElement, elementK) is true, return true.
// c. Increase k by 1.
// NOTE: === provides the correct "SameValueZero" comparison needed here.
if (o[k] === searchElement) {
return true;
}
k++;
}
// 8. Return false
return false;
}
});
}
Since I went on about it in the comments for #Rocket's answer, I may as well provide an example that uses no libraries. This requires two new prototype functions, contains and unique
Array.prototype.contains = function(v) {
for (var i = 0; i < this.length; i++) {
if (this[i] === v) return true;
}
return false;
};
Array.prototype.unique = function() {
var arr = [];
for (var i = 0; i < this.length; i++) {
if (!arr.contains(this[i])) {
arr.push(this[i]);
}
}
return arr;
}
var duplicates = [1, 3, 4, 2, 1, 2, 3, 8];
var uniques = duplicates.unique(); // result = [1,3,4,2,8]
console.log(uniques);
For more reliability, you can replace contains with MDN's indexOf shim and check if each element's indexOf is equal to -1: documentation
One Liner, Pure JavaScript
With ES6 syntax
list = list.filter((x, i, a) => a.indexOf(x) === i)
x --> item in array
i --> index of item
a --> array reference, (in this case "list")
With ES5 syntax
list = list.filter(function (x, i, a) {
return a.indexOf(x) === i;
});
Browser Compatibility: IE9+
Using EcmaScript 2016 you can simply do it like this.
var arr = ["a", "a", "b"];
var uniqueArray = Array.from(new Set(arr)); // Unique Array ['a', 'b'];
Sets are always unique, and using Array.from() you can convert a Set to an array. For reference have a look at the documentations.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
These days, you can use ES6's Set data type to convert your array to a unique Set. Then, if you need to use array methods, you can turn it back into an Array:
var arr = ["a", "a", "b"];
var uniqueSet = new Set(arr); // {"a", "b"}
var uniqueArr = Array.from(uniqueSet); // ["a", "b"]
//Then continue to use array methods:
uniqueArr.join(", "); // "a, b"
If you want to leave the original array intact,
you need a second array to contain the uniqe elements of the first-
Most browsers have Array.prototype.filter:
const unique = array1.filter((item, index, array) => array.indexOf(item) === index);
//if you need a 'shim':
Array.prototype.filter= Array.prototype.filter || function(fun, scope){
var T= this, A= [], i= 0, itm, L= T.length;
if(typeof fun== 'function'){
while(i<L){
if(i in T){
itm= T[i];
if(fun.call(scope, itm, i, T)) A[A.length]= itm;
}
++i;
}
}
return A;
}
Array.prototype.indexOf= Array.prototype.indexOf || function(what, i){
if(!i || typeof i!= 'number') i= 0;
var L= this.length;
while(i<L){
if(this[i]=== what) return i;
++i;
}
return -1;
}
Fast, compact, no nested loops, works with any object not just strings and numbers, takes a predicate, and only 5 lines of code!!
function findUnique(arr, predicate) {
var found = {};
arr.forEach(d => {
found[predicate(d)] = d;
});
return Object.keys(found).map(key => found[key]);
}
Example: To find unique items by type:
var things = [
{ name: 'charm', type: 'quark'},
{ name: 'strange', type: 'quark'},
{ name: 'proton', type: 'boson'},
];
var result = findUnique(things, d => d.type);
// [
// { name: 'charm', type: 'quark'},
// { name: 'proton', type: 'boson'}
// ]
If you want it to find the first unique item instead of the last add a found.hasOwnPropery() check in there.
Not native in Javascript, but plenty of libraries have this method.
Underscore.js's _.uniq(array) (link) works quite well (source).
If you don't need to worry so much about older browsers, this is exactly what Sets are designed for.
The Set object lets you store unique values of any type, whether
primitive values or object references.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
const set1 = new Set([1, 2, 3, 4, 5, 1]);
// returns Set(5) {1, 2, 3, 4, 5}
Using jQuery, here's an Array unique function I made:
Array.prototype.unique = function () {
var arr = this;
return $.grep(arr, function (v, i) {
return $.inArray(v, arr) === i;
});
}
console.log([1,2,3,1,2,3].unique()); // [1,2,3]
Short and sweet solution using second array;
var axes2=[1,4,5,2,3,1,2,3,4,5,1,3,4];
var distinct_axes2=[];
for(var i=0;i<axes2.length;i++)
{
var str=axes2[i];
if(distinct_axes2.indexOf(str)==-1)
{
distinct_axes2.push(str);
}
}
console.log("distinct_axes2 : "+distinct_axes2); // distinct_axes2 : 1,4,5,2,3
Majority of the solutions above have a high run time complexity.
Here is the solution that uses reduce and can do the job in O(n) time.
Array.prototype.unique = Array.prototype.unique || function() {
var arr = [];
this.reduce(function (hash, num) {
if(typeof hash[num] === 'undefined') {
hash[num] = 1;
arr.push(num);
}
return hash;
}, {});
return arr;
}
var myArr = [3,1,2,3,3,3];
console.log(myArr.unique()); //[3,1,2];
Note:
This solution is not dependent on reduce. The idea is to create an object map and push unique ones into the array.
You only need vanilla JS to find uniques with Array.some and Array.reduce. With ES2015 syntax it's only 62 characters.
a.reduce((c, v) => b.some(w => w === v) ? c : c.concat(v)), b)
Array.some and Array.reduce are supported in IE9+ and other browsers. Just change the fat arrow functions for regular functions to support in browsers that don't support ES2015 syntax.
var a = [1,2,3];
var b = [4,5,6];
// .reduce can return a subset or superset
var uniques = a.reduce(function(c, v){
// .some stops on the first time the function returns true
return (b.some(function(w){ return w === v; }) ?
// if there's a match, return the array "c"
c :
// if there's no match, then add to the end and return the entire array
c.concat(v)}),
// the second param in .reduce is the starting variable. This is will be "c" the first time it runs.
b);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
Another thought of this question. Here is what I did to achieve this with fewer code.
var distinctMap = {};
var testArray = ['John', 'John', 'Jason', 'Jason'];
for (var i = 0; i < testArray.length; i++) {
var value = testArray[i];
distinctMap[value] = '';
};
var unique_values = Object.keys(distinctMap);
console.log(unique_values);
Array.prototype.unique = function () {
var dictionary = {};
var uniqueValues = [];
for (var i = 0; i < this.length; i++) {
if (dictionary[this[i]] == undefined){
dictionary[this[i]] = i;
uniqueValues.push(this[i]);
}
}
return uniqueValues;
}
I have tried this problem in pure JS.
I have followed following steps 1. Sort the given array, 2. loop through the sorted array, 3. Verify previous value and next value with current value
// JS
var inpArr = [1, 5, 5, 4, 3, 3, 2, 2, 2,2, 100, 100, -1];
//sort the given array
inpArr.sort(function(a, b){
return a-b;
});
var finalArr = [];
//loop through the inpArr
for(var i=0; i<inpArr.length; i++){
//check previous and next value
if(inpArr[i-1]!=inpArr[i] && inpArr[i] != inpArr[i+1]){
finalArr.push(inpArr[i]);
}
}
console.log(finalArr);
Demo
You can enter array with duplicates and below method will return array with unique elements.
function getUniqueArray(array){
var uniqueArray = [];
if (array.length > 0) {
uniqueArray[0] = array[0];
}
for(var i = 0; i < array.length; i++){
var isExist = false;
for(var j = 0; j < uniqueArray.length; j++){
if(array[i] == uniqueArray[j]){
isExist = true;
break;
}
else{
isExist = false;
}
}
if(isExist == false){
uniqueArray[uniqueArray.length] = array[i];
}
}
return uniqueArray;
}
Here is an approach with customizable equals function which can be used for primitives as well as for custom objects:
Array.prototype.pushUnique = function(element, equalsPredicate = (l, r) => l == r) {
let res = !this.find(item => equalsPredicate(item, element))
if(res){
this.push(element)
}
return res
}
usage:
//with custom equals for objects
myArrayWithObjects.pushUnique(myObject, (left, right) => left.id == right.id)
//with default equals for primitives
myArrayWithPrimitives.pushUnique(somePrimitive)
I was just thinking if we can use linear search to eliminate the duplicates:
JavaScript:
function getUniqueRadios() {
var x=document.getElementById("QnA");
var ansArray = new Array();
var prev;
for (var i=0;i<x.length;i++)
{
// Check for unique radio button group
if (x.elements[i].type == "radio")
{
// For the first element prev will be null, hence push it into array and set the prev var.
if (prev == null)
{
prev = x.elements[i].name;
ansArray.push(x.elements[i].name);
} else {
// We will only push the next radio element if its not identical to previous.
if (prev != x.elements[i].name)
{
prev = x.elements[i].name;
ansArray.push(x.elements[i].name);
}
}
}
}
alert(ansArray);
}
HTML:
<body>
<form name="QnA" action="" method='post' ">
<input type="radio" name="g1" value="ANSTYPE1"> good </input>
<input type="radio" name="g1" value="ANSTYPE2"> avg </input>
<input type="radio" name="g2" value="ANSTYPE3"> Type1 </input>
<input type="radio" name="g2" value="ANSTYPE2"> Type2 </input>
<input type="submit" value='SUBMIT' onClick="javascript:getUniqueRadios()"></input>
</form>
</body>
I need to test whether each item in an array is identical to each other. For example:
var list = ["l","r","b"]
Should evaluate as false, because each item is not identical. On the other hand this:
var list = ["b", "b", "b"]
Should evaluate as true because they are all identical. What would be the most efficient (in speed/resources) way of achieving this?
In ES5, you could do:
arr.every(function(v, i, a) {
// first item: nothing to compare with (and, single element arrays should return true)
// otherwise: compare current value to previous value
return i === 0 || v === a[i - 1];
});
.every does short-circuit as well.
function identical(array) {
for(var i = 0; i < array.length - 1; i++) {
if(array[i] !== array[i+1]) {
return false;
}
}
return true;
}
You could always do a new Set, and check the length.
var set1 = [...new Set(list)].length === 1;
The one line answer is:
arr.every((val, ind, arr) => val === arr[0]);
You can look into Array.every for more details.
Note:
Array.every is available ES5 onwards.
This method returns true for any condition put on an empty array.
Syntax: arr.every(callback[, thisArg]) or array.every(function(currentValue, index, arr), thisValue)
It does not change the original array
The execution of every() is short-circuited. As soon as every() finds an array element that doesn't match the predicate, it immediately returns false and doesn't iterate over the remaining elements
arr.every(i=>i==arr[0]) //will return true if all items in arr are identical
function matchList(list) {
var listItem = list[0];
for (index in list) {
if(list[index] != listItem {
return false;
}
}
return true;
}
var list = ["b", "b", "b"];
var checkItem = list[0];
var isSame = true;
for (var i = 0; i < list.length; i++) {
if (list[i] != checkItem) {
isSame = false;
break;
}
}
return isSame;
function identical(array) {
// a variable holding standard value
//against this standard value we are examining the array
var standard = array[1];
for (var i = 0; i < array.length; i++) {
if (array[i] !== standard) {
return false;
}
}
return true;
}
identical([1, 1, 1, 1, 1]); //return true
identical(['a', 'a', 'a']); //return true
identical(['a', 'a', 'b'])
function identical(array) {
// a variable holding standard value
//against this standard value we are examining the array
var standard = array[1];
for (var i = 0; i < array.length; i++) {
if (array[i] !== standard) {
return false;
}
}
return true;
}
identical([1, 1, 1, 1, 1]); //return true
identical(['a', 'a', 'a']); //return true
identical(['a', 'a', 'b'])
My suggestion would be to remove duplicates (check out Easiest way to find duplicate values in a JavaScript array), and then check to see if the length == 1. That would mean that all items were the same.
function allEqual(list)
{
if(list.length == 0 || list.length == 1)
{
return true;
}
for (index in list) {
if(list[index] != list[index+1] {
return false;
}
}
return true;
}
In javascript, lets say I want to access a property deep in an object, for example:
entry.mediaGroup[0].contents[0].url
At any point along that structure, a property may be undefined (so mediaGroup may not be set).
What is a simple way to say:
if( entry.mediaGroup[0].contents[0].url ){
console.log( entry.mediaGroup[0].contents[0].url )
}
without generating an error? This way will generate an undefined error if any point along the way is undefined.
My solution
if(entry) && (entry.mediaGroup) && (entry.MediaGroup[0]) ...snip...){
console.log(entry.mediaGroup[0].contents[0].url)
}
which is pretty lengthy. I am guessing there must be something more elegant.
This is a very lazy way to do it, but it meets the criteria for many similar situations:
try {
console.log(entry.mediaGroup[0].contents[0].url);
} catch (e) {}
This should not be done on long code blocks where other errors may potentially be ignored, but should be suitable for a simple situation like this.
/*decend through an object tree to a specified node, and return it.
If node is unreachable, return undefined. This should also work with arrays in the tree.
Examples:
var test1 = {a:{b:{c:{d:1}}}};
console.log(objectDesend(test1, 'a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}}; //will fail to reach d
console.log(objectDesend(test2, 'a', 'b', 'c', 'd'));
*/
var objectDescend = function(){
var obj = arguments[0];
var keys = arguments;
var cur = obj;
for(var i=1; i<keys.length; i++){
var key = keys[i];
var cur = cur[key];
if(typeof(cur)=='undefined')
return cur;
}
return cur;
}
var test1 = {a:{b:{c:{d:1}}}};
console.log(objectDescend(test1, 'a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}};
console.log(objectDescend(test2, 'a', 'b', 'c', 'd'));
So this will return either the value you are looking for, or undefined since that value doesn't exist. It won't return false, as that may actually be the value you are looking for (d:false).
In my code base, I add Object.prototype.descend, so I can do test1.descend('a', 'b', 'c', 'd'). This will only work in ECMAScript 5 (IE>=9) since you need to make it so your function doesn't appear in enumerations. For more info:
Add a method to Object primative, but not have it come up as a property
Here is my code for that:
Object.defineProperty(Object.prototype, 'descend', {
value: function(){
var keys = arguments;
var cur = this;
for(var i=0; i<keys.length; i++){
var key = keys[i];
var cur = cur[key];
if(typeof(cur)=='undefined')
return cur;
}
return cur;
}
});
var test1 = {a:{b:{c:{d:false}}}};
//this will return false, which is the value of d
console.log(test1.descend('a', 'b', 'c', 'd'));
var test2 = {a:{b:{c:1}}};
//undefined since we can't reach d.
console.log(test2.descend(test2, 'a', 'b', 'c', 'd'));
Your current solution is probably as good as you can get, as mVChr says, try..catch is just lazy here. It's probably far less effient and has nothing to recommend it other than perhaps being easier to type (but not significantly so) and it'll be harder to debug as it silently hides errors.
The real issue is the very long "reference worm" created by attempting such access. An alternative to the original that at least reduces the number of property lookups is:
var o;
if ( (o = entry ) &&
(o = o.mediaGroup) &&
(o = o[0] ) &&
(o = o.contents ) &&
(o = o[0] )) {
alert(o.url);
}
But I expect you won't like that.
If you have many such deep access paths, you might like to create a function to do the access and return the last object on success or some other vaule on failure. For failure, you could also have it return the last non-falsey object on the path.
// Create test object
var entry = {};
entry.mediaGroup = [{
contents: [{url: 'url'}]
}];
// Check that it "works"
// alert(entry.mediaGroup[0].contents[0].url);
// Deep property access function, returns last object
// or false
function deepAccess(obj) {
var path = arguments;
var i = 0, iLen = path.length;
var o = path[i++]; // o is first arg
var p = path[i++]; // p is second arg
// Go along path until o[p] is falsey
while (o[p]) {
o = o[p];
p = path[i++];
}
// Return false if didn't get all the way along
// the path or the last non-falsey value referenced
return (--i == iLen) && o;
}
// Test it
var x = deepAccess(entry, 'mediaGroup','0','contents','0');
alert(x && x.url); // url
var x = deepAccess(entry, 'mediaGroup','1','contents','0');
alert(x && x.url); // false
There are probably 3-4 different questions along this vein, and four times as many answers. None of them really satisfied me, so I made my own, and I'll share it.
This function is called "deepGet".
Example:
deepGet(mySampleData, "foo.bar[2].baz", null);
Here is the full code:
function deepGet (obj, path, defaultValue) {
// Split the path into components
var a = path.split('.');
// If we have just one component left, note that for later.
var last = (a.length) === 1;
// See if the next item is an array with an index
var myregexp = /([a-zA-Z]+)(\[(\d+)\])+/; // matches: item[0]
var match = myregexp.exec(a[0]);
// Get the next item
var next;
if (match !== null) {
next = obj[match[1]];
if (next !== undefined) {
next = next[match[3]];
}
} else {
next = obj[a[0]];
}
if (next === undefined || next === null) {
// If we don't have what we want, return the default value
return defaultValue;
} else {
if (last) {
// If it's the last item in the path, return it
return next;
} else {
// If we have more items in the path to go, recurse
return deepGet (next, a.slice(1).join("."), defaultValue);
}
}
}
Here is a jsFiddle: http://jsfiddle.net/7quzmjh8/2/
I was inspired by these two things:
http://designpepper.com/blog/drips/making-deep-property-access-safe-in-javascript.html
http://jsfiddle.net/wxrzM/1/
Hopefully this is useful to someone out there :)
I use this simple function for playing around with deep object properties:
getProperty = function(path) {
try {
return eval(path);
}
catch (e) {
return undefined;
}
};
Here's an example:
var test = {a:{b:{c:"success!"}}};
alert(getProperty('test.c.c'));
// undefined
alert(getProperty('test.a.b.c'));
// success!
Here's the one i have been using for a while
var obj = { a: { b: [
{ c: {d: 'XYZ'} }
] } };
// working
obj.a.b[0].c.d = null;
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:null
obj.a.b[0].c.d = 'XYZ';
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:XYZ
console.log('value:'+getProperty(obj, 'a.b[0].c.d.k.sds', 'NOT-AVAILABLE')); // value:NOT-AVAILABLE
obj.a.b[0].c = null;
console.log('value:'+getProperty(obj, 'a.b[0].c.d', 'NOT-AVAILABLE')); // value:NOT-AVAILABLE
// will not work
//console.log('v:'+getProperty(obj, 'a.b["0"].c.d'));
Here's the function
function getProperty(obj, str, defaultValue){
var props = str.split('.').map(function(prop){
var arrAccessRegEx = /(.*)\[(.*)\]/g;
if (arrAccessRegEx.test(prop)){
return prop.split(arrAccessRegEx).filter(function(ele){return ele!=''; });
} else {
var retArr = [];
retArr.push(prop);
return retArr
};
});
//console.log(props);
for(var i=0;i<props.length;i++){
var prop = props[i][0];
//console.log('prop:'+prop);
if (obj === null) return defaultValue;
obj = obj[prop];
if (obj === undefined) return defaultValue;
if (props[i].length == 2){
var idx = props[i][1];
if (!(obj instanceof Array)) return defaultValue;
if (idx < obj.length ){
obj = obj[idx];
if (obj === undefined) return defaultValue;
}
}
} // for each item in split
return obj;
}