In my project I often find myself checking if a value is an array.
If the value is not an array I create a single element array from it.
If the value is undefined or null, I create an empty array.
The value itself is usually either an array of objects or a single object or undefined
const array = value ? (Array.isArray(value) ? value: [value]) : [];
Is there a more succinct way of doing this (perhaps with lodash or underscore), before I decide to factor this into a separate util function?
You could do
var eventsArray = events ? [].concat(events) : [];
The .concat() function accepts both arrays and individual arguments, so either way you end up with what you want.
since you are using const in your code I assume you are using ES2015 / ES6. ES1015's
Default function parameters allow formal parameters to be initialized with default values if no value or undefined is passed.
function abc(value = []) {
const array = Array.isArray(value) ? value: [value];
}
If you use ES6, you can do the following, it's cleaner than .concat();
function method(value = []) {
const array = [...value];
}
Here's a solution using lodash's castArray and isNil all wrapped up in a mixin:
_.mixin( {'asArray' : function(value){
return _.isNil(value) ? [] : _.castArray(value);
}});
Usage:
_.asArray(null) -> []
_.asArray(undefined) -> []
_.asArray(0) -> [0]
_.asArray(false) -> [false]
_.asArray([1,2,3]) -> [1,2,3]
_.asArray('wibble') -> ['wibble']
A condensed ES2020 way to ensure a variable is an array:
value = [].concat(value ?? [])
Explanation
As #VoteyDisciple explained, the concat function will concatenate single values or an arrays of values. The nullish coalescing operator (??) will use the value on the right (the second empty array) if the value on the left is null or undefined. So if value is null, it will concatenate an empty array to an empty array, which returns an empty array.
Examples
// Different inputs and outputs
values = [].concat([1,2,3] ?? []) // [1,2,3]
values = [].concat(1 ?? []) // [1]
values = [].concat(null ?? []) // []
// Wrap it in a function if you like
function array(value) {
return [].concat(value ?? [])
}
// Use it like this
values = array(values)
// Or even like this
for (let value of array(values)) {
console.log(value)
}
Even shorter solution using .flat()
There is an even slicker way to do it if you don't have to check for the undefined or null value. This basically converts the value to a singleton array if it was not an array before.
[value].flat()
Example usage
[12].flat() // Output: [12]
[[[12]]].flat() // Output: [[12]]
Removing nullish values
If you want to remove nullish values - just add a filter on top of that. This will remove all the values that are falsy.
[value].filter(x => x).flat()
Pitfalls
Due to the fact that 0 (and false) is a falsy value, if you are storing numbers in an array it is advised to explicitly compare array values with null or undefined.
[value].filter(x => x !== null && x !== undefined).flat()
In order to shorten it a bit we could use Lodash's isNil method.
[value].filter(_.isNil).flat()
Related
I have an object array, and I want to test if one of them has a data-attr so I could make a simple if statement, here is an example of what I'm trying to do
if (array.hasAttribute("data-attr")) {}
I'm tried some() and every()
if (array.some(this.hasAttribute("data-attr"))) {}
but I can't figured out the right syntax, please help
array.some(this.hasAttribute("data-attr"))
Was almost correct. Array#some takes a predicate, so you need to do
array.some(function(el) {
return el.hasAttribute("data-attr");
})
Or shorter using arrow functions:
array.some(el => el.hasAttribute("data-attr"))
Array#every works the same way and takes a predicate, so you can call it the same way:
array.every(function(el) {
return el.hasAttribute("data-attr");
})
//or
array.every(el => el.hasAttribute("data-attr"))
And if you want to use one or the other, you can extract the predicate and re-use it:
var hasDataAttribute = el => el.hasAttribute("data-attr")
array.some(hasDataAttribute)
array.every(hasDataAttribute)
If you have jQuery objects, you have to use some tricks since jQuery doesn't have a method that returns true or false for the existence attributes. You can easily simulate it with obj.attr('some-attribute') !== undefined - this will return true if some-attribute exists and false if it doesn't.
var hasDataAttribute = jQueryEl => jQueryEl.attr('data-attr') !== undefined
For data-* attriutes, you can also use jQuery.data() which is (in this case) a shorthand element.attr('data-something') is the same as element.data('something'):
var hasDataAttribute = jQueryEl => jQueryEl.data('attr') !== undefined
Some background for this coding problem. Our termTopics function needs to count how many times each of those topics was mentioned in the surveys, and then return an array with the number of mentions in the following order: smart city, arts funding, and then transportation.
const termTopics = (interviews) => {
const count = interviews.reduce((acc, cv) => {
return {...acc, [cv]: acc[cv] ? acc[cv]+1 : 1}
}, {})
return [count['smart city'], count['arts funding'], count['transportation']];
}
What I cannot understand is the spread operator, and how that creates a truthy statement for the ternary operator to operate with.
const count = interviews
.reduce((resultObject, interview) => {
// We are creating an object through the reduce function by iterating through the interviews array.
// At each iteration we will modify the result object according to the current array interview item value
return {
// First we copy the object we have so far
...resultObject,
// Then we set the property corresponding to the current item
[interview]: resultObject[interview]
// If it is not the first time we have seen this item, the object property already exists and we update it by adding one to its value
? resultObject[interview] + 1
// Otherwise we create a new property and set it to one
: 1
}
}, {})
The spread syntax and the conditional operator are completely unrelated here. Let's unfold back to forth here:
The entire conditional operator expression is acc[cv] ? acc[cv]+1 : 1, so it will resolve to only acc[cv]+1 or 1 based on whether or not acc[cv] is truthy or not.
The result of the conditional operator is assigned to a property in an object.
The property name is [cv] - a computed property name that will equal the current value of cv.
The property name and value are added to an object.
The rest of the object values are ...acc or the current values of the object that are spread into the object.
In effect, {...acc, [cv]: acc[cv] ? acc[cv]+1 : 1} is a shorter version of the following ES5 code:
var result = {};
//{...acc}
for (var key in acc) {
result[key] = acc[key];
}
//[cv]: acc[cv] ? acc[cv]+1 : 1
if (acc[cv]) {
result[cv] = acc[cv]+1;
} else {
result[cv] = 1;
}
The truthy (or falsy) value is coming from here: acc[cv] and if there's a value you increment it by one, otherwise you set it to one.
acc[cv] is a computed property and will look up the value of cv as a property of acc, eg acc['smart city'].
Is there a more readable way of spreading undefined fields of an object on another object without traversing every element of it?
Following example spreads object A on object B:
let A = { f1:'Foo', f2:'Bar', f3:'Baz' }
let B = { ...A }
// Now B has the value of { f1:'Foo', f2:'Bar', f3:'Baz' }
However in the following example spread operator will not include undefined values:
let A = { f1:'Foo', f2:undefined, f3:'Baz' }
let B = { ...A }
// Now B has the value of { f1:'Foo', f3:'Baz' }
// I would like it to be spread like { f1:'Foo', f2:undefined, f3:'Baz' }
// or { f1:'Foo', f2:null, f3:'Baz' }
Is there a way of projecting fields with undefined value using spread operator? (and obviously WITHOUT traversing every field of the object A and spreading into B if the value of that field is not undefined)
If you're asking if the spread operator will maintain undefined property values 'post spread', they do.
const original = { one: 1, two: 2, three: undefined, four: null };
console.log(original);
const withSpread = {five: 5, ...original };
console.log(withSpread);
console.log(typeof withSpread.three === 'undefined')
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
It turned out to be an invalid assertion of mine. Spread operator indeed spreads fields with undefined value. It was JSON.stringify() removing those fields within one of my sources, which lead me to an invalid assertion.
For Express.js users; you can use app.set('json replacer', (k, v) => v===undefined ? null : v); to let express stringify your json response by replacing undefined values with null
Or likewise, you can use JSON.stringify({...}, (k, v) => v===undefined ? null : v) to let it stringify by replacing undefined values with null
I have an application that can turns a tex file into a JavaScript object, with key-value pairs. The key being the word and the value being the number of times it has appeared in the text file. Let's go through it together:
FormatText.prototype.toDowncase = function() {
return this._data = this._data.toLowerCase();
};
This turns the words to lowercase
FormatText.prototype.deleteWords = function() {
return this._data = this._data.replace(/\W/g, " ");
};
This replaces all non-words with a space
FormatText.prototype.splitWords = function() {
return this._data = this._data.split(/\s+/);
};
This turns the string in an array and splits at each delimiter
FormatText.prototype.filterEntries = function() {
return this._data = this._data.filter(v => !!v);
};
This one above I have no clue what it does.
FormatText.prototype.countWords = function() {
return this._data = this._data.reduce((dict, v) => {dict[v] = v in dict ? dict[v] + 1 : 1; return dict}, {});
}
Could someone explain this one, however I will get it a try:
This one takes the array and passed the method 'reduce' with two arguments. It counts how many times each individual word has appeared and returns an object with the 'key-value' pairs described at the beginning of this question.
v => !!v means take v, and coerce it to a Boolean type by applying NOT twice. So the filter function is basically removing any falsey values (0, null, undefined) from this._data.
countWords is counting the number of times each word occurs in this._data - it is going through the array and adding 1 to the count if the word has been encountered before, or returning 1 if the word has not been encountered before. It returns an object with the words as keys and the counts as values.
As a note, these functions change the type of this._data, from a string, to an array, to an object. That may cause bugs to appear if e.g. you run the same method twice
Why not just return the value, without NOT NOT, like
v => v
because for filtering the value coerces to a boolean value.
From Array#filter:
Description
filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values. Array elements which do not pass the callback test are simply skipped, and are not included in the new array.
In this case the double exclamation mark is useless: the value returned from the callback in filter(callback) is then coerced to a boolean automatically, so no need to do it using double exclamation mark. The following lines are equivalent:
.filter(v => !!v)
.filter(v => v)
.filter(Boolean)
This one above I have no clue what it does.
The javascript operator ! (logical not) performs a type coercion (to boolean) on its argument. So applied twice you somehow convert any type to a boolean value which gives you whether it is falsy or truthy.
This is interesting when you want to apply a condition to different types whose semantic is more or less "no value". For example:
!!('') //false
!!(0) //false
!!null //false
!!undefined //false
Could someone explain this one, however I will get it a try
reduce is method of the array prototype which allows to iterate over a collection while aggregating value.
In your specific example the aggregator is a dictionary which maps a word to a count (number of appearance). So if the word is not present in the dictionary it creates a key for this word with a counter initialized to 1 otherwise it increments the counter (if word already present).
A equivalent could be
const countWords = function (words = [], dictionary = {}) {
if(words.length === 0) {
return dictionary;
}
const word = words.pop(); //remove and read the word at the end of the array
if(word in dictionary) {//if key is present in the dictionary
dictionary[word] += 1; // increment
else {
dictionary[word] = 1; // start a counter for new keyword
}
return countWords(words, dictionary);
}
I have an object that can sometimes return properties (or Immutable.map) as undefined.
(2) [Map, undefined]
I'd like for this parent object to be a boolean of true only when both items are Maps. Currently,
let hasLocation = Lodash.overEvery(obj.mapData.toArray())
and then later
hasLocation(2)
seems to be the best option but only seems to return true when both are undefined and examples of overEvery seem to be very thin on the ground. What am I missing here?
You don't need lodash:
const noUndefineds = hasLocation.every(item => item !== undefined);
If you know that only the undefineds would be falsey, you can shorten to:
const noUndefineds = hasLocation.every(Boolean); // all items are truthy values.