How to filter records in a nested array using ramda? - javascript

I've seen this question in several places but still can't figure this out.
Using ramda, how can I filter the following object to return the records that are true for tomatoes?
[
{
"id": "a",
"name": "fred",
"food_prefs": {
"tomatoes": true,
"spinach": true,
"pasta": false
},
"country": "singapore"
},
{
"id": "b",
"name": "alexandra",
"food_prefs": {
"tomatoes": false,
"spinach": true,
"pasta": true
},
"country": "france"
},
{
"id": "c",
"name": "george",
"food_prefs": {
"tomatoes": true,
"spinach": false,
"pasta": false
},
"country": "argentina"
}
]
Storing this array as myData object, I thought that the following should work:
const R = require("ramda")
const lovesTomatoes = R.pipe ( // taken from: https://stackoverflow.com/a/61480617/6105259
R.path (["food_prefs"]),
R.filter (R.prop ("tomatoes"))
)
console.log(lovesTomatoes(myData))
But I end up with the error:
if (typeof obj[methodNames[idx]] === 'function') {
What am I doing wrong?
EDIT
The answers provided by #Ori Drori and #ThanosDi are both great, but I want to emphasize that a pipe-based solution would be ideal because I have follow-up steps I wish to carry on the filtered array. Consider for example the following array. It's similar the one above, but includes more data: year_born and year_record.
[
{
"id": "a",
"name": "fred",
"year_born": 1995,
"year_record": 2010,
"food_prefs": {
"tomatoes": true,
"spinach": true,
"pasta": false
},
"country": "singapore"
},
{
"id": "b",
"name": "alexandra",
"year_born": 2002,
"year_record": 2015,
"food_prefs": {
"tomatoes": false,
"spinach": true,
"pasta": true
},
"country": "france"
},
{
"id": "c",
"name": "george",
"year_born": 1980,
"year_record": 2021,
"food_prefs": {
"tomatoes": true,
"spinach": false,
"pasta": false
},
"country": "argentina"
}
]
So, for example, to answer a full question such as "for those who love tomatoes, what is the average age at the time of the record creation?"
we would need to:
filter the records that love tomates;
extract the elements year_born and year_record
get the difference between values
take the average of the differences
Therefore, using a pipe would be very beneficial.

What went wrong?
You try to get the value of food_prefs out of the array. Since the array doesn't have this key - R.path (["food_prefs"]) is undefined, and then you try to filter this undefined value.
How to solve this problem?
Filter the array, and use R.path to get the tomatoes value.
const { filter, path, identity } = R
const lovesTomatoes = filter(path(['food_prefs', 'tomatoes']))
const data = [{"id":"a","name":"fred","food_prefs":{"tomatoes":true,"spinach":true,"pasta":false},"country":"singapore"},{"id":"b","name":"alexandra","food_prefs":{"tomatoes":false,"spinach":true,"pasta":true},"country":"france"},{"id":"c","name":"george","food_prefs":{"tomatoes":true,"spinach":false,"pasta":false},"country":"argentina"}]
const result = lovesTomatoes(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Filtering using a pipe:
Using R.pipe. I wouldn't go this way for a simple filter by nested properties, but you can use a Schwartzian transform. The idea is to create a new array if pairs [value of tomatoes, original object], filter by the value of tomatoes, and then extract the original object:
const { pipe, map, applySpec, path, identity, filter, last, head } = R
const lovesTomatoes = pipe(
map(applySpec([path(['food_prefs', 'tomatoes']), identity])), // create an array of [value of tomatoes, original object]
filter(head), // filter by the value of the tomatoes
map(last) // extract the original object
)
const data = [{"id":"a","name":"fred","food_prefs":{"tomatoes":true,"spinach":true,"pasta":false},"country":"singapore"},{"id":"b","name":"alexandra","food_prefs":{"tomatoes":false,"spinach":true,"pasta":true},"country":"france"},{"id":"c","name":"george","food_prefs":{"tomatoes":true,"spinach":false,"pasta":false},"country":"argentina"}]
const result = lovesTomatoes(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
How to combine the 1st lovesTomatoes filtering function in a pipe:
However, if you just need the pipe to perform other operations on the filtered array, use the filter as one of the steps:
const { filter, path, identity, pipe, map, prop, uniq } = R
const lovesTomatoes = filter(path(['food_prefs', 'tomatoes']))
const countriesOfTomatoLovers = pipe(
lovesTomatoes,
map(prop('country')),
uniq
)
const data = [{"id":"a","name":"fred","food_prefs":{"tomatoes":true,"spinach":true,"pasta":false},"country":"singapore"},{"id":"b","name":"alexandra","food_prefs":{"tomatoes":false,"spinach":true,"pasta":true},"country":"france"},{"id":"c","name":"george","food_prefs":{"tomatoes":true,"spinach":false,"pasta":false},"country":"argentina"}]
const result = countriesOfTomatoLovers(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

const myData = [
{
"id": "a",
"name": "fred",
"food_prefs": {
"tomatoes": true,
"spinach": true,
"pasta": false
},
"country": "singapore"
},
{
"id": "b",
"name": "alexandra",
"food_prefs": {
"tomatoes": false,
"spinach": true,
"pasta": true
},
"country": "france"
},
{
"id": "c",
"name": "george",
"food_prefs": {
"tomatoes": true,
"spinach": false,
"pasta": false
},
"country": "argentina"
}
];
const lovesTomatoes = filter(pathOr(false, ['food_prefs','tomatoes']));
lovesTomatoes(myData);
Ramda REPL

Ramda comes with a whole suite of predicates built-in already,
one of them that I'd use here is pathEq.
I'd suggest to adopt a map and reduce kind of approach, whereas the match function is separated from the actual aggregation...
Collect your data point
Reduce it to the information you need
const tomatoLovers = R.filter(
R.pathEq(['food_prefs', 'tomatoes'], true),
);
const avgAge = R.pipe(R.pluck('age'), R.mean);
const data = [{
"id": "a",
age: 16,
"name": "fred",
"food_prefs": {
"tomatoes": true,
"spinach": true,
"pasta": false
},
"country": "singapore"
},
{
"id": "b",
age: 66,
"name": "alexandra",
"food_prefs": {
"tomatoes": false,
"spinach": true,
"pasta": true
},
"country": "france"
},
{
"id": "c",
age: 44,
"name": "george",
"food_prefs": {
"tomatoes": true,
"spinach": false,
"pasta": false
},
"country": "argentina"
}
]
console.log(
'Average age of tomato lovers is:',
R.pipe(tomatoLovers, avgAge) (data),
);
console.log(
'They are the tomato lovers',
R.pipe(tomatoLovers, R.pluck('name')) (data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.js" integrity="sha512-ZZcBsXW4OcbCTfDlXbzGCamH1cANkg6EfZAN2ukOl7s5q8skbB+WndmAqFT8fuMzeuHkceqd5UbIDn7fcqJFgg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Related

Sort array from object based on a property and return the sorted initial object

I have the following object
{
"object": "list",
"url": "/v1/prices",
"has_more": false,
"data": [
{
"id": "price_1KHlU72eZvKYlo2CblI51Z8e",
"object": "price",
"active": true,
"billing_scheme": "per_unit",
"created": 1642150211,
"currency": "usd",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"product": "prod_Kxgr3hZDfHnqu1",
"recurring": {
"aggregate_usage": null,
"interval": "month",
"interval_count": 1,
"usage_type": "licensed"
},
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "recurring",
"unit_amount": 2,
"unit_amount_decimal": "2"
},
{...},
{...}
]
}
How can I sort the object.data from it based on the unit_amount property and also keep the initial values of the object (url, has_more, ...)?
I've tried using Ramda, but it's giving me an new object just with the sorted data.
So I need object.data from the initial object to be sorted and to return the entire object after sorting.
Just simple sorting, for example
const obj = {
"object": "list",
"url": "/v1/prices",
"has_more": false,
"data": [
{"unit_amount": 2,},
{"unit_amount": 1,},
{"unit_amount": 3,},
{"unit_amount": 9,},
{"unit_amount": 5,},
]
};
const newObj = { ...obj, data: obj.data.sort((o1, o2) => o1.unit_amount - o2.unit_amount) };
console.log(newObj)
.as-console-wrapper {max-height: 100% !important; top: 0}
Objects in Javascript have no inherent order to their properties, whereas arrays do, so I suggest you extract the data array, sort it, then replace the unsorted version in your object. Something like this (object simplified for readability):
o = {
"object": "list",
"url": "/v1/prices",
"has_more": false,
"data": [
{
"id": "price_1KH",
"unit_amount": 12,
},
{
"id": "price_7QW",
"unit_amount": 3,
},
{
"id": "price_4DD",
"unit_amount": 7,
},
]
}
let data = o.data;
data.sort((x, y) => x.unit_amount - y.unit_amount);
o.data = data;
console.dir(o);
VM694:23
Object
data: Array(3)
0: {id: 'price_7QW', unit_amount: 3}
1: {id: 'price_4DD', unit_amount: 7}
2: {id: 'price_1KH', unit_amount: 12}
length: 3
[[Prototype]]: Array(0)
has_more: false
object: "list"
url: "/v1/prices"
[[Prototype]]: Object
Ramda will not make it easy for you to modify data, but if you want to create a copy of your input with a sorted copy of the array, that's pretty simple using the lens functions lensProp and over:
const sortData = over (lensProp ('data'), sortBy (prop ('unit_amount')))
const obj = {object: "list", url: "/v1/prices", has_more: false, data: [{unit_amount: 2,}, {unit_amount: 1,}, {unit_amount: 3,}, {unit_amount: 9,}, {unit_amount: 5,} ]};
console .log (sortData (obj))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.2/ramda.min.js"></script>
<script>const {over, lensProp, sortBy, prop} = R </script>
Here we steal the simple input format from Alexandr Belan. That answer already has some simple code. I think this is simpler -- and it's definitely more declarative -- but I wouldn't bother with it unless I was already using Ramda in my application.

how to make nested array objects in javascript in a key value pair format

array data=[
{
"id":1,
"name":"john",
"income":22000,
"expenses":15000
},
{
"id":2,
"name":"kiran",
"income":27000,
"expenses":13000
},
{
"id":1,
"name":"john",
"income":35000,
"expenses":24000
}
]
i want to make a new array set in following format which is in a key value pair. ie result set.
can you please explain the best method. ? how to achive using foreach.?
tried using foreach method by looping each element. but cant get the desired output format
var result= [ {
"name": "john",
"series": [
{
"name": "income",
"value": 22000
},
{
"name": "expenses",
"value": 15000
},
]
},
{
"name": "kiran",
"series": [
{
"name": "income",
"value": 27000
},
{
"name": "expenses",
"value": 13000
},
]
}]
// Your array
const result = [
{
name: "john",
series: [
{
name: "income",
value: 22000,
},
{
name: "expenses",
value: 15000,
},
],
},
{
name: "kiran",
series: [
{
name: "income",
value: 27000,
},
{
name: "expenses",
value: 13000,
},
],
},
];
// What is .map function?
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
// Output
// map return a new function.
// it's a loop method but more equipped
result.map((item, index) => {
const seriesKeyValues = {};
// forEach is too, it's a loop method.
// but not have a return value,
// just loops and give you item on each loop
item.series.forEach(serie => {
//seriesKeyValues is a object.
// different between seriesKeyValues.serie.name
// it's a bracket notation
// look this documentation
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names
seriesKeyValues[serie.name] = serie.value;
});
// return new Object
// ... is 'spread syntax' basically combine objects
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#spread_properties
// spread syntax is a new way.
// old way is https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
return {
id: index,
name: item.name,
...seriesKeyValues,
};
});
I hope it will help :). if you don't understand any lines of code, i can explain

Merge and Dedupe Array of Complex Objects with Arrays

I have a pretty complex problem that I can't seem to figure out. I have two array of objects that I would like to merge scores for. It should merge/append certain properties based on the scores. For example between the two arrays there are 4 total gameId's with 3 of them being unique. When merging it should combine the _scores section if it's the same gameId so in this case it would be both EarthNormal merging. But the problem is sometimes the score in _scores can have duplicate scores so the BAR and BASH almost look the exact same but are different it can be appended but FOO score is the exact same on both so I don't want it merged into the scores (if that makes sense).
const arr1 = [{
"gameId": "AirNormal",
"_scores":
[{
"score": 144701,
"playerName": "FOO",
"fullCombo": true,
"timestamp": 1599968866
}]
}, {
"gameId": "EarthNormal",
"_scores":
[{
"score": 177352,
"playerName": "BAR",
"fullCombo": true,
"timestamp": 1599969253
}, {
"score": 164665,
"playerName": "FOO",
"fullCombo": false,
"timestamp": 1599970971
}]
}];
const arr2 = [{
"gameId": "EarthNormal",
"_scores":
[{
"score": 177352,
"playerName": "BASH",
"fullCombo": false,
"timestamp": 1512969017
}, {
"score": 164665,
"playerName": "FOO",
"fullCombo": false,
"timestamp": 1599970971
}]
}, {
"gameId": "FireNormal",
"_scores":
[{
"_score": 124701,
"_playerName": "FOO",
"_fullCombo": true,
"_timestamp": 1591954866
}]
}];
I would want the final merged array to look like:
mergedArray = [{
"gameId": "AirNormal",
"_scores":
[{
"score": 144701,
"playerName": "FOO",
"fullCombo": true,
"timestamp": 1599968866
}]
}, {
"gameId": "EarthNormal",
"_scores":
[{
"score": 177352,
"playerName": "BAR",
"fullCombo": true,
"timestamp": 1599969253
}, {
"score": 177352,
"playerName": "BASH",
"fullCombo": false,
"timestamp": 1512969017
}, {
"score": 164665,
"playerName": "FOO",
"fullCombo": false,
"timestamp": 1599970971
}]
}, {
"gameId": "FireNormal",
"_scores":
[{
"score": 124701,
"playerName": "FOO",
"fullCombo": true,
"timestamp": 1591954866
}]
}]
I have tried doing this and using lodash:
let merged = [...arr1, ...arr2];
merged = _.uniqBy[merged, 'gameId']
let scoresMerge = _.uniqBy[merged, '_scores']
console.log(scoresMerge);
but it didn't work as I expected. Am I approaching this incorrectly?
This is fairly straight forward using vanilla javascript.
merge the arrays using destructuring
reduce() the merged arrays into an object indexed by gameId
check all properties of each _score object against the accumulated _scores array using .some() and push if no match is found.
return the values of the reduced object using Object.values()
const arr1 = [{ "gameId": "AirNormal", "_scores": [{ "score": 144701, "playerName": "FOO", "fullCombo": true, "timestamp": 1599968866 }]}, { "gameId": "EarthNormal", "_scores": [{ "score": 177352, "playerName": "BAR", "fullCombo": true, "timestamp": 1599969253 }, { "score": 164665, "playerName": "FOO", "fullCombo": false, "timestamp": 1599970971 }]}];
const arr2 = [{"gameId": "EarthNormal","_scores":[{"score": 177352,"playerName": "BASH","fullCombo": false,"timestamp": 1512969017}, {"score": 164665,"playerName": "FOO","fullCombo": false,"timestamp": 1599970971}]}, {"gameId": "FireNormal","_scores":[{"_score": 124701,"_playerName": "FOO","_fullCombo": true,"_timestamp": 1591954866}]}];
const merged = Object.values([...arr1, ...arr2].reduce((a, {gameId, _scores}) => {
// retrieve gameId object otherwise initialize it.
a[gameId] = {...a[gameId] ?? {gameId, _scores: []}};
// iterate over all _score objects
_scores.forEach(s => {
// if accumulator _scores array doesn't have an object matching all properties, push _score
if (!a[gameId]['_scores'].some(o => {
return !Object.entries(s).some(([k, v]) => o[k] !== v)})
) {
a[gameId]['_scores'].push({...s});
}
});
return a;
}, {}));
console.log(merged);
You need to identify objects with the same gameId, and then concat and dedupe their _.scores array.
It's easy to concat/dedup non primitive array items using Array.reduce() and a Map. For every item you check if the requested key is already in the Map. If it's not, you assign the current item to the Map's key. If it is you replace / merge the current item with the item in the Map.
After you finish iterating the Map, use Array.from() to convert the Map's .values() iterator to an array.
const arr1 = [{"gameId":"AirNormal","_scores":[{"score":144701,"playerName":"FOO","fullCombo":true,"timestamp":1599968866}]},{"gameId":"EarthNormal","_scores":[{"score":177352,"playerName":"BAR","fullCombo":true,"timestamp":1599969253},{"score":164665,"playerName":"FOO","fullCombo":false,"timestamp":1599970971}]}];
const arr2 = [{"gameId":"EarthNormal","_scores":[{"score":177352,"playerName":"BASH","fullCombo":false,"timestamp":1512969017},{"score":164665,"playerName":"FOO","fullCombo":false,"timestamp":1599970971}]},{"gameId":"FireNormal","_scores":[{"score":124701,"playerName":"FOO","fullCombo":true,"timestamp":1591954866}]}];
const dedupLastBy = (a1 = [], a2 = [], key) => Array.from(
[...a1, ...a2].reduce((acc, obj) => {
const keyName = obj[key];
if(acc.has(keyName)) acc.delete(keyName);
return acc.set(keyName, obj);
}, new Map()).values()
)
const handleDups = ({ _scores: a, ...o1 }, { _scores: b, ...o2 }) => ({
...o1,
...o2,
_scores: dedupLastBy(a, b, 'playerName')
});
const result = Array.from([...arr1, ...arr2]
.reduce((acc, o) => {
const { gameId } = o;
if(acc.has(gameId)) acc.set(gameId, handleDups(acc.get(gameId), o));
else acc.set(gameId, o);
return acc;
}, new Map()).values());
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>

using .includes with .map to find matching values

I'm trying to test if one array contains an id which exists in another array. Here's the first Array:
var ids = [
"N06Jrz5hx6Q9bcVDBBUrF3GKSTp2",
"eLLfNlWLkTcImTRqrYnU0nWuu9P2"
]
And here's an example of how the user object looks like:
var userExample = {
"category": "Chemia",
"coins": 100,
"friends": {},
"level": "Liceum",
"newAnswers": false,
"nickname": "czainusek",
"notifications": true,
"safety": true,
"tagID": "drawable/icon_other1",
"tasksUploaded": 0,
"works": {
"-LI1z-yO4MJnPwrdwHGl": "-LI1z-tB6EpqIhJwzchP",
"-LIHAwCpc84UQqGeT1bU": "-LIHAw8RuTZxfgz0ef8G",
"-LIHB2Ov5B-4XqpM79Av": "-LIHB2IPmMqOxa3UPFxs"
},
"id": "Nqez1dKRJ6aEpOb4BuUOK75iJoJ2"
}
And I have an array of said objects. They all look like this:
var usersExamples = [
{
"category": "Chemia",
"coins": 100,
"friends": {},
"level": "Liceum",
"newAnswers": false,
"nickname": "czainusek",
"notifications": true,
"safety": true,
"tagID": "drawable/icon_other1",
"tasksUploaded": 0,
"works": {
"-LI1z-yO4MJnPwrdwHGl": "-LI1z-tB6EpqIhJwzchP",
"-LIHAwCpc84UQqGeT1bU": "-LIHAw8RuTZxfgz0ef8G",
"-LIHB2Ov5B-4XqpM79Av": "-LIHB2IPmMqOxa3UPFxs"
},
"id": "Nqez1dKRJ6aEpOb4BuUOK75iJoJ2"
},
{
"coins": 20,
"friends": {},
"level": "Gimnazjum",
"newAnswers": false,
"nickname": "Tomasz ",
"notifications": true,
"safety": true,
"tagID": "drawable/emot_icons1",
"tasksUploaded": 5,
"id": "SAlVGjXIagdDuJrHYO4i6LwzUql2"
}
]
So knowing this I tried something like this:
console.log(reportIds.includes(users.map(user => user.id)));
This returned false. But if I try an id like this:
console.log(reportIds.includes('N06Jrz5hx6Q9bcVDBBUrF3GKSTp2'));
it returns true.
The object with said Id exists. I checked the ids manually. I tried this:
console.log(reportIds.includes(['8VbBNBDrT1Z3kmyaQzy6LqskcOT2', 'N06Jrz5hx6Q9bcVDBBUrF3GKSTp2'].map(id => id)));
But this returned false.
Same with this:
console.log(reportIds.includes(['8VbBNBDrT1Z3kmyaQzy6LqskcOT2', 'N06Jrz5hx6Q9bcVDBBUrF3GKSTp2']));
So how can I check which ids are in the ids array?
You can use a combination of Array#filter and Array#includes for that :
let ids = [
"N06Jrz5hx6Q9bcVDBBUrF3GKSTp2",
"eLLfNlWLkTcImTRqrYnU0nWuu9P2"
];
let usersExamples = [
{
"id": "Nqez1dKRJ6aEpOb4BuUOK75iJoJ2"
},
{
"id": "SAlVGjXIagdDuJrHYO4i6LwzUql2"
},
{
"id": "eLLfNlWLkTcImTRqrYnU0nWuu9P2"
}
];
let result = usersExamples.filter(u => ids.includes(u.id));
console.log(result);

Is it possible to pass a custom comparator to lodash's sortBy function?

For example, I want to sort with respect to Intl.Collator().compare. Is there any way to pass this comparator to be used by _.sortBy?
You can use lodash mixin's
_.mixin({
sortWith : function(arr, customFn) {
return _.map(arr).sort(customFn)
}
});
You can now do
_.sortWith(array, function(a, b) {
//custom function that returns either -1, 0, or 1 if a is <, ==, or > than b
});
You can now chain this like:
_.chain(myObject)
.get('some_array_property')
.sortWith(function(a, b) {
//determine if a <=> b
})
.value();
Internally, sortWith maps the array to a new array so that it doesn't modify the array passed into it and uses the native sort() method.
No, unfortunately this is not currently possible.
A workaround is to use the iteratees function to map the values to something the standard comparator will sort correctly. This is however almost never practical.
It's also asked for here https://github.com/lodash/lodash/issues/246, but no response from the author.
This is enough for simple ordering based on finite values.
const MAP = {
BRONZE: 1,
SILVER: 2,
GOLD: 3,
PLATINUM: 4,
}
const DATA = [
{ name: 'A', type: 'SILVER' },
{ name: 'B', type: 'BRONZE' },
{ name: 'C', type: 'PLATINUM' },
{ name: 'F', type: 'SILVER' },
{ name: 'G', type: 'GOLD' },
{ name: 'H', type: 'BRONZE' },
]
_.sortBy(DATA, (item) => MAP[item.type])
Result:
[
{"name":"B","type":"BRONZE"},
{"name":"H","type":"BRONZE"},
{"name":"A","type":"SILVER"},
{"name":"F","type":"SILVER"},
{"name":"G","type":"GOLD"},
{"name":"C","type":"PLATINUM"}
]
Not exactly sure what you are looking for. But if you are finding ways to use comparator in lodash sort, this may help:
Using _.chain() to get lodash collect will enable you to pass in comparator to sort()
console.log(JSON.stringify(_.sortBy(res, lat))); // sortBy doesn't take in comparator
console.log(
JSON.stringify(
_.chain(res)
.sort((a, b) => b.lat - a.lat) // sort takes in comparator
.value()
)
);
Not lodash, but might come in handy for someone looking for native sort.
var customSort = (
selector,
options,
locales = undefined,
) => (a, b) => {
return selector(a).localeCompare(selector(b), locales, {numeric: true,...options});
};
var x = [
{ name: '1-test' },
{ name: '01-test' },
{ name: '11-test' },
{ name: '11-Test' },
{ name: '10-test' },
{ name: '40-btest' },
{ name: '40-ctest' },
{ name: '40-atest' },
{ name: '2-test' },
{ name: '20-test' },
{ name: 'ätest' },
{ name: 'atest' },
];
console.log(x.sort(customSort((x) => x.name)));
console.log(x.sort(customSort((x) => x.name, { caseFirst: 'upper' })));
// in Swedish
console.log(x.sort(customSort((x) => x.name, { sensitivity: 'base' },'sv')));
Options come from:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
As said by other answers, you cannot pass in a comparator to _.sortBy like in Array.prototype.sort().
One workaround would be to add a new calculated property to the objects which would be the ordering and then use _.sortBy on that.
So if you had a list of objects like [{name: "hello there"}, {name: "world"}] but you wanted to sort them by name length, you could do:
_(arr)
//augment each object with a calculated `order` property
.map(obj => ({...obj, order: obj.name.length}))
.sortBy('order')
.value()
Result: [{name: "world", order: 5}, {name: "hello there", order: 11}]
Simple implementaion for lodash/fp (functional programming lodash) with currying (not restricted to lodash actually):
const sortWith = comparator => list => list.map(i => i).sort(comparator);
And for TypeScript:
type ComparatorFn<T> = (a: T, b: T) => number;
const sortWith = <P>(comparator: ComparatorFn<P>) => (list: P[]): P[] => list.map(i => i).sort(comparator);
Found the corresponding issue, here is stated that the mentioned feature is already merged long ago but unfortunately not released yet. https://github.com/lodash/lodash/pull/3764. This would be really great to have it available
actually iteratees can be mapping for the return result:
const users = [
{ 'user': 'fred', 'age': 48 },
{ 'user': 'barney', 'age': 36 },
{ 'user': 'fred', 'age': 40 },
{ 'user': 'barney', 'age': 34 }
];
_.sortBy(users, [function(o) { return o.user; }]);
// output: objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
or iteratees can be normal js sort function like i made in the below example to sort array of objects to sort cards when cardsStatus === 'NORI' so card should be on top of array
const cardsBeforeSort = [
{
"cardStatus": "NORM",
"consumedLimit": 0,
"cardAccountSerial": "10551880",
"cashLimit": null,
"applePayStatus": "ELIGIBLE",
"embossName": "Hdhh",
"nickName": "",
"aan": "123",
"balance": -9,
"key": "405433******8106"
},
{
"cardStatus": "NORI",
"consumedLimit": 0,
"cardAccountSerial": "10551908",
"cashLimit": null,
"applePayStatus": "ELIGIBLE",
"embossName": "Hdhh",
"nickName": "",
"aan": "123",
"balance": 1,
"key": "405433******8382"
},
{
"cardStatus": "HOLD",
"consumedLimit": -169122.81,
"cardAccountSerial": "10548192",
"cashLimit": null,
"applePayStatus": "ELIGIBLE",
"embossName": "Hdjj",
"nickName": "",
"aan": "123",
"balance": 5579.29,
"key": "417323******3321"
},
{
"cardStatus": "NORI",
"consumedLimit": -7.74,
"cardAccountSerial": "10549814",
"cashLimit": null,
"applePayStatus": "ELIGIBLE",
"embossName": "Hdhh",
"nickName": "",
"aan": "123",
"balance": 1,
"key": "429927******1548"
}
]
const sortedCards = sortBy(userCards, [
(first, second) =>
first.cardStatus === 'NORI' ? -1 : second === 'NORI' ? 1 : 0,
]);
this will result in the following output:
console.log(sortedCards);
[
{
"cardStatus": "NORI",
"consumedLimit": -7.74,
"cardAccountSerial": "10549814",
"cashLimit": null,
"applePayStatus": "ELIGIBLE",
"embossName": "Hdhh",
"nickName": "",
"aan": "123",
"balance": 1,
"key": "429927******1548"
},
{
"cardStatus": "NORI",
"consumedLimit": 0,
"cardAccountSerial": "10551908",
"cashLimit": null,
"applePayStatus": "ELIGIBLE",
"embossName": "Hdhh",
"nickName": "",
"aan": "123",
"balance": 1,
"key": "405433******8382"
},
{
"cardStatus": "NORM",
"consumedLimit": 0,
"cardAccountSerial": "10551880",
"cashLimit": null,
"applePayStatus": "ELIGIBLE",
"embossName": "Hdhh",
"nickName": "",
"aan": "123",
"balance": -9,
"key": "405433******8106"
},
{
"cardStatus": "HOLD",
"consumedLimit": -169122.81,
"cardAccountSerial": "10548192",
"cashLimit": null,
"applePayStatus": "ELIGIBLE",
"embossName": "Hdjj",
"nickName": "",
"aan": "123",
"balance": 5579.29,
"key": "417323******3321"
},
]
actually the benefit of using sortBy lodash function is being functional programming immutable solution because of not mutating cardsBeforeSort array

Categories

Resources