Sort objects of objects by nested property - javascript

I have an object that looks like the one below. How can I sort something like this based on a common property within nested object. The output I expect is for player2 to come first based on the higher score.
My challenge is in accessing the property of each object to sort.
Here is what I had in mind and tried but it didn't do the sorting.
Object.keys(data).sort(function(p1, p2){
return p1.score - p2.score;
}).forEach(function(key) {
var value = data[key];
delete data[key];
data[key] = value;
});
My data
var data =
{
player1:
{ score: 4,
cards: 6 },
player2:
{ score: 6,
cards: 4}
}

You need to sort the data with the object, not with a key's property and then it has to be reverted, because you need a descending sort.
return data[b].score - data[a].score;
// ^^^^^^^ ^^^^^^^ object
// ^ ^ descending
I suggest to use an empty object and insert the properties by the ordered keys.
var data = { player1: { score: 4, cards: 6 }, player2: { score: 6, cards: 4 } },
sorted = {};
Object
.keys(data).sort(function(a, b){
return data[b].score - data[a].score;
})
.forEach(function(key) {
sorted[key] = data[key];
});
console.log(sorted);

Here is one line functional approach using Object.entries(), Array.prototype.sort() and Object.fromEntries method. Before sorting you need to make the object an array by using Object.entries() method. It returns array of key-value pair of the given object. Then sort the array in descending order by the score. At last, use Object.fromEntries() method to transform the key-value pair into an object.
const data = {
player1: { score: 4, cards: 6 },
player2: { score: 6, cards: 4 },
};
const ret = Object.fromEntries(
Object.entries(data).sort((x, y) => y[1].score - x[1].score)
);
console.log(ret);

You can't sort an object. You should convert your object to an array and then sort it.
var data =
{
player1:
{ score: 4,
cards: 6 },
player2:
{ score: 6,
cards: 4}
}
var array = $.map(data, function(value, index) {
value.key = index;
return value;
});
var sortedData = array.sort(function(p1, p2){
return p2.score - p1.score;
});
console.log(sortedData);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

Related

How to dedupe an array of objects by a key value pair?

