Sort javascript array of objects on subobject key - javascript

I have an array of objects containing sub-objects like this;
var array=[
{
"EmployeeName": "John",
"Experience": "12",
"data":{
"Technology":"SharePoint"
}
},
{
"EmployeeName": "Charles",
"Experience": "9",
"data":{
"Technology":"ASPNET"
}
},
{
"EmployeeName": "Jo",
"Experience": "3",
"data":{
"Technology":"PHP"
}
},
{
"EmployeeName": "Daine",
"Experience": "7",
"data":{
"Technology":"javascript"
}
},
{
"EmployeeName": "Zain",
"Experience": "6",
"data":{
"Technology":"Unity"
}
}
];
Now, I want to sort this array based on the sub-objects key "Technology". I am using the example from here: https://jsfiddle.net/KSPriyaranjan/4Lzr0qux
This does the job when it sorts from a base object, but how do I get this to work:
function GetSortOrder(prop){
return function(a,b){
if( a[prop] > b[prop]){
return 1;
}else if( a[prop] < b[prop] ){
return -1;
}
return 0;
}
}
array.sort( GetSortOrder("data.Technology") );
document.write("<br><br> Sorted Technology Names : <br>");
for (var item in array) {
document.write("<br>"+array[item].Technology);
}
This produces this:
Sorted Technology Names :
undefined
undefined
undefined
undefined
Hope someone can help in this matter and thanks in advance :-)
undefined

If the property passed contains any .s, use reduce to navigate to the last nested value before comparison:
const GetSortOrder = (accessor) => {
const getVal = accessor.includes('.')
? outer => accessor.split('.').reduce((a, prop) => a[prop], outer)
: outer => outer;
return (a, b) => getVal(a).localeCompare(getVal(b));
};
var array=[
{
"EmployeeName": "John",
"Experience": "12",
"data":{
"Technology":"SharePoint"
}
},
{
"EmployeeName": "Charles",
"Experience": "9",
"data":{
"Technology":"ASPNET"
}
},
{
"EmployeeName": "Jo",
"Experience": "3",
"data":{
"Technology":"PHP"
}
},
{
"EmployeeName": "Daine",
"Experience": "7",
"data":{
"Technology":"javascript"
}
},
{
"EmployeeName": "Zain",
"Experience": "6",
"data":{
"Technology":"Unity"
}
}
];
const GetSortOrder = (accessor) => {
const getVal = accessor.includes('.')
? outer => accessor.split('.').reduce((a, prop) => a[prop], outer)
: outer => outer;
return (a, b) => getVal(a).localeCompare(getVal(b));
};
array.sort( GetSortOrder("data.Technology") );
console.log(array);
The conditional isn't strictly necessary (though you may consider it to make the intent clearer), you could also do
const getVal = outer => accessor.split('.').reduce((a, prop) => a[prop], outer)

Technology is not a property of your object. It is a property of your object.data.
Try changing
javascript array[item].Technology
to
array[item].data.Technology

You could add a function for getting the value by keeping your commparing approach.
function GetSortOrder(prop) {
const
keys = prop.split('.'),
getValue = object => keys.reduce((o, k) => o[k], object);
return function(a, b) {
const
left = getValue(a),
right = getValue(b);
if (left > right) return 1;
else if (left < right) return -1;
return 0;
}
}
var array = [{ EmployeeName: "John", Experience: "12", data: { Technology: "SharePoint" } }, { EmployeeName: "Charles", Experience: "9", data: { Technology: "ASPNET" } }, { EmployeeName: "Jo", Experience: "3", data: { Technology: "PHP" } }, { EmployeeName: "Daine", Experience: "7", data: { Technology: "javascript" } }, { EmployeeName: "Zain", Experience: "6", data: { Technology: "Unity" } }];
array.sort(GetSortOrder("data.Technology"));
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Return additional properties using reduce and spread operator JS?

I am using the reduce function below to count how many times a players name is mentioned and then list them based on who was mentioned the most to the least.
I am trying to return the 2nd property [`${value.subtitles[0].name} + ${index}`] : value.subtitles[0].url with my object and sort it. However it is not sorting properly. When only returning the first property [value.title]: (acc[value.title] || 0) + 1, everything works as intended. But the second property is making it sort incorrectly. It is supposed to be sorting based on the title property value which is an integer of how many times that player was mentioned, from most to least. Why is this happening?
Thanks for the help!
const players = [
{
"title": "Mike",
"titleUrl": "https://mikegameplay",
"subtitles": [
{
"name": "Mike Channel",
"url": "https://channel/mike"
}
]
},
{
"title": "Cindy",
"titleUrl": "https://cindy",
"subtitles": [
{
"name": "Cindy Channel",
"url": "https://channel/cindy"
}
]
},
{
"title": "Mike",
"titleUrl": "https://mike",
"subtitles": [
{
"name": "Mike Channel",
"url": "https://channel/mike"
}
]
},
{
"title": "Haley",
"titleUrl": "https://Haley",
"subtitles": [
{
"name": "Haley Channel",
"url": "https://channel/haley"
}
]
},
{
"title": "Haley",
"titleUrl": "https://Haley",
"subtitles": [
{
"name": "Haley Channel",
"url": "https://channel/haley"
}
]
},
{
"title": "Haley",
"titleUrl": "https://Haley",
"subtitles": [
{
"name": "Haley Channel",
"url": "https://channel/haley"
}
]
}
]
const counts = players.reduce((acc, value, index) => ({
...acc,
[value.title]: (acc[value.title] || 0) + 1,
[`${value.subtitles[0].name} + ${index}`] : value.subtitles[0].url
}), {});
const sortedValues = [];
for (const value in counts) {
sortedValues.push([value, counts[value]]);
};
sortedValues.sort((a, b) => b[1] - a[1]);
console.log(sortedValues)
try this
var groupBy = function (xs, key) {
return xs.reduce(function (rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
var pl = groupBy(players, "title");
console.log(pl);
let sortable = [];
for (var item in pl) {
sortable.push([item, pl[item].length, pl[item][0].subtitles[0].url]);
}
sortable.sort(function (a, b) {
return b[1] - a[1];
});
console.log(JSON.stringify(sortable));
result
[["Haley",3,"https://channel/haley"],["Mike",2,"https://channel/mike"],["Cindy",1,"https://channel/cindy"]]

How in JS to merge in one object two json objects where the ID of on object correspond on the same ID of the second object

My question relates to the fact I'm querying 2 different objects from DB and the result is in JSON. I need to merge them into one.
The 2 objects have in common this two key/value IRBId = ... and id = ... and they look as an example
OBJ 1
{
"data":{
"IRBs":{
"nodes":[
{
"id":"8",
"name":"Admin ",
},
{
"id":"9",
"name":"Again",
}
],
}
}
}
OBJ 2
{
"data":{
"informedConsentForms":{
"count":3,
"nodes":[
{
"id":"93",
...
"IRBId":"9",
},
{
"id":"92",
...
"IRBId":"8",
},
{
"id":"91",
...
"IRBId":"8",
}
],
}
},
As you will see above OBJ 2 and OBJ 1 corresponding with the same at IRBid and id.
What I need is to merge the two OBJ where IRBId OBJ 2 === id OBJ 1
The result I would expect after the merge is
OBJ merged
{
[{
"id":"93",
...
"IRBId":"9",
"irb": {
"name":"Again ",
...
}
},
{
"id":"92",
...
"IRBId":"8",
"irb": {
"name":"Admin ",
...
}
},
{
"id":"91",
...
"IRBId":"8",
"irb": {
"name":"Admin ",
...
}
],
},
I don't know how to make it looks like this.
Try using Array.reduce
Logic
Loop through second object data nodes
Find the matching nodes from object 1 data nodes.
Push to accumulator with required details. (I have added only the nodes that was mentioned in in Expected resut, you can add asmuch as you need.)
const obj1 = {
"data": {
"IRBs": {
"nodes": [
{
"id": "8",
"name": "Admin ",
},
{
"id": "9",
"name": "Again",
}
],
}
}
}
const obj2 = {
"data": {
"informedConsentForms": {
"count": 3,
"nodes": [
{
"id": "93",
"IRBId": "9",
},
{
"id": "92",
"IRBId": "8",
},
{
"id": "91",
"IRBId": "8",
}
],
}
},
};
const obj1List = obj1.data.IRBs.nodes;
const output = obj2.data.informedConsentForms.nodes.reduce((acc, curr) => {
const matchingNode = obj1List.find((item) => item.id === curr.IRBId);
if (matchingNode) {
acc.push({
id: curr.id,
IRBId: curr.IRBId,
irb: {
name: matchingNode.name
}
})
}
return acc;
}, []);
console.log(output);
You need to use the map function on the nodes in the first object to construct a new object that contains the second and first object's attributes.
const obj1 = {
"data": {
"IRBs": {
"nodes": [{
"id": "8",
"obj1": "one",
"name": "Admin ",
},
{
"id": "9",
"obj1": "two",
"name": "Again",
}
]
}
}
};
const obj2 = {
"data": {
"informedConsentForms": {
"count": 3,
"nodes": [{
"id": "93",
"obj2": "1",
"IRBId": "9",
},
{
"id": "92",
"obj2": "2",
"IRBId": "8",
},
{
"id": "91",
"obj2": "3",
"IRBId": "8",
}
],
}
}
};
const obj1Data = obj1.data.IRBs.nodes;
const obj2Data = obj2.data.informedConsentForms.nodes;
const res = obj2Data.map(item => {
const obj1Item = obj1Data.find(obj1Item => item.IRBId === obj1Item.id);
return obj1Item ? { ...item, "irb": { ...obj1Item}} : { ...item};
});
console.log(res);
i am using nested loop, try this one
const obj2 = {
"data":{
"informedConsentForms":{
"count":3,
"nodes":[
{
"id":"93",
"IRBId":"9",
},
{
"id":"92",
"IRBId":"8",
},
{
"id":"91",
"IRBId":"8",
}
],
}
},
}
const obj1 = {
"data":{
"IRBs":{
"nodes":[
{
"id":"8",
"name":"Admin ",
},
{
"id":"9",
"name":"Again",
}
],
}
}
}
const result = [];
const obj2Nodes = obj2.data.informedConsentForms.nodes;
for(let i = 0; i < obj2Nodes.length; i++) {
const obj1Nodes = obj1.data.IRBs.nodes
for(let j = 0; j < obj1Nodes.length; j++) {
if(obj2Nodes[i].IRBId === obj1Nodes[j].id) {
const {id, ...reObj1Nodes} = obj1Nodes[j];
result.push({
...obj2Nodes[i],
'irb': {
...reObj1Nodes
}
})
}
}
}
console.log(result)

sort an array of objects based on previous and next id

I have an array of task objects containing previous and next task ids. I need to display an array of tasks which are sorted based on previous and next task ids. For example:
My input task array is
[
{
"taskId": "101",
"previousTaskId":"0",
"nextTaskId":"102"
},
{
"taskId": "103",
"previousTaskId":"102",
"nextTaskId":"0"
},
{
"taskId": "102",
"previousTaskId":"101",
"nextTaskId":"103"
}
]
My output task array is:
[
{
"taskId": "101",
"previousTaskId":"0",
"nextTaskId":"102"
},
{
"taskId": "102",
"previousTaskId":"101",
"nextTaskId":"103"
},
{
"taskId": "103",
"previousTaskId":"102",
"nextTaskId":"0"
}
]
Is there any way to implement it using es6 methods? I am trying to use reduce and map functionality
const sortedTasks = tasks.reduce((acc, task) => {
let {taskId, previousTaskId, nextTaskId} = task;
return {...acc, task.map(function(item){
if(item.previousTaskId === 0) //first position
//how to compare with another taskId and assign it to acc ?
})
};
}, {});
You could build a reference from each predecessor to the node and build the result from zero.
function sort(array) {
const
temp = data.reduce((t, o) => {
t[o.previousTaskId] = o;
return t;
}, {}),
result = [];
let p = '0';
while (temp[p]) {
result.push(temp[p]);
p = temp[p]?.taskId;
}
return result;
}
const
data = [{ taskId: "101", previousTaskId: "0", nextTaskId: "102" }, { taskId: "103", previousTaskId: "102", nextTaskId: "0" }, { taskId: "102", previousTaskId: "101", nextTaskId: "103" }];
console.log(sort(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Is there a simple way to transform this JSON back and forth?

We are using Postman for our API testing. Some object we are getting back are very verbose and not easy to handle, so I want to create a helper method to make them a bit more concise. I know there are all kind of transformation libraries like node-json-transform, selecttransform, jsontransforms, etc., but unfortunately I can only use the Postman Sandbox libraries and vanilla JS.
I am looking for the simplest (least amount of loc and functions) way to transform this object:
var verbose = [
{
"Key": "Name",
"Value": "John Doe",
"Instance": 1
},
{
"Key": "Age",
"Value": "33",
"Instance": 1
},
{
"Key": "Child",
"Value": "Jane",
"Instance": 1
},
{
"Key": "Child",
"Value": "Rocky",
"Instance": 2
}];
into this:
var concise = {
"Name": "John Doe",
"Age": "33",
"Child": ["Jane", "Rocky"]
};
and back again into the verbose form.
I already tried the native way of foreach-ing over each object and adding properties/values to a new object, but it went ugly soon when I reached the multiple instance key/value pairs. I can imagine there is an easier way using map/reduce but I am unfamiliar with those methods.
Based on how I've understood your question, you want to create key-value pairs from your verbose array of objects. However, if there are key clashes, then the values should be converted into an array.
With that in mind, you will have to:
Use forEach to loop through your array of objects.
If key does not clash, we simply create a new key-value pair
If key clashes, then it gets a bit tricky:
If key clashes and this is the first occurrence, we convert the value in the key-value pair into an array
If key clashes and this is not the first occurrence, we know we are looking at an array
Now we definitely has an array, so we push our value into it
See proof-of-concept below:
var verbose = [{
"Key": "Name",
"Value": "John Doe",
"Instance": 1
},
{
"Key": "Age",
"Value": "33",
"Instance": 1
},
{
"Key": "Child",
"Value": "Jane",
"Instance": 1
},
{
"Key": "Child",
"Value": "Rocky",
"Instance": 2
}];
var concise = {};
verbose.forEach(function(i) {
var key = i['Key'];
var value = i['Value'];
// If item exists, we want to convert the value into an array of values
if (key in concise) {
var item = concise[key];
// If it is not an array already, we convert it to an array
if (!Array.isArray(item))
item = [item];
item.push(value);
concise[key] = item;
}
// If item does not exist, we simply create a new key-value pair
else {
concise[key] = value;
}
});
console.log(concise);
Here, I assume all attributes are multivalued, then I reduce those that have length 1 to a simple value. This is a bit slower than the reverse approach, where you assume values are singlevalued and promote them to arrays when they prove otherwise, in order to respect the ordering imposed by Instance.
function makeConcise(verbose) {
let concise = {};
verbose.forEach(({Key, Value, Instance}) => {
if (!concise[Key]) concise[Key] = [];
concise[Key][Instance - 1] = Value;
});
Object.keys(concise).forEach(Key => {
if (concise[Key].length == 1) concise[Key] = concise[Key][0];
});
return concise;
}
The reverse function is similarly simple:
function makeVerbose(concise) {
let verbose = [];
Object.keys(concise).forEach(Key => {
if (Array.isArray(concise[Key])) {
concise[Key].forEach((Value, index) => {
verbose.push({Key, Value, Instance: index + 1});
});
} else {
verbose.push({Key, Value: concise[Key], Instance: 1});
}
});
return verbose;
}
const verbose = [{
"Key": "Name",
"Value": "John Doe",
"Instance": 1
},
{
"Key": "Age",
"Value": "33",
"Instance": 1
},
{
"Key": "Child",
"Value": "Jane",
"Instance": 1
},
{
"Key": "Child",
"Value": "Rocky",
"Instance": 2
}
];
let concise = {};
verbose.forEach(item => {
const values = Object.values(item)
if (concise[values[0]]) concise = {...concise, [values[0]]: [concise[values[0]], values[1]]};
else concise = {...concise, ...{[values[0]]: values[1]}}
})
Try this. I have written both conversion functions.
I see other answers only provide only verbose to concise requirement.
let verbose = [{
"Key": "Name",
"Value": "John Doe",
"Instance": 1
},
{
"Key": "Age",
"Value": "33",
"Instance": 1
},
{
"Key": "Child",
"Value": "Jane",
"Instance": 1
},
{
"Key": "Child",
"Value": "Rocky",
"Instance": 2
}
]
let concise = {
"Name": "John Doe",
"Age": "33",
"Child": ["Jane", "Rocky"]
}
verboseToConcise = (verbose) => {
let obj = {}
verbose.forEach(v => {
let key = obj[v.Key]
if (key) typeof key === 'string' ? obj[v.Key] = [key, v.Value] : key.push(v.Value)
else obj[v.Key] = v.Value
})
return obj
}
conciseToVerbose = (concise) => {
let arr = []
Object.entries(concise).forEach(([key, value]) => {
if (typeof value === 'object') {
for (let i = 0; i < value.length; i++){
arr.push({
"Key": key,
"Value": value[i],
"Instance": i+1
})
}
} else {
arr.push({
"Key": key,
"Value": value,
"Instance": 1
})
}
})
return arr
}
console.log(verboseToConcise(verbose))
console.log(conciseToVerbose(concise))
You can do:
const verbose = [{"Key": "Name","Value": "John Doe","Instance": 1},{"Key": "Age","Value": "33","Instance": 1},{"Key": "Child","Value": "Jane","Instance": 1},{"Key": "Child","Value": "Rocky","Instance": 2}];
const concise = Object.values(verbose.reduce((a, {Key, Value}) => (Key === 'Child' ? a.childs[0].Child.push(Value) : a.keys.push({[Key]: Value}), a), {keys: [], childs: [{Child: []}]})).flat(1);
console.log(concise);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I also gave it a try using reduce:
EDIT: Without ... spread syntax, with Object.assign and array.concat
EDIT2: I wanted to try and turn it back again. In this code we lose the value of Instance:(
var verbose = [
{
Key: 'Name',
Value: 'John Doe',
Instance: 1,
},
{
Key: 'Age',
Value: '33',
Instance: 1,
},
{
Key: 'Child',
Value: 'Jane',
Instance: 1,
},
{
Key: 'Child',
Value: 'Rocky',
Instance: 2,
},
]
const concise = verbose.reduce(
(p, n) =>
Object.assign(p, {
[n.Key]: !p.hasOwnProperty(n.Key)
? n.Value
: typeof p[n.Key] === 'string'
? [p[n.Key], n.Value]
: p[n.Key].concat(n.Value),
}),
{},
)
console.log(concise)
// { Name: 'John Doe', Age: '33', Child: [ 'Jane', 'Rocky' ] }
const backAgain = Object.entries(concise).reduce(
(p, [k, v]) =>
Array.isArray(v)
? p.concat(v.map(x => ({ Key: k, Value: x })))
: p.concat({ Key: k, Value: v }),
[],
)
console.log(backAgain)
// [ { Key: 'Name', Value: 'John Doe' },
// { Key: 'Age', Value: '33' },
// { Key: 'Child', Value: 'Jane' },
// { Key: 'Child', Value: 'Rocky' } ]

Organise element in a array

i'm searching a smart way to reoganise an array by a element inside it:
In entry i've got:
[{"name": "brevet",
"country": "fr"
},{
"name": "bac",
"country": "fr"
},{
"name": "des",
"country": "ca"
},{
"name": "dep",
"country": "ca"
}{
"name": "other",,
"country": "other"}]
I want to reorganize my array by country to have this in my output:
[{
"name": "fr",
"degrees": [
{
"name": "brevet",
"country": "fr"
},{
"name": "bac",
"country": "fr"
}]
},{
"name": "ca",
"degrees": [{
"name": "des",
"country": "ca"
},{
"name": "dep",
"country": "ca"
}]
},{
"name": "other",
"degrees": [{
"name": "other",
"country": "other"
}]
}]
For this i write a dirty function, but it seems to me there is a better way but i don't see how. If someone can ligth my brain in a better way to do this i'll be helpfull
private organizeDegrees(degrees: Array<SubscriptionFieldInterface>) {
let degreesByCountry = new Array();
let storeIndex = new Array();
degrees.map(degree => {
let index = null;
storeIndex.find((element, idx) => {
if (element === degree.country) {
index = idx;
return true;
}
});
if (index === null) {
index = degreesByCountry.length;
let newEntry = {
'name': degree.country,
'degrees': new Array()
};
storeIndex.push(degree.country);
degreesByCountry.push(newEntry);
}
degreesByCountry[index].degrees.push(degree);
});
return degreesByCountry;
}
thank's
You can group the array and map the object using Object.keys:
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
var grouped = groupBy(array, "country");
var mappedArray = Object.keys(grouped).map(key => ( {name: key, degrees: grouped [key]} ));
And one more way:
arr = [ /* your array */ ];
arr = Object.values(arr.reduce((ac, el) => {
if(!ac[el.country]) ac[el.country] = {"name": el.country, "degrees": []}
ac[el.country].degrees.push(el);
return ac
}, {}))
console.log(arr) // formated
Another solution, which also handles 'id' => '#id' mapping:
const a = [{"name":"brevet","country":"fr"},{"name":"bac","country":"fr"},{"id":73,"name":"des","country":"ca"},{"name":"dep","country":"ca"},{"name":"other","country":"other"}];
const r = [...new Set(a.map(({country}) => country))] // list of unique country names
.map(c => Object.assign({name: c}, // for each country
{degrees: a.filter(x => x.country === c).map(y => Object.keys(y).includes('id') // handle 'id' => '#id' mutation
? {'#id': "/subscription_fields/" + y.id, name: y.name, country: y.country}
: y)
}))
console.log(r)
This is purely ES6, and quite terse, but possibly less readable. Also, it doesn't add the "#id": "/subscription_fields/83", which could be added as a post process:
const groupByKey = (arr, key) => [...arr.reduce((acc, deg) =>
acc.set(deg[key], {name: deg[key], degrees: [ ...(acc.get(deg[key]) || {degrees: []}).degrees, deg]})
, new Map()).values()];
console.log(groupByKey(degrees, 'country'));
You could use a hash table and collect all values in an object. For getting the result array, you need to push the object only once.
var data = [{ name: "brevet", country: "fr" }, { name: "bac", country: "fr" }, { id: 73, name: "des", country: "ca" }, { name: "dep", country: "ca" }, { name: "other", country: "other" }],
result = data.reduce(function (hash) {
return function (r, a) {
if (!hash[a.country]) {
hash[a.country] = { name: a.country, degrees: [] };
r.push(hash[a.country]);
}
hash[a.country].degrees.push({ name: a.name, country: a.country });
return r;
};
}(Object.create(null)), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources