Replace object values lodash - javascript

Lets say I have an object
var users = [
{ Mike: 'true' },
{ Tony: 'True' },
{ Ismael: 'RU' }
];
I have this problem where I want to normalise my object, basically replace "true" or "True" with a boolean true anything else should be false.
By the way I may have the syntax for users wrong, chrome is telling me users.constructor == Object and not Array.
How can I achieve this using lodash?

In Lodash, you can use _.mapValues:
const users = [
{ Mike: 'true' },
{ Tony: 'True' },
{ Ismael: 'RU' },
];
const normalisedUsers = users.map(user =>
_.mapValues(user, val => val.toLowerCase() === 'true')
);
console.log(normalisedUsers);
<script src="https://cdn.jsdelivr.net/lodash/4.16.3/lodash.min.js"></script>

You don't have to use lodash. You can use native Array.prototype.map() function:
const users = [
{ Mike: 'true' },
{ Tony: 'True' },
{ Ismael: 'RU' },
];
const normalisedUsers = users.map(user =>
// Get keys of the object
Object.keys(user)
// Map them to [key, value] pairs
.map(key => [key, user[key].toLowerCase() === 'true'])
// Turn the [key, value] pairs back to an object
.reduce((obj, [key, value]) => (obj[key] = value, obj), {})
);
console.log(normalisedUsers);
Functional programming FTW!

You don't need lodash to achieve this, just use a for loop. Here is an example
var users = [
{ Mike: 'true' },
{ Tony: 'True' },
{ Ismael: 'RU' }
];
for (key in users) {
if (users[key] == 'true' || users[key] == 'True') {
users[key] = true
} else {
users[key] = false
}
}
What this is doing is, if your value is 'true' or 'True' then assign a boolean val of true to your object & else assign false.
Edit
You could also do it the shorthand way, as suggested by 4castle:
users[key] = users[key] == 'true' || users[key] == 'True';
Simply replace the if/else block with this one line.

Related

Search function that iterates through an array and returns the values matched init also in the child object

I'm trying to search an array of objects with objects that are nested in, so for example i have this array:
[
{
website: 'Stackoverflow',
info: {
"extension": "com",
"ssl": true
}
},
{
website: 'Faceoobok',
info: {
"extension": "com",
"ssl": true
}
}
]
So I want to search all fields, and then also search the object inside and return an array with the method filter, also the char cases won't matter, it needs to return the object in the array even for example Stackoverflow is not the same as stackoverflow with the casing methods that come with JS.
Here is what I've tried, and It searches the objects and returns them but It doesn't search the object inside, what I mean is for example it searchs the website, but not the .info:
const searchMachine = (arr, query) => {
let queryFormatted = query.toLowerCase();
return arr.filter((obj) =>
Object.keys(obj).some((key) => {
if (typeof obj[key] === 'string') {
return obj[key]
.toLowerCase()
.includes(queryFormatted);
}
return false;
})
);
You could take a closure over the wanted string and use a recursive approach for objects.
const
searchMachine = (array, query) => {
const
check = (query => object => Object
.values(object)
.some(value =>
typeof value === 'string' && value.toLowerCase().includes(query) ||
value && typeof value === 'object' && check(value)
))(query.toLowerCase());
return array.filter(check);
},
data = [{ website: 'Stackoverflow', info: { extension: 'com', ssl: true } }, { website: 'Faceoobok', info: { extension: 'com', ssl: true } }];
console.log(searchMachine(data, 'stack'));
console.log(searchMachine(data, 'com'));
You can split the task in two step. The first one is to get all string in the object.
function getAllStrings(obj) {
if(typeof obj === 'object'){
return Object.values(obj).flatMap(v => getAllStrings(v));
}else if (typeof obj === 'string'){
return [obj];
}
return [];
}
And the second one is to filter.
const searchMachine = (arr, query) => {
const queryFormatted= query.toLowerCase();
return getAllStrings(arr).filter(s => s.toLowerCase().includes(queryFormatted));
}
You can reuse the Object.keys.some(...) code you used to search in the object, to search in object.info.
First make a function of it that lets us pass in the object:
const findInObject = (obj) =>
Object.keys(obj).some((key) => {
if (typeof obj[key] === 'string') {
return obj[key]
.toLowerCase()
.includes(queryFormatted);
}
return false;
});
Then call it within arr.filter. findInObject(obj) is your original logic, and check for the presence of obj.info and then call findInObject on obj.info
...
return arr.filter((obj) =>
findInObject(obj) || obj.info && findInObject(obj.info)
);
...

iterate over a map and change the position of element

I have an array of object
const test = [{'type':'Material'}, {'type':''}, {'type':'ABC'}]
Here I am using map over here to itertae
export const mapToNA = values => map(test, value => type || 'NA')
mapToNA(test)
This returns the [{'type':'Material'}, {'type':'NA'}, {'type':'ABC'}]
Now I want value which is NA then it should be at the end
so Output would be like:
[{'type':'Material'},{'type':'ABC'},{'type':'NA'}, ]
How can I get this ?
Since you're already using lodash, lets use _.sortBy with a custom function:
// Data
let test = [{'type':'Material'}, {'type':''}, {'type':'ABC'}];
// Map '' to 'NA'
const mapToNA = values => _.map(test, value => { return { type: value.type || 'NA' }; } )
test = mapToNA(test)
// Sort
test = _.sortBy(test, element => (element.type === 'NA'));
// Log
console.log(test)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
[
{
"type": "Material"
},
{
"type": "ABC"
},
{
"type": "NA"
}
]
You can partition the array to items with type, and items out without, and then map the relevant items' type to NA, and use spread to combine the arrays:
const data = [{'type':'Material'}, {'type':''}, {'type':'ABC'}];
// Pertition items to have type and NA
const [itemsWithType, itemsWithEmptyType] = _.partition(data, o => o.type)
// map the itemsWithEmptyType to NA and combine
const result = [...itemsWithType, ...itemsWithEmptyType.map(o => ({ ...o, type: 'NA' }))]
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
You can easily achieve this result using map and a custom sorting algorithm.
const test = [
{ type: "ABC" },
{ type: "Material" },
{ type: "" },
{ type: "ABC" },
{ type: "" },
];
const result = test
.map((s) => (s.type === "" ? { type: "NA" } : s))
.sort((a, b) => {
if (a.type === "NA" && b.type === "NA") return 0;
if (a.type === "NA") return 1;
if (b.type === "NA") return -1;
else return 0;
});
console.log(result);
You could use 2 separate arrays to keep track of objects with and without type. Merge them after the loop. This is readable and faster.
const withType = [],
withoutType = []
for (const o of test) {
if (o.type)
withType.push(o)
else
withoutType.push({ type: 'NA' })
}
console.log( withType.concat(withoutType) )
You could also reduce with 2 separate arrays and flat them:
const group = test.reduce((acc, o) => {
if (o.type)
acc[0].push(o)
else
acc[1].push({ 'type': 'NA' })
return acc
}, [[], []])
console.log( group.flat() )
You can use default sort function from javascript to solve this.
const arr =[{'type':'Material'}, {'type':'NA'}, {'type':'ABC'}];
arr.sort((a, b) => a["type"] === 'NA' ? 1 : -1);
console.log(arr)
Do you want to switch position between last element of array with your target element using Array.prototype.map ?
If so,
const test = [{"type": "Material"}, {"type": "NA"}, {"type":"ABC"}];
const someFunction = values => {
// NAIndex remember which index of values have "NA"
let NAIndex = 0;
// copy original array values.
const copiedValues = values.slice();
return values.map((value,index) => {
if(value["type"] === "NA"){
NAIndex = index;
return copiedValues[values.length-1];
}
else if(index === values.length-1){
return copiedValues[NAIndex];
}
return value;
})
}
const array = someFunction(test);
console.log(array)
/*
(3) [Object, Object, Object]
0: Object
type: "Material"
1: Object
type: "ABC"
2: Object
type: "NA"
*/
only use map method is a bit give you constraints yourself. try use splice or something. using only map has low efficiency
You can map the array and then sort your array based on 'NA'
const test = [{'type':'Material'}, {'type':''}, {'type':'ABC'}]
let result = test
.map(val => ({type: val.type || 'NA'}))
.sort((a,b) => a.type === 'NA' ? 1 : b.type === 'NA' ? -1 : 0);
console.log(result)
We can use the sort function.
test.sort((elem1, elem2) => {
if (elem2.type !== 'NA')
if (elem1.type === 'NA')
return 1
else
return 0
return -1
})
Or you can use the shorter one
test.sort((elem1, elem2) =>
elem2.type !== 'NA' ? elem1.type === 'NA' ? 1 : 0 : -1)
Learn more about the sort method here.

How to use a reduce to filter fields in object

I need to write a reduce function that takes an object and returns a new one but only with string-type fields.
E.g.
Input: { type: 'pilot', isActive: true }
Output: { type: 'pilot' }
Input: { isActive: true }
Output: {}
At the first step you need to verify the type of values of input object, and if they are not 'string' => put to the new Object. Also use "Object.keys" to apply reduce to object.
It can be look like:
const newObj = return Object.keys(obj).reduce(
(acc, rec) => {
if (typeof obj[rec] === 'string') {
return { ...acc, [rec]: obj[rec] }
}
return acc
},
{}
)
You need only put this code to your function.
If not particular about reduce, using Object.entries and Object.fromEntries will simplify.
const obj1 = { type: "pilot", isActive: true };
const obj2 = { isActive: true };
const filter = obj =>
Object.fromEntries(
Object.entries(obj).filter(([, value]) => typeof value === "string")
);
console.log(filter(obj1));
console.log(filter(obj2));

lodash orderby with null and real values not ordering correctly

I have an Angular 2 typescript application that is using lodash for various things.
I have an array of objects that I am ordering using a property in the object...
_.orderBy(this.myArray, ['propertyName'], ['desc']);
This works well however my problem is that sometimes 'propertyName' can have a null value.
These are ordered as the first item in a descending list, the highest real values then follow.
I want to make these null values appear last in the descending ordering.
I understand why the nulls come first.
Does anyone know how to approach this?
The _.orderBy() function's iteratees can use a method instead of a string. Check the value, and if it's null return an empty string.
const myArray = [{ propertyName: 'cats' }, { propertyName: null }, { propertyName: 'dogs' }, { propertyName: 'rats' }, { propertyName: null }];
const result = _.orderBy(myArray, ({ propertyName }) => propertyName || '', ['desc']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
The check can be simple (like the one I've used), which converts all falsy values to an empty string:
propertyName || ''
If you need a stricter check, you can use the ternary operator, and handle just null values:
propertyName === null ? '' : propertyName
Edit: Example with multiple ordering:
const result = _.orderBy(myArray, (item) => [get(item, 'propertyName', 0), get(item, 'propertyName2')], ['desc', 'asc']);
This will order by propertyName then propertyName2.
If propertyName is undefined/null then its default order will be set to 0. (and therefore will be displayed at last because of desc ordering on the propertyName field). In such case, propertyName2 will therefore determine the ordering.
The code I needed looks like this...
_.orderBy(this.myArray, [( o ) => { return o.myProperty || ''}], ['desc']);
Just for future reference to others you can do this to sort ascending with falsey values at the end.
items =>
orderBy(
items,
[
i => !!i.attributeToCheck,
i => {
return i.attributeToCheck ? i.attributeToCheck.toLowerCase() : ''
}
],
['desc', 'asc']
)
mine looks like this. PropName and sort are both variables in my solution
return _.orderBy( myarray, [
( data ) => {
if ( data[propName] === null ) {
data[propName] = "";
}
return data[propName].toLowerCase();
}
], [sort] );
I wanted tolowercase because otherwise the sorting is not correct if different casings
This will put bad values at the bottom, and it differentiates between numbers and strings.
const items = [] // some list
const goodValues = isAscending => ({ value }) => {
if (typeof value !== 'string' && isNaN(value)) {
return isAscending ? Infinity : -Infinity
}
return value || ''
}
const sortedItems = orderBy(
items,
[goodValues(isAscending), 'value'],
[isAscending ? 'asc' : 'desc']
)
This worked for me
orders = [{id : "1", name : "test"}, {id : "1"}];
sortBy = ["id", "name"];
orderby(
orders,
sortBy.map(s => {
return (r: any) => {
return r[s] ? r[s] : "";
};
})),
);
I created a function for this (ts code):
const orderByFix = (array: any[], orderKeys: string[], orderDirs: ('asc' | 'desc')[]) => {
const ordered = orderBy(array, orderKeys, orderDirs);
const withProp = ordered.filter((o) => orderKeys.every(k => o[k]));
const withoutProp = ordered.filter((o) => !orderKeys.every(k => o[k]));
return [...withProp, ...withoutProp];
};
I've extended gwendall's answer to also handle case when "order keys" are functions (_.orderBy allows that)
const orderByFix = (
array: any[],
orderKeys: (string | ((o: any) => any))[],
orderDirs: ('asc' | 'desc')[]
) => {
const ordered = orderBy(array, orderKeys, orderDirs)
const withProp = ordered.filter((o) =>
orderKeys.every((k) => {
if (typeof k === 'string') {
return o[k]
} else if (typeof k === 'function') {
return k(o)
} else {
throw Error(`Order key must be string or function not ${typeof k}`)
}
})
)
const withoutProp = ordered.filter(
(o) =>
!orderKeys.every((k) => {
if (typeof k === 'string') {
return o[k]
} else if (typeof k === 'function') {
return k(o)
} else {
throw Error(`Order key must be string or function not ${typeof k}`)
}
})
)
return [...withProp, ...withoutProp]
}

how to check if all object keys has false values

JS Object:
var saver = {
title: false,
preview: false,
body: false,
bottom: false,
locale: false
};
The question is how to check if all values is false?
I can use $.each() jQuery function and some flag variable, but there may be a better solution?
Updated version. Thanks #BOB for pointing out that you can use values directly:
Object.values(obj).every((v) => v === false)
Also, the question asked for comparison to false and most answers below return true if the object values are falsy (eg. 0, undefined, null, false), not only if they are strictly false.
This is a very simple solution that requires JavaScript 1.8.5.
Object.keys(obj).every((k) => !obj[k])
Examples:
obj = {'a': true, 'b': true}
Object.keys(obj).every((k) => !obj[k]) // returns false
obj = {'a': false, 'b': true}
Object.keys(obj).every((k) => !obj[k]) // returns false
obj = {'a': false, 'b': false}
Object.keys(obj).every((k) => !obj[k]) // returns true
Alternatively you could write
Object.keys(obj).every((k) => obj[k] == false)
Object.keys(obj).every((k) => obj[k] === false) // or this
Object.keys(obj).every((k) => obj[k]) // or this to return true if all values are true
See the Mozilla Developer Network Object.keys()'s reference for further information.
This will do the trick...
var result = true;
for (var i in saver) {
if (saver[i] === true) {
result = false;
break;
}
}
You can iterate objects using a loop, either by index or key (as above).
If you're after tidy code, and not repeating that then simply put it in a function...
Object.prototype.allFalse = function() {
for (var i in this) {
if (this[i] === true) return false;
}
return true;
}
Then you can call it whenever you need, like this...
alert(saver.allFalse());
Here's a working sample...
Object.prototype.allFalse = function() {
for (var i in this) {
if (this[i] === true) return false;
}
return true;
}
var saver = {
title: false,
preview: false,
body: false,
bottom: false,
locale: false
};
console.log("all are false - should alert 'true'");
console.log(saver.allFalse());
saver.body = true;
console.log("one is now true - should alert 'false'");
console.log(saver.allFalse());
In a comment you ask if you can avoid iteration. You can if you use a javascript library supporting a functional approach, like Underscore, Lodash or Sugar.
With Underscore and Lodash you can write something like this:
var result = _.every(_.values(saver), function(v) {return !v;});
With Sugar you can simply write:
var result = Object.all(saver,false);
Use array.some()
It's more clean and understandable! And it can save us running time, because once the function condition exist once, it goes out of the loop and returns true.
Object.values(obj).some(val => val)
if you actually need strict equality to false write this:
Object.values(obj).some(val => val !== false)
Object.values(obj) make an array with the values of each key.
Short and handy one-liner, fully supported by browsers:
Object.keys(saver).every(k => saver[k] === false);
or
Object.values(saver).every(v => v === false);
(careful tho, Object.values() is not supported by IE yet.)
This should work on all major browsers:
Object.keys(saver).every(key => saver[key] === false); // return true
Do like this,
for (var i in saver) {
if (saver[i]) {
return false; // here if any value is true it wll return as false /
}
}
return true; //here if all value is false it wll return as true
If you want to do it without external iteration (i.e. in your code), try mapping the properties to an array with $.map then using $.inArray to see if any true values exist:
var allFalse = $.inArray(true, $.map(saver, function(obj){return obj})) < 0;
JSFiddle: http://jsfiddle.net/TrueBlueAussie/FLhZL/1/
With lodash you could also do const allFalse = !_.some(saver);
Lodash (3.10.1+) makes this even cleaner to express explicitly:
_.every({a: false, b: false, c: false}, _.negate(Boolean)); // True
But using _.some per ngstschr's answer is more succinct.
✏️ This one-liner checks if there's a falsy value inside any object of an array of objects:
const hasFalsyValue = (list) =>
!list.every(obj => Object.values(obj).every(prop => prop))
I find this one useful to prevent null / falsy values from being passed further on:
const listA = [ { a:'🍎', b:100 }, { a:'🍌', b:200 } ]
const listB = [ { a:null, b:100 }, { a:'🍌', b:200 } ]
// hasFalsyValue(listA) === false
// hasFalsyValue(listB) === true
There's just one detail we should be aware of:
⚠️ In Javascript 0 and '' are Falsy values! ⚠️
So if hasFalsyValue() finds any zero value or any empty string value inside an object in the array, it will consider it a falsy value and thus return true!
...While this might be what you want, sometimes you might want to allow any particular Falsy value to be considered as Truthy.
✍🏽 Say you want to allow zero values in your objects, you can do the following:
const hasFalsyValue_ZeroAllowed =
(list) => !list.every(obj => Object.values(obj).every(prop => prop || prop === 0))
Now zero values won't be considered falsy anymore:
const listC = [ { a:0, b:100 }, { a:'🍌', b:200 } ]
const listD = [ { a: null, b:100 }, { a:'🍌', b:200 } ]
hasFalsyValue_ZeroAllowed(listC) // false
hasFalsyValue_ZeroAllowed(listD) // true
And you can keep on adding further conditions to the function for a tailored validation:
✍🏽 To allow null values:
const hasFalsyValue_NullAllowed =
(list) => !list.every(obj => Object.values(obj).every(prop => prop || prop === null))
const listE = [ { a: null, b:100 }, { a:'🍌', b:200 } ]
hasFalsyValue_NullAllowed(listE) // false
✍🏽 To allow empty string values:
const hasFalsyValue_EmptyStringAllowed =
(list) => !list.every(obj => Object.values(obj).every(prop => prop || prop === ''))
const listF = [ { a: '', b:100 }, { a:'🍌', b:200 } ]
hasFalsyValue_EmptyStringAllowed(listF) // false
As of Lodash 4.0, overEvery can be used
overEvery(saver, false) loops through every element & checks if its false
It returns true if every element is false otherwise returns false

Categories

Resources