// This is a large array of objects, e.g.:
let totalArray = [
{"id":"rec01dTDP9T4ZtHL4","fields":
{"user_id":170180717,"user_name":"abcdefg","event_id":516575,
}]
let uniqueArray = [];
let dupeArray = [];
let itemIndex = 0
totalArray.forEach(x => {
if(!uniqueArray.some(y => JSON.stringify(y) === JSON.stringify(x))){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
node.warn(totalArray);
node.warn(uniqueArray);
node.warn(dupeArray);
return msg;
I need my code to identify duplicates in the array by a key value of user_id within the objects in the array. Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this? I am struggling to figure out how to path the for each loop to identify the dupe based on the key value instead of the entire object.
Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this?
Don’t compare the JSON representation of the whole objects then, but only their user_id property specifically.
totalArray.forEach(x => {
if(!uniqueArray.some(y => y.fields.user_id === x.fields.user_id)){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
You could take a Set and push to either uniques or duplicates.
var array = [
{ id: 1, data: 0 },
{ id: 2, data: 1 },
{ id: 2, data: 2 },
{ id: 3, data: 3 },
{ id: 3, data: 4 },
{ id: 3, data: 5 },
],
uniques = [],
duplicates = [];
array.forEach(
(s => o => s.has(o.id) ? duplicates.push(o) : (s.add(o.id), uniques.push(o)))
(new Set)
);
console.log(uniques);
console.log(duplicates);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way is to keep a list of ids you found so far and act accordingly:
totalArray = [
{ id: 1, val: 10 },
{ id: 2, val: 20 },
{ id: 3, val: 30 },
{ id: 2, val: 15 },
{ id: 1, val: 50 }
]
const uniqueArray = []
const dupeArray = []
const ids = {}
totalArray.forEach( x => {
if (ids[x.id]) {
dupeArray.push(x)
} else {
uniqueArray.push(x)
ids[x.id] = true
}
})
for (const obj of uniqueArray) console.log("unique:",JSON.stringify(obj))
for (const obj of dupeArray) console.log("dupes: ",JSON.stringify(obj))

Query an array of objects in JavaScript, matching against multiple criteria

I'm trying to query an array of objects in JavaScript, and return objects that match a specific filter criteria.
I've managed - thanks to help from others - to filter a simple object, but now I need to apply the same thing to a more complex object.
// Simple object query:
var recipes = {
'soup': {'ingredients': ['carrot', 'pepper', 'tomato']},
'pie': {'ingredients': ['carrot', 'steak', 'potato']},
'stew': {'ingredients': ['steak', 'pepper', 'tomato']}
};
var shoppingList = ['carrot', 'steak', 'tomato', 'pepper']
var result = Object.entries(recipes)//1. get the key-value pairs
.filter(([key, {ingredients}]) => ingredients.every(t => shoppingList.includes(t))) //2. filter them
.map(([key]) => key) //3. get the keys only
console.log(result);
// More complex object:
var itemsTest = [
{
uid: 1,
items: [
{ item: { uid: "a" } },
{ item: { uid: "b" } },
{ item: { uid: "g" } }
]
},
{
uid: 2,
items: [
{ item: { uid: "b" } },
{ item: { uid: "q" } },
{ item: { uid: "f" } }
]
},
}
];
var filter = ["b", "q", "f"]
// Expect filter to return {uid: 2, items}
}
The recipes filter works great. But now I have a more complex array of objects, it seems the same approach isn't possible.
I want to filter itemsTest according to the uid of each item in the items array. I'd be happy to use lodash, if it makes life easier.
I tried to flatten the array of objects using Object.entries(), to no avail.
var flattened = objectMap(itemsList, function(value) {
return Object.entries(value);
});
var result = flattened.filter(([key, { uid }]) =>
uid.every(t => filter.includes(t))
)
I also tried a simplified approach filtering with one value using Array.filter.prototype(), which doesn't work either:
var newArray = flattened.filter(function(el) {
return el.uid <= 2
});
console.log(newArray)
Any help understanding how to navigate an object like this would be great.
You can use Array.find() (or Array.filter() to iterate the array of objects. Now use Array.every(), and for each item check if it's uid is included in the filter array.
const itemsTest = [{"uid":1,"items":[{"item":{"uid":"a"}},{"item":{"uid":"b"}},{"item":{"uid":"g"}}]},{"uid":2,"items":[{"item":{"uid":"b"}},{"item":{"uid":"q"}},{"item":{"uid":"f"}}]}];
const filter = ["b", "q", "f"];
const result = itemsTest.find(({ items }) => // use filter instead of find to get multiple items
items.every(o => filter.includes(o.item.uid))
);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

Trying to sort object keys based on child property

I have this object:
var myObject = {
cat: {
order: 1
},
mouse: {
order: 4
},
dog: {
order: 2
},
shark: {
order: 3
}
}
I'm trying to get back: ["cat", "dog", "shark", "mouse"]
I tried:
_.sortBy(x, function(e) { return e.order} )
You can simply use Object.keys() and Array.sort() for it.
get all the keys from the Object using Object.keys().
Simply sort all the keys by passing a custom Comparator to the sort function which compares the order property of the keys in the object.
var myObject = { cat: { order: 1 }, mouse: { order: 4 }, dog: { order: 2 }, shark: { order: 3 } };
let result = Object.keys(myObject).sort((a,b)=> myObject[a].order - myObject[b].order);
console.log(result);
use Object.entries first, then sort by the order property of its second element (because Object.entries returns an array of a given object's own enumerable property [key, value] pairs), finally use Array.map to get what you need.
var myObject = {
cat: {
order: 1
},
mouse: {
order: 4
},
dog: {
order: 2
},
shark: {
order: 3
}
}
console.log(
Object.entries(myObject).sort((a, b) => {
return a[1].order - b[1].order
}).map(item => item[0])
)
var myObject = {
cat: {
order: 1
},
mouse: {
order: 4
},
dog: {
order: 2
},
shark: {
order: 3
}
}
let oKeys = Object.keys(myObject)
let tempArray = []
oKeys.forEach(function(key) {
tempArray[myObject[key]['order']-1] = key
})
console.log(tempArray)
Here is a solution using lodash.
Use _.map and _.sort for it.
First, _.map to array of order and name.
Then, _.sort and _.map name
By using lodash chain, make the code easy to read.
_(myObject)
.map((v, k) => ({order: v.order, name: k}))
.sortBy('order')
.map('name')
.value()

What is the best way to convert an array of strings into an array of objects in Angular 2?

Let's say I have an array as follows:
types = ['Old', 'New', 'Template'];
I need to convert it into an array of objects that looks like this:
[
{
id: 1,
name: 'Old'
},
{
id: 2,
name: 'New'
},
{
id: 3,
name: 'Template'
}
]
You can use map to iterate over the original array and create new objects.
let types = ['Old', 'New', 'Template'];
let objects = types.map((value, index) => {
return {
id: index + 1,
name: value
};
})
You can check a working example here.
The solution of above problem is the map() method of JavaScript or Type Script.
map() method creates a new array with the results of calling
a provided function on every element in the calling array.
let newArray = arr.map((currentvalue,index,array)=>{
return Element of array
});
/*map() method creates a new array with the results of calling
a provided function on every element in the calling array.*/
let types = [
'Old',
'New',
'Template'
];
/*
let newArray = arr.map((currentvalue,index,array)=>{
return Element of array
});
*/
let Obj = types.map((value, i) => {
let data = {
id: i + 1,
name: value
};
return data;
});
console.log("Obj", Obj);
Please follow following links:
TypeScript
JS-Fiddle
We can achieve the solution of above problem by for loop :
let types = [
"One",
"Two",
"Three"
];
let arr = [];
for (let i = 0; i < types.length; i++){
let data = {
id: i + 1,
name: types[i]
};
arr.push(data);
}
console.log("data", arr);

How can I use lodash/underscore to sort by multiple nested fields?

I want to do something like this:
var data = [
{
sortData: {a: 'a', b: 2}
},
{
sortData: {a: 'a', b: 1}
},
{
sortData: {a: 'b', b: 5}
},
{
sortData: {a: 'a', b: 3}
}
];
data = _.sortBy(data, ["sortData.a", "sortData.b"]);
_.map(data, function(element) {console.log(element.sortData.a + " " + element.sortData.b);});
And have it output this:
"a 1"
"a 2"
"a 3"
"b 5"
Unfortunately, this doesn't work and the array remains sorted in its original form. This would work if the fields weren't nested inside the sortData. How can I use lodash/underscore to sort an array of objects by more than one nested field?
I've turned this into a lodash feature request: https://github.com/lodash/lodash/issues/581
Update: See the comments below, this is not a good solution in most cases.
Someone kindly answered in the issue I created. Here's his answer, inlined:
_.sortBy(data, function(item) {
return [item.sortData.a, item.sortData.b];
});
I didn't realize that you're allowed to return an array from that function. The documentation doesn't mention that.
If you need to specify the sort direction, you can use _.orderBy with the array of functions syntax from Lodash 4.x:
_.orderBy(data, [
function (item) { return item.sortData.a; },
function (item) { return item.sortData.b; }
], ["asc", "desc"]);
This will sort first ascending by property a, and for objects that have the same value for property a, will sort them descending by property b.
It works as expected when the a and b properties have different types.
Here is a jsbin example using this syntax.
There is a _.sortByAll method in lodash version 3:
https://github.com/lodash/lodash/blob/3.10.1/doc/README.md#_sortbyallcollection-iteratees
Lodash version 4, it has been unified:
https://lodash.com/docs#sortBy
Other option would be to sort values yourself:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
function compareValues(v1, v2) {
return (v1 > v2)
? 1
: (v1 < v2 ? -1 : 0);
};
var data = [
{ a: 2, b: 1 },
{ a: 2, b: 2 },
{ a: 1, b: 3 }
];
data.sort(function (x, y) {
var result = compareValues(x.a, y.a);
return result === 0
? compareValues(x.b, y.b)
: result;
});
// data after sort:
// [
// { a: 1, b: 3 },
// { a: 2, b: 1 },
// { a: 2, b: 2 }
// ];
The awesome, simple way is:
_.sortBy(data, [function(item) {
return item.sortData.a;
}, function(item) {
return item.sortData.b;
}]);
I found it from check the source code of lodash, it always check the function one by one.
Hope that help.
With ES6 easy syntax and lodash
sortBy(item.sortData, (item) => (-item.a), (item) => (-item.b))
I think this could work in most cases with underscore:
var properties = ["sortData.a", "sortData.b"];
data = _.sortBy(data, function (d) {
var predicate = '';
for (var i = 0; i < properties.length; i++)
{
predicate += (i == properties.length - 1
? 'd.' + properties[i]
: 'd.' + properties[i] + ' + ')
}
return eval(predicate)
});
It works and you can see it in Plunker
If the problem is an integer is converted to a string, add zeroes before the integer to make it have the same length as the longest in the collection:
var maxLength = _.reduce(data, function(result, item) {
var bString = _.toString(item.sortData.b);
return result > bString.length ? result : bString.length;
}, 0);
_.sortBy(data, function(item) {
var bString = _.toString(item.sortData.b);
if(maxLength > bString.length) {
bString = [new Array(maxLength - bString.length + 1).join('0'), bString].join('');
}
return [item.sortData.a, bString];
});
I've found a good way to sort array by multiple nested fields.
const array = [
{id: '1', name: 'test', properties: { prop1: 'prop', prop2: 'prop'}},
{id: '2', name: 'test2', properties: { prop1: 'prop second', prop2: 'prop second'}}
]
I suggest to use 'sorters' object which will describe a key and sort order. It's comfortable to use it with some data table.
const sorters = {
'id': 'asc',
'properties_prop1': 'desc',//I'm describing nested fields with '_' symbol
}
dataSorted = orderBy(array, Object.keys(sorters).map(sorter => {
return (row) => {
if (sorter.includes('_')) { //checking for nested field
const value = row["properties"][sorter.split('_')[1]];
return value || null;
};
return row[sorter] || null;// checking for empty values
};
}), Object.values(sorters));
This function will sort an array with multiple nested fields, for the first arguments it takes an array to modify, seconds one it's actually an array of functions, each function have argument that actually an object from 'array' and return a value or null for sorting. Last argument of this function is 'sorting orders', each 'order' links with functions array by index. How the function looks like simple example after mapping:
orderBy(array, [(row) => row[key] || null, (row) => row[key] || null , (row) => row[key] || null] , ['asc', 'desc', 'asc'])
P.S. This code can be improved, but I would like to keep it like this for better understanding.

Categories