I need to sort an array based on another array. However, there is a larger array but I only want to sort on some items properties. The first array looks like this :
var data = [ { id : "23", name : "Item 1", isActive : true},
{ id : "25", name : "Item 2", isActive : false},
{ id : "26", name : "Item 3", isActive : false},
{ id : "30", name : "Item 4", isActive : true},
{ id : "45", name : "Item 5", isActive : true}
]
Then I do this to get the id of active items :
var ids = _.filter(c, function(el) {
return el.isActive === true;
}).map(function(e) { return e.id;}) //["23","30","45"]
I do some operation (database comparaison) and I get for example a result like this :
var sorted = ["45","23","30"]
As I first sorted items by isActive, I only want to sort items that are active. (depending on the sorted array above ) Like this :
var expected = [
{ id : "45", name : "Item 5", isActive : true},
{ id : "23", name : "Item 1", isActive : true},
{ id : "30", name : "Item 4", isActive : true},
//try to keep order for the last values
{ id : "25", name : "Item 2", isActive : false},
{ id : "26", name : "Item 3", isActive : false}]
I tried to use _.sortBy method but this didn't worked as expected :
var sortObj = sorted.reduce(function(acc, value, index) {
acc[value] = value;
return acc;
}, {});
sortedItems = _.sortBy(data, function(x) {
return _.indexOf(sortObj, x.id)
});
But it didn't work as expected
Any ideas ? Huge thanks in advance =)
As you want to keep the order for sorted, and inactive,.
We can use two associated array's to keep an index of the order, you can then use the two associated arrays to create a sort of composite index.
Updated: To hopefully simplify and make easer to follow.
var data = [ { id : "23", name : "Item 1", isActive : true},
{ id : "25", name : "Item 2", isActive : false},
{ id : "26", name : "Item 3", isActive : false},
{ id : "30", name : "Item 4", isActive : true},
{ id : "45", name : "Item 5", isActive : true}
];
var sorted = ["45","23","30"];
//true = active sort, false = data sort
var order = { true: {}, false: {}};
//create order index for data
data.forEach(function (o, i) { order[false][o.id] = i; });
//create order index for sort
sorted.forEach(function (v, i) { order[true][v] = i });
data.sort(function (a, b) {
return (b.isActive - a.isActive) ||
order[a.isActive][a.id] - order[b.isActive][b.id];
});
console.log(data);
You can run 2 filters and combine the result into one array.
return el.isActive === true; and return el.isActive === false;
You can sort both results before inserting into final array
You could use sorting with map and an object for the sort order.
var data = [{ id: "23", name: "Item 1", isActive: true }, { id: "25", name: "Item 2", isActive: false }, { id: "26", name: "Item 3", isActive: false }, { id: "30", name: "Item 4", isActive: true }, { id: "45", name: "Item 5", isActive: true }],
sorted = ["45", "23", "30"],
order = {};
sorted.forEach(function (a, i) { order[a] = i + 1; });
// temporary array holds objects with position and sort-value
var mapped = data.map(function (el, i) {
return { index: i, active: el.isActive, value: order[el.id] || i };
});
// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
return b.active - a.active || a.value - b.value;
});
// container for the resulting order
var result = mapped.map(function (el) {
return data[el.index];
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I have a nested object with no pre-determinable path to child objects - example:
{
children :
[
{
children :
[
{
children :
[
{
id : "F100517B-D00F",
level : 4,
note : "update me",
parentId : "23A2A0DB-CCE3",
title : "change me"
}
],
id : "23A2A0DB-CCE3",
level : 3,
note : "me too",
parentId : "a0H4H00000Roi",
title : "..and me"
}
],
id : "a0H4H00000Roi",
level : 2,
note : "none",
parentId : "0064H00000ysL",
title : "pending"
},
{
"children" :
[
{
id : "6A45E2EC-7825",
level : 3,
note : "|",
parentId : "a0H4H00000Roi",
title : ""
}
],
id : "a0H4H00000Roi",
level : 2,
note : "",
parentId : "0064H00000ysL",
title : "Change me"
}
],
id : "0064H00000ysL",
level : 1,
note : "hello",
title : "Test Co"
}
Following this I generate a list of updates via a map function - sample results:
[{
content: "New Co",
id: "0064H00000ysL",
note: "Here's your update"
}, {
content: "91%",
id: "a0H4H00000Roi",
note: "New note here"
}]
I need to iterate through the update object array and update nested object values, I've tried a few things but can't seem to quite nail it (my JS skill is a bit limited atm).
Here's my last attempt, taken from the closest solution I found here:
Javascript update values in nested object by array path
var updates = $('div.node').map(function() {
return {
id: $(this).attr("id"),
content: $(this).find('div.content').text(),
note: $(this).find('div.note').text()
};
}).get();
const checkAndChange = (obj, update) => { //function to check id match for update
if (update.id.includes(obj.id)) {
obj.title = update.content;
obj.note = update.note;
}
}
const recursion = (obj, update) => {
const o = obj;
checkAndChange(o, update); // check if id exists update values
if (o.children.length > 0) { //check if has children
o.children.forEach(v => { //if has children do same recursion for children
recursion(v, update);
});
}
return o; //return updated object
}
var updatesLength = updates.length;
for(let it = 0; it < updatesLength; it++) {
recursion(obj, updates[it]);
}
console.log(obj)
The indented map function at the top works fine but I get Uncaught TypeError: Cannot read properties of undefined (reading 'id')" when I try to loop though the update array and write back to the main object (obj).
Any help appreciated
You can use a redursive approach here, we'll create a function updateNestedObj() to apply the updates, applying to each object and any of its child objects:
const objToUpdate = { children : [ { children : [ { children : [ { id : "F100517B-D00F", level : 4, note : "update me", parentId : "23A2A0DB-CCE3", title : "change me" } ], id : "23A2A0DB-CCE3", level : 3, note : "me too", parentId : "a0H4H00000Roi", title : "..and me" } ], id : "a0H4H00000Roi", level : 2, note : "none", parentId : "0064H00000ysL", title : "pending" }, { "children" : [ { id : "6A45E2EC-7825", level : 3, note : "|", parentId : "a0H4H00000Roi", title : "" } ], id : "a0H4H00000Roi", level : 2, note : "", parentId : "0064H00000ysL", title : "Change me" } ], id : "0064H00000ysL", level : 1, note : "hello", title : "Test Co" }
const updateArr = [{ content: "New Co", id: "0064H00000ysL", note: "Here's your update" }, { content: "91%", id: "a0H4H00000Roi", note: "New note here" }];
function updateNestedObj(obj, updates) {
const updateToApply = updates.find(upd => upd.id === obj.id);
if (updateToApply) {
obj.title = updateToApply.content;
obj.note = updateToApply.note;
}
// Apply updates to any child objects
for(let k in obj) {
if (typeof(obj[k]) === 'object') {
updateNestedObj(obj[k], updates);
}
}
}
updateNestedObj(objToUpdate, updateArr);
console.log(objToUpdate)
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have an object with keys representing a schema. A schema contains a definition of columns and I want to end up with a list of columns that match a specific condition. I can use a for each but think it should also be possible to achieve with a filter or reduce; however getting stuck accessing the nested portion and accumulate the results.
I tried a number of variants, with reduce, spread operators but not achieving the desired result.
Hope anyone can pull me out of the quicksand, I tend to sink deeper with every move I make.
<script type="text/javascript">
let data = {
"Schema A" : [{"Id" : "1", "Type" : "measure"}, {"Id" : "2", "Type" : "dimension"}, {"Id" : "3", "Type" : "measure"}],
"Schema B" : [{"Id" : "4", "Type" : "measure"}, {"Id" : "5", "Type" : "dimension"}, {"Id" : "6", "Type" : "measure"}],
};
var d = Object.entries(data); // convert to array
console.log(d);
let result = d.map(val => {
return val.filter(x => x[1].Type === 'measure');
});
console.log(result);
</script>
You need to take the values from the key/value entries for filtering.
const
data = { "Schema A": [{ Id: "1", Type: "measure" }, { Id: "2", Type: "dimension" }, { Id: "3", Type: "measure" }], "Schema B" : [{ Id: "4", Type: "measure" }, { Id: "5", Type: "dimension" }, { Id: "6", Type: "measure" }] },
result = Object
.entries(data)
.map(([k, v]) => [k, v.filter(({ Type }) => Type === 'measure')]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
suppose i have this data:
data = [{
"_id" : "2fApaxgiPx38kpDLA",
"profile" : {
"name" : "Karina 1",
"avatar" : "avatar1.jpg",
"bio" : "my bio 1"
},
{
"_id" : "NXM6H4EWfeRAAhB7c",
"profile" : {
"name" : "Karina 2",
"avatar" : "avatar2.jpg",
"bio" : "my bio 2"
},
}];
i did _.map(data, "profile") but it remove top level _id:
wrongResult = [{
"name" : "Karina 1",
"avatar" : "avatar1.jpg",
"bio" : "my bio 1"
},
{
"name" : "Karina 2",
"avatar" : "avatar2.jpg",
"bio" : "my bio 2"
}];
How to move nested object into top level without removing top level like this one:
expectedResult = [{
"_id" : "2fApaxgiPx38kpDLA",
"name" : "Karina 1",
"avatar" : "avatar1.jpg",
"bio" : "my bio 1"
},
{
"_id" : "NXM6H4EWfeRAAhB7c",
"name" : "Karina 2",
"avatar" : "avatar2.jpg",
"bio" : "my bio 2"
}];
thank You so much....
Something Like this? (not tested)
_.map(data,function(d){
d.profile._id = d._id;
return d.profile;
});
Recently needed to do something like this myself. Ended up writing a general purpose function to bring all (nested included) object values to the top level:
const reduceObjValues = (obj, cache = {}) => {
const objectValues = Object.keys(obj).reduce((acc, cur) => {
if (!Array.isArray(obj[cur]) && typeof obj[cur] === 'object') {
return reduceObjValues({ ...acc, ...obj[cur] }, cache);
}
acc[cur] = obj[cur];
return acc;
}, {});
return {
...objectValues,
...cache,
};
}
reduceObjValues({
a: {
b: 'a',
c: 'b',
},
d: {
e: 'a',
f: {
g: {
h: [
1,
2,
3,
]
}
}
}
});
=> { b: 'a', c: 'b', e: 'a', h: [ 1, 2, 3 ] }
one issue with this function is that it will overwrite any keys that are the same.
You can use flatten to move the nested object to its parent level... https://www.npmjs.com/package/flat
Since you are using lodash, I came up with a generic function to flatten out any deeply nested object.
const flattener = obj => {
const toPairs = obj => _.entries(obj).map(([key, val]) => typeof val === 'object' ? toPairs(val) : [key, val]);
return _.chain(toPairs(obj)).flattenDeep().chunk(2).fromPairs().value();
}
So, with an array like this
data = [
{
"_id" : "2fApaxgiPx38kpDLA",
"profile" : {
"name" : "Karina 1",
"avatar" : "avatar1.jpg",
"bio" : "my bio 1"
}
},
{
"_id" : "NXM6H4EWfeRAAhB7c",
"profile" : {
"name" : "Karina 2",
"avatar" : "avatar2.jpg",
"bio" : "my bio 2"
},
}
]
you can do
data.map(obj => flattener(obj))
which will give you
[
{
"_id": "2fApaxgiPx38kpDLA",
"name": "Karina 1",
"avatar": "avatar1.jpg",
"bio": "my bio 1"
},
{
"_id": "NXM6H4EWfeRAAhB7c",
"name": "Karina 2",
"avatar": "avatar2.jpg",
"bio": "my bio 2"
}
]
NB: This flattener function will throw away duplicate object keys, so if you have an object like;
myObj = { name: 'rick', age: 10, country: { name: 'uganda' } }
Flattening this out by calling flattener(myObj) will result in
{ name: 'uganda', age: 10 }
and not in
{ name: 'uganda', age: 10, name: 'rick' }
because you can't have an object with 2 similar keys even if the values to those keys are unique.
I have one JSON Object like this :
var myObject = [
{
"Name" : "app1",
"id" : "1",
"groups" : [
{ "id" : "test1",
"name" : "test group 1",
"desc" : "this is a test group"
},
{ "id" : "test2",
"name" : "test group 2",
"desc" : "this is another test group"
}
]
},
{
"Name" : "app2",
"id" : "2",
"groups" : [
{ "id" : "test3",
"name" : "test group 4",
"desc" : "this is a test group"
},
{ "id" : "test4",
"name" : "test group 4",
"desc" : "this is another test group"
}
]
},
{
"Name" : "app3",
"id" : "3",
"groups" : [
{ "id" : "test5",
"name" : "test group 5",
"desc" : "this is a test group"
},
{ "id" : "test6",
"name" : "test group 6",
"desc" : "this is another test group"
}
]
}
];
I have new value available of "name" for specific "id".
How can I replace "name" of specific "id" inside any object ?
And how to count total number of groups among all objects ?
for example : replace name to "test grp45" for id = "test1"
Here is fiddle
http://jsfiddle.net/qLTB7/21/
The following function will search through an object and all of its child objects/arrays, and replace the key with the new value. It will apply globally, so it won't stop after the first replacement. Uncomment the commented line to make it that way.
function findAndReplace(object, value, replacevalue) {
for (var x in object) {
if (object.hasOwnProperty(x)) {
if (typeof object[x] == 'object') {
findAndReplace(object[x], value, replacevalue);
}
if (object[x] == value) {
object["name"] = replacevalue;
// break; // uncomment to stop after first replacement
}
}
}
}
Working jsfiddle: http://jsfiddle.net/qLTB7/28/
Try this
function findAndReplace(object,keyvalue, name) {
object.map(function (a) {
if (a.groups[0].id == keyvalue) {
a.groups[0].name = name
}
})
}
findAndReplace(myObject,"test1" ,"test grp45");
Here's a different approach using Array.prototype.some. It assumes that the Name property in the outer objects should be actually be name (note capitalisation).
function updateNameById(obj, id, value) {
Object.keys(obj).some(function(key) {
if (obj[key].id == id) {
obj[key].name = value;
return true; // Stops looping
}
// Recurse over lower objects
else if (obj[key].groups) {
return updateNameById(obj[key].groups, id, value);
}
})
}
The advantage of some is that it stops as soon as the callback returns true.
I think this should work for you:-
var id = 'test1';
var newname = 'test grp45';
var numberOfGruops = 0;
myObject.forEach(function(app){
numberOfGruops += app.groups.length; //Count all groups in this app
app.groups.forEach(function(group){
if(group.id===id)
group.name = newname; // replace the name
});
});
Maybe a more succinct sol'n
function changeName(objArray, objId, newName) {
objArray.forEach(function(obj) {
if (obj.id === objId) obj.Name = newName;
});
}
Personally: if this were me, when creating these objects, I would create a new obj and key them by id.
var myApps = {};
myObject.forEach(function(o) {
myApps[o.id] = o;
});
=>
{
"1": {
"Name": "app1",
"id": "1",
"groups": [
{
"id": "test1",
"name": "test group 1",
"desc": "this is a test group"
},
{
"id": "test2",
"name": "test group 2",
"desc": "this is another test group"
}
]
}
}
And then you could just do:
myApps['someId'].name = 'This is my new Name'
Check it out here:
http://jsfiddle.net/qLTB7/40/
it should be if (object["id"] == value) instead of if (object[x] == value) in 7th line of PitaJ answer, so whole function will look like:
function findAndReplace(object, value, replacevalue) {
for (var x in object) {
if (object.hasOwnProperty(x)) {
if (typeof object[x] == 'object') {
findAndReplace(object[x], value, replacevalue);
}
if (object["id"] == value) {
object["name"] = replacevalue;
// break; // uncomment to stop after first replacement
}
}
}
}
if you leave object[x] - function will replace name also for objects with other keys values set to "test1", for example
{"id": "xxx", "name": "test group 1", "desc": "test1"}
I have an array of objects, like this:
var companies = [
{ "name" : "Company 1",
"logo" : "/logo.gif" },
{ "name" : "Company 2",
"logo" : "/logo2.gif" },
{ "name" : "Company 3",
"logo" : "/logo3.gif" } ];
I want to filter this array to get only values which have a name which exists in another array:
var myCompanies = [ "Company 1", "Company 3" ];
In this example, the data to be returned would be:
var companies = [
{ "name" : "Company 1",
"logo" : "/logo.gif" },
{ "name" : "Company 3",
"logo" : "/logo3.gif" } ];
What's the best way to do this?
You can use $.grep() to get a new, filtered array, like this
var result = $.grep(companies, function(e) {
return $.inArray(e.name, myCompanies) != -1;
});
You can test it here. Note that this performs much better than a $.each() loop, you can test it here: http://jsperf.com/each-vs-grep
By loop only..
var newArray = [];
$.each(companies, function(){
if($.inArray(this.name, myCompanies) !== -1) newArray.push(this);
});
jQuery utilies are used here: jQuery.each() and jQuery.inArray()
This should to the job:
companies = $.map(companies,function(element){
return ($.inArray(element.name,myCompanies)>-1?element:null)
}