This question already has answers here:
Sorting an array of objects by property values
(35 answers)
Closed 5 years ago.
I have a JS object that has the following snippet:
{
"foo": "bar",
"baz": [
{
"order": 2,
"fruit": "banana"
},
{
"order": 1,
"fruit": "apple"
},
{
"order": 3,
"fruit": "peach"
},
]
}
My goal is to be able to iterate through all the objects in "baz" but in the order according to their "order" property, not the order as stored in the object. The order of the objects in "baz" can/will change frequently, so sorting them previously or storing them in the order they need to be in is not an option.
A pure JS or jQuery answer is all acceptable.
Hello you have to sort your object. Just use .sort of the array for that.
Here is a sample:
var obj = {
"foo": "bar",
"baz": [
{
"order": 2,
"fruit": "banana"
},
{
"order": 1,
"fruit": "apple"
},
{
"order": 3,
"fruit": "peach"
},
]
}
// get property
var arr = obj["baz"];
// may copy array
var counter = arr.length;
var arrCopy = new Array(counter);
while(counter--) {
arrCopy[counter] = arr[counter];
}
// sort
arrCopy.sort(function(a, b) { return a.order - b.order});
// iterate it
arrCopy.forEach(function(v) {
console.log(v.fruit);
})
You've said
The order of the objects in "baz" can/will change frequently...
If you literally mean just that, and not that their order property changes, you can give yourself another array, separate from baz, that contains the objects in order. Then it doesn't matter what order they're in in baz.
var bazInOrder = theObject.baz.slice();
bazInOrder.sort(function(a, b) {
return a.order - b.order;
});
If you add an entry to baz, you'll need to add it (in sorted position) to bazInOrder; and similarly if you delete an entry from baz, you'll want to delete it from bazInOrder.
Your other option is to add a property to them pointing to the "next" entry; e.g., overlaying a linked list on the array. That again requires a fair bit of maintenance on add/remove, but not just if their order in baz changes.
theObject.baz.slice()
.sort(function(a, b) { return a.order - b.order; })
.forEach(function(entry, index, arr) {
entry.next = index < arr.length - 1 ? arr[index + 1] : null;
});
then to access them in order requires finding the order = 1 entry, then looping with next:
for (var e = theObject.baz.find(function(e) { return e.order === 1; });
e;
e = e.next) {
// ...use `e`...
}
Again, though, linked lists involve maintenance on add/remove.
I guess you could create an array that has the original index of baz in order of baz[n].order
a bit like
var obj = {
"foo": "bar",
"baz": [
{
"order": 2,
"fruit": "banana"
},
{
"order": 1,
"fruit": "apple"
},
{
"order": 3,
"fruit": "peach"
},
]
}
var bazIndexOrder = obj.baz
.map(({order}, index) => ({index, order}))
.sort(({order:a}, {order:b}) => a - b)
.map(({index}) => index);
console.log(bazOrder); // [1, 0, 2]
bazOrder.forEach(index => console.log(obj.baz[index].fruit)); // apple, banana, peach
If the ES2015+ code scares you, it's
var bazIndexOrder = obj.baz.map(function (_ref, index) {
var order = _ref.order;
return { index: index, order: order };
}).sort(function (_ref2, _ref3) {
var a = _ref2.order;
var b = _ref3.order;
return a - b;
}).map(function (_ref4) {
var index = _ref4.index;
return index;
});
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
How can I make this
var foo = [{
"number":[1, 2, 3],
"id": [81, 82, 83]
}];
Into this
var foo = [{
"number": 1,
"id": 81
},{
"number": 2,
"id": 82
},{
"number": 3,
"id": 83
}]
I tried .map() and .filter() but they don't turn out the way I need it. Any suggestions? Thanks
You could create a function for that:
function transform(values) {
const result = [];
values.forEach(value => {
value.id.forEach((id, i) => {
result.push({id, number: value.number[i]});
});
});
return result;
}
While I find this to be an odd question, and I'm still hoping for a response regarding my suspicion this is an XY problem, here is a possible approach you can use for whatever you're trying to do.
Let's assume that foo is a single object which only contains enumerable properties that are all arrays of equal length:
var foo = {
"number": [1, 2, 3],
"id": [81, 82, 83]
}
function spread(obj) {
// get the enumerable keys of your object
var keys = Object.keys(obj)
// initialize an empty array
var array = []
// for each key...
keys.forEach(function (key) {
// for each element in the array of the property...
obj[key].forEach(function (value, index) {
// if the array element does not contain an object
// initialize an empty object in index
var base = index < array.length ? array[index] : (array[index] = {})
// assign the value to the key in the element
base[key] = value
})
})
// return the generated array
return array
}
console.log(spread(foo))
You can map the array's object, and thee number array of each object into a new array, then concat the results to flatten the sub arrays into one array.
var foo = [{
"number":[1, 2, 3],
"id": [81, 82, 83]
}];
var result = [].concat.apply([], foo.map(function(obj) { // map the array into sub arrays and flatten the results with concat
return obj.number.map(function(n, i) { // map the number array
return {
number: n,
id: obj.id[i] // get the id value by using the index
};
})
}));
console.log(result);
You need to create a list of objects based on the number of values within a given key.
So, you need to loop over the main list of objects. Inside that loop, you need to loop over the values for a key (i.e. pick the first). You will not need to use these values directly, they are just to determine how many records will be created in the final array. Lastly, you just iterate over the keys again and map the key-values pairs based on the current index.
The Array.prototype.concat.apply([], ...arrays) that happens at the end will combine all the arrays.
The function supports a single object or a list of objects.
var foo = [{
"number" : [ 1, 2, 3],
"id" : [81, 82, 83]
}, {
"number" : [ 4, 5, 6],
"id" : [84, 85, 86]
}];
console.log(JSON.stringify(mapValues(foo), null, 4));
function mapValues(arr) {
arr = !Array.isArray(arr) ? [arr] : arr;
return Array.prototype.concat.apply([], arr.map(item => {
return item[Object.keys(item)[0]].map((val, index) => {
return Object.keys(item).reduce((obj, key) => {
obj[key] = item[key][index];
return obj;
}, {});
});
}));
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
Here is another version that does not introduce much complexity.
var foo = [{
"number" : [1, 2, 3],
"id" : [81, 82, 83]
}, {
"number" : [4, 5, 6],
"id" : [84, 85, 86]
}];
console.log(JSON.stringify(mapValues(foo), null, 4));
function mapValues(arr) {
arr = !Array.isArray(arr) ? [arr] : arr;
let records = [], fields;
arr.forEach(item => {
fields = fields || Object.keys(item);
item[fields[0]].forEach((val, index) => {
records.push(fields.reduce((obj, key) => {
obj[key] = item[key][index];
return obj;
}, {}));
});
});
return records;
}
.as-console-wrapper {
top: 0;
max-height: 100% !important;
}
Result
[{
"number": 1,
"id": 81
}, {
"number": 2,
"id": 82
}, {
"number": 3,
"id": 83
}, {
"number": 4,
"id": 84
}, {
"number": 5,
"id": 85
}, {
"number": 6,
"id": 86
}]
I'm working in wso2 carbon dashboard. My table is containing 2 fields (Name and Number). I Have duplicate name in the objects but with different number. I want unique name with addition of numbers.
[
{
"Name":"Abc",
"number":2
},
{
"Name":"bcd",
"number":3
},
{
"Name":"Abc",
"number":5
}
]
expected output
[
{
"name":"Abc",
"Number":7
},
{
"name":"bcd",
"Number":3
}
]
I'm using java script to achieve such task. please help me
Use Array#reduce method with a reference object.
var data = [{
"Name": "Abc",
"number": 2
}, {
"Name": "bcd",
"number": 3
}, {
"Name": "Abc",
"number": 5
}];
// object for index reference
var ref = {};
// iterate and generate the array
var res = data.reduce(function(arr, o) {
// check index already defined
if (!(o.Name in ref)) {
// if not defined then define index
ref[o.Name] = arr.length;
// and push the element
// you can also use
// arr.push({Name:o.Name, number:o.number});
arr.push(Object.assign({}, o));
} else {
// if index already defined update the number
arr[ref[o.Name]].number += o.number;
}
// return the array reference
return arr;
// set initial value as empty array
}, []);
console.log(res);
I have this kind of array:
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
I'd like to filter it to have:
var bar = [ { "a" : "1" }, { "b" : "2" }];
I tried using _.uniq, but I guess because { "a" : "1" } is not equal to itself, it doesn't work. Is there any way to provide underscore uniq with an overriden equals function?
.uniq/.unique accepts a callback
var list = [{a:1,b:5},{a:1,c:5},{a:2},{a:3},{a:4},{a:3},{a:2}];
var uniqueList = _.uniq(list, function(item, key, a) {
return item.a;
});
// uniqueList = [Object {a=1, b=5}, Object {a=2}, Object {a=3}, Object {a=4}]
Notes:
Callback return value used for comparison
First comparison object with unique return value used as unique
underscorejs.org demonstrates no callback usage
lodash.com shows usage
Another example :
using the callback to extract car makes, colors from a list
If you're looking to remove duplicates based on an id you could do something like this:
var res = [
{id: 1, content: 'heeey'},
{id: 2, content: 'woah'},
{id: 1, content:'foo'},
{id: 1, content: 'heeey'},
];
var uniques = _.map(_.groupBy(res,function(doc){
return doc.id;
}),function(grouped){
return grouped[0];
});
//uniques
//[{id: 1, content: 'heeey'},{id: 2, content: 'woah'}]
Implementation of Shiplu's answer.
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var x = _.uniq( _.collect( foo, function( x ){
return JSON.stringify( x );
}));
console.log( x ); // returns [ { "a" : "1" }, { "b" : "2" } ]
When I have an attribute id, this is my preffered way in underscore:
var x = [{i:2}, {i:2, x:42}, {i:4}, {i:3}];
_.chain(x).indexBy("i").values().value();
// > [{i:2, x:42}, {i:4}, {i:3}]
Using underscore unique lib following is working for me, I m making list unique on the based of _id then returning String value of _id:
var uniqueEntities = _.uniq(entities, function (item, key, a) {
return item._id.toString();
});
Here is a simple solution, which uses a deep object comparison to check for duplicates (without resorting to converting to JSON, which is inefficient and hacky)
var newArr = _.filter(oldArr, function (element, index) {
// tests if the element has a duplicate in the rest of the array
for(index += 1; index < oldArr.length; index += 1) {
if (_.isEqual(element, oldArr[index])) {
return false;
}
}
return true;
});
It filters out all elements if they have a duplicate later in the array - such that the last duplicate element is kept.
The testing for a duplicate uses _.isEqual which performs an optimised deep comparison between the two objects see the underscore isEqual documentation for more info.
edit: updated to use _.filter which is a cleaner approach
The lodash 4.6.1 docs have this as an example for object key equality:
_.uniqWith(objects, _.isEqual);
https://lodash.com/docs#uniqWith
Try iterator function
For example you can return first element
x = [['a',1],['b',2],['a',1]]
_.uniq(x,false,function(i){
return i[0] //'a','b'
})
=> [['a',1],['b',2]]
here's my solution (coffeescript) :
_.mixin
deepUniq: (coll) ->
result = []
remove_first_el_duplicates = (coll2) ->
rest = _.rest(coll2)
first = _.first(coll2)
result.push first
equalsFirst = (el) -> _.isEqual(el,first)
newColl = _.reject rest, equalsFirst
unless _.isEmpty newColl
remove_first_el_duplicates newColl
remove_first_el_duplicates(coll)
result
example:
_.deepUniq([ {a:1,b:12}, [ 2, 1, 2, 1 ], [ 1, 2, 1, 2 ],[ 2, 1, 2, 1 ], {a:1,b:12} ])
//=> [ { a: 1, b: 12 }, [ 2, 1, 2, 1 ], [ 1, 2, 1, 2 ] ]
with underscore i had to use String() in the iteratee function
function isUniq(item) {
return String(item.user);
}
var myUniqArray = _.uniq(myArray, isUniq);
I wanted to solve this simple solution in a straightforward way of writing, with a little bit of a pain of computational expenses... but isn't it a trivial solution with a minimum variable definition, is it?
function uniq(ArrayObjects){
var out = []
ArrayObjects.map(obj => {
if(_.every(out, outobj => !_.isEqual(obj, outobj))) out.push(obj)
})
return out
}
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var bar = _.map(_.groupBy(foo, function (f) {
return JSON.stringify(f);
}), function (gr) {
return gr[0];
}
);
Lets break this down. First lets group the array items by their stringified value
var grouped = _.groupBy(foo, function (f) {
return JSON.stringify(f);
});
grouped looks like:
{
'{ "a" : "1" }' = [ { "a" : "1" } { "a" : "1" } ],
'{ "b" : "2" }' = [ { "b" : "2" } ]
}
Then lets grab the first element from each group
var bar = _.map(grouped, function(gr)
return gr[0];
});
bar looks like:
[ { "a" : "1" }, { "b" : "2" } ]
Put it all together:
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var bar = _.map(_.groupBy(foo, function (f) {
return JSON.stringify(f);
}), function (gr) {
return gr[0];
}
);
You can do it in a shorthand as:
_.uniq(foo, 'a')
We have MongoDB docs that look like this:
var JavascriptObject = {
DbDocs : [
{
_id : "1",
{..more values..}
},
{
_id : "2",
{..more values..}
},
{
_id : "3",
{..more values..}
}
]
}
Based on certain values in the JavascriptObject, we order an array of the _id from the documents, and the result is this:
var OrderedArray = [ 2, 1, 3 ];
Right now, we're rebuilding the entire JavascriptObject by matching the _id in the OrderedArray with the _id in DbDocs:
var JavascriptObjectToRebuild = [];
var DbDocuments = JavascriptObject.DbDocs;
var DocumentCount = 0;
for (var OrderedNumber in OrderedArray) {
for (var Document in DbDocuments) {
if ( DbDocuments[Document]._id === OrderedArray[OrderedNumber] ) {
JavascriptObjectToRebuild[DocumentCount] = {}; // new Document Object
JavascriptObjectToRebuild[DocumentCount]._id = DbDocuments[Document]._id;
JavascriptObjectToRebuild[DocumentCount]...more values = DbDocuments[Document]...more values;
DocumentCount++; // increment
}
}
}
var SortedJavascriptObject = { DbDocs: [] }; // format for client-side templating
for (var Document in JSONToRebuild) {
SortedJavascriptObject.DbDocs.push(JavascriptObjectToRebuild[Document]);
}
Is there a faster more efficient way to sort the JavascriptObject based on this OrderedArray?
See update below if it's impossible to sort directly and you have to use OrderedArray instead.
If you can apply your criteria within the callback of the Array#sort function (e.g., if you can do it by comparing two entries in the array to one another), you can simply sort JSON.DbDocs directly.
Here's an example that sorts based on the numeric value of _id; naturally you'd replace that with your logic comparing objects.
Also note I've changed the name of the top-level variable (JSON is kinda in use, and in any case, it's not JSON):
var Obj = {
DbDocs : [
{
_id : "2",
more: "two"
},
{
_id : "1",
more: "one"
},
{
_id : "3",
more: "three"
}
]
};
Obj.DbDocs.sort(function(a, b) {
return +a._id - +b._id; // Replace with your logic comparing a and b
});
document.querySelector('pre').innerHTML = JSON.stringify(Obj, null, 2);
<pre></pre>
If it's impossible to sort directly and you have to work from OrderedArray, then it's still possible with sort, but it's less elegant: You use Array#indexOf to find out where each entry in the array should be:
Obj.DbDocs.sort(function(a, b) {
return OrderedArray.indexOf(+a._id) - OrderedArray.indexOf(+b._id);
});
(The + converts the IDs from strings to numbers, since OrderedArray contains numbers in your question, but the ID values are strings.)
Live Example:
var Obj = {
DbDocs : [
{
_id : "1",
more: "one"
},
{
_id : "2",
more: "two"
},
{
_id : "3",
more: "three"
}
]
};
var OrderedArray = [2, 1, 3];
Obj.DbDocs.sort(function(a, b) {
return OrderedArray.indexOf(+a._id) - OrderedArray.indexOf(+b._id);
});
document.querySelector('pre').innerHTML = JSON.stringify(Obj, null, 2);
<pre></pre>
If there are going to be lots of entries in OrderedArray, you might want to make a lookup object first, to avoid lots of indexOf calls (which are costly: (georg did that in an answer, but he's since deleted it for some reason)
var OrderMap = {}
OrderedArray.forEach(function(entry, index) {
OrderMap[entry] = index;
});
Obj.DbDocs.sort(function(a, b) {
return OrderMap[a._id] - OrderMap[b._id];
});
(We don't need to convert the IDs to numbers because property names are always strings, so we've converted the numbers to strings when building the map.)
Live Example:
var Obj = {
DbDocs : [
{
_id : "1",
more: "one"
},
{
_id : "2",
more: "two"
},
{
_id : "3",
more: "three"
}
]
};
var OrderedArray = [2, 1, 3];
var OrderMap = {}
OrderedArray.forEach(function(entry, index) {
OrderMap[entry] = index;
});
Obj.DbDocs.sort(function(a, b) {
return OrderMap[a._id] - OrderMap[b._id];
});
document.querySelector('pre').innerHTML = JSON.stringify(Obj, null, 2);
<pre></pre>
As I understood, you want to get result like so,
[{"_id":"2"}, {"_id":"1"}, {"_id":"3"}]
so you can do it with one forEach and indexOf, like so
var JSONDADA = {
DbDocs : [{_id : "1",}, {_id : "2"}, {_id : "3"}]
};
var DbDocuments = JSONDADA.DbDocs;
var OrderedArray = [ 2, 1, 3 ];
var result = [];
DbDocuments.forEach(function (el) {
var position = OrderedArray.indexOf(+el._id);
if (position >= 0) {
result[position] = el;
}
});
console.log(JSON.stringify(result));
303 see other
......
Convert OrderedNumber into a hash _id => position:
sorter = {}
OrderedNumber.forEach(function(_id, pos) {
sorter[_id] = pos
})
and then sort the target array by comparing id's positions:
DbDocuments.sort(function(a, b) {
return sorter[a._id] - sorter[b._id];
})
For example with have this code:
var json = {
"user1" : {
"id" : 3
},
"user2" : {
"id" : 6
},
"user3" : {
"id" : 1
}
}
How can I sort this json to be like this -
var json = {
"user3" : {
"id" : 1
},
"user1" : {
"id" : 3
},
"user2" : {
"id" : 6
}
}
I sorted the users with the IDs..
I don't know how to do this in javascript..
First off, that's not JSON. It's a JavaScript object literal. JSON is a string representation of data, that just so happens to very closely resemble JavaScript syntax.
Second, you have an object. They are unsorted. The order of the elements cannot be guaranteed. If you want guaranteed order, you need to use an array. This will require you to change your data structure.
One option might be to make your data look like this:
var json = [{
"name": "user1",
"id": 3
}, {
"name": "user2",
"id": 6
}, {
"name": "user3",
"id": 1
}];
Now you have an array of objects, and we can sort it.
json.sort(function(a, b){
return a.id - b.id;
});
The resulting array will look like:
[{
"name": "user3",
"id" : 1
}, {
"name": "user1",
"id" : 3
}, {
"name": "user2",
"id" : 6
}];
Here is a simple snippet that sorts a javascript representation of a Json.
function isObject(v) {
return '[object Object]' === Object.prototype.toString.call(v);
};
JSON.sort = function(o) {
if (Array.isArray(o)) {
return o.sort().map(JSON.sort);
} else if (isObject(o)) {
return Object
.keys(o)
.sort()
.reduce(function(a, k) {
a[k] = JSON.sort(o[k]);
return a;
}, {});
}
return o;
}
It can be used as follows:
JSON.sort({
c: {
c3: null,
c1: undefined,
c2: [3, 2, 1, 0],
},
a: 0,
b: 'Fun'
});
That will output:
{
a: 0,
b: 'Fun',
c: {
c2: [3, 2, 1, 0],
c3: null
}
}
In some ways, your question seems very legitimate, but I still might label it an XY problem. I'm guessing the end result is that you want to display the sorted values in some way? As Bergi said in the comments, you can never quite rely on Javascript objects ( {i_am: "an_object"} ) to show their properties in any particular order.
For the displaying order, I might suggest you take each key of the object (ie, i_am) and sort them into an ordered array. Then, use that array when retrieving elements of your object to display. Pseudocode:
var keys = [...]
var sortedKeys = [...]
for (var i = 0; i < sortedKeys.length; i++) {
var key = sortedKeys[i];
addObjectToTable(json[key]);
}
if(JSON.stringify(Object.keys(pcOrGroup).sort()) === JSON.stringify(Object.keys(orGroup)).sort())
{
return true;
}