I got stuck on a maybe simple task, but could not find any solution.
I have some JSON Data - lets say:
[{
"_id": 1,
"type": "person",
"Name": "Hans",
"WorksFor": ["3", "4"]
}, {
"_id": 2,
"type": "person",
"Name": "Michael",
"WorksFor": ["3"]
}, {
"_id": 3,
"type": "department",
"Name": "Marketing"
}, {
"_id": 4,
"type": "department",
"Name": "Sales"
}]
As I learned here it is quite simple to get all the persons and the departments they work for together using a map array for the departments.
Then I can map the corresponding department to the Person and receive something like:
[{
"_id": 1,
"type": "person",
"Name": "Hans",
"WorksFor": ["3", "4"],
"Readable": ["Marketing", "Sales"]
}, {
"_id": 2,
"type": "person",
"Name": "Michael",
"WorksFor": ["3"],
"Readable": ["Sales"]
}]
But for another interface I need the data "the other way round" e.g.
[{
"_id": 3,
"type": "department",
"Name": "Marketing",
"employees": [
"Hans", "Michael"
]
}, {
"_id": 4,
"type": "department",
"Name": "Sales",
"employees": [
"Hans"
]
}]
Is there any decent way to achieve this structure? Two days of trying didn't get me anywhere...
You can try something like this:
var data = [{ "_id": 1, "type": "person", "Name": "Hans", "WorksFor": ["3", "4"]}, { "_id": 2, "type": "person", "Name": "Michael", "WorksFor": ["3"]}, { "_id": 3, "type": "department", "Name": "Marketing"}, { "_id": 4, "type": "department", "Name": "Sales"}]
var ignoreDept = ['person'];
var result = data.reduce(function(p,c,i,a){
if(ignoreDept.indexOf(c.type) < 0){
c.employees = a.reduce(function(arr,emp){
if(emp.WorksFor && emp.WorksFor.indexOf(c._id.toString()) > -1){
arr.push(emp.Name)
}
return arr;
},[]);
p.push(c);
}
return p;
}, []);
console.log(result)
The solution using Array.prototype.filter()
and Array.prototype.forEach()
functions:
var data = [{ "_id": 1, "type": "person", "Name": "Hans", "WorksFor": ["3", "4"]}, { "_id": 2, "type": "person", "Name": "Michael", "WorksFor": ["3"]}, { "_id": 3, "type": "department", "Name": "Marketing"}, { "_id": 4, "type": "department", "Name": "Sales"}],
// getting separated "lists" of departments and employees(persons)
deps = data.filter(function(o){ return o.type === "department"; }),
persons = data.filter(function(o){ return o.type === "person"; });
deps.forEach(function (d) {
d['employees'] = d['employees'] || [];
persons.forEach(function (p) {
if (p.WorksFor.indexOf(String(d._id)) !== -1) { // check the `id` coincidence between the employee and the department
d['employees'].push(p.Name);
}
});
});
console.log(deps);
You could use a hash table and a single loop for each array.
Methods:
Array#reduce
for iterating an array and returning the result,
Array#forEach
for looping the inner array WorksFor
,
Object.create(null)
to generate an object without any prototypes,
- some other pattern, like a closure over
hash
and
the use of logical OR ||
for checking a falsy value and taking an object as default.
hash[b] = hash[b] || { _id: b, employees: [] };
var data = [{ _id: 1, type: "person", Name: "Hans", WorksFor: [3, 4] }, { _id: 2, type: "person", Name: "Michael", WorksFor: [3] }, { _id: 3, type: "department", Name: "Marketing" }, { _id: 4, type: "department", Name: "Sales" }],
result = data.reduce(function (hash) {
return function (r, a) {
if (a.type === 'person') {
a.WorksFor.forEach(function (b) {
hash[b] = hash[b] || { _id: b, employees: [] };
hash[b].employees.push(a.Name);
});
}
if (a.type === 'department') {
hash[a._id] = hash[a._id] || { _id: b, employees: [] };
hash[a._id].type = a.type;
hash[a._id].Name = a.Name;
r.push(hash[a._id]);
}
return r;
};
}(Object.create(null)), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a way you can get the first mapping. I've added some comments so you can follow along, and with it I hope you can find the answer to your second problem.
// First, let's get just the items in this array that identify persons
// I've called this array "data"
data.filter(x => x.type === 'person')
// Now let's map over them
.map(person =>
// We want all of the data associated with this person, so let's
// use Object.assign to duplicate that data for us
Object.assign({}, person, {
// In addition, we want to map the ID of the WorksFor array to the Name
// of the corresponding department. Assuming that the _id key is unique,
// we can due this simply by mapping over the WorksFor array and finding
// those values within the original array.
Readable: person.WorksFor.map(wfId =>
// Notice here the parseInt. This will not work without it due to
// the type difference between WorksFor (string) and _id (integer)
data.find(d => d._id === parseInt(wfId)).Name
)
})
);