I have following array of object
let studentArray =
[{
"name" : "Computer Science",
"students" : [
{
"student_name" : "A"
},
{
"student_name" : "B"
}
]
},
{
"name" : "Math",
"students" : [
{
"student_name" : "A"
},
{
"student_name" : "B"
},
{
"student_name" : "C"
}
]
}]
and I want answer like below.
[
{
"student_name" : "A",
"courses": ["Computer Science", "Math"]
},
{
"student_name" : "B",
"courses": ["Computer Science", "Math"]
},
{
"student_name" : "C",
"courses": ["Math"]
}
]
Please help with javascript functionality and according to data structure algorithm.
I have tried below it is not working.
I there any another way to doing this Using different another loops or something another logic for that.
let studentArray = [{
"name": "Computer Science",
"students": [{
"student_name": "A"
},
{
"student_name": "B"
}
]
},
{
"name": "Math",
"students": [{
"student_name": "A"
},
{
"student_name": "B"
},
{
"student_name": "C"
}
]
}
]
studentArray.forEach((item, index) => {
//console.log(item);
if (index > 0) {
console.log("Previous: " + studentArray[index - 1].students);
}
if (index < studentArray.length - 1) {
console.log("Next: " + studentArray[index + 1].students);
}
//console.log(studentArray);
console.log(item.students.filter(comparer(item.students)));
});
function comparer(otherArray) {
return function(current) {
return otherArray.filter(function(other) {
return other.value == current.value && other.display == current.display
}).length == 0;
}
}
You can use Array.reduce() on the studentArray to group students with their courses.
We create an object keyed by student name and iterate over each course's student array to add students to the map (using for...each).
Finally, we use Object.values() to turn our map into an array:
const studentArray = [{ "name" : "Computer Science", "students" : [ { "student_name" : "A" }, { "student_name" : "B" } ] }, { "name" : "Math", "students" : [ { "student_name" : "A" }, { "student_name" : "B" }, { "student_name" : "C" } ] }];
const result = Object.values(studentArray.reduce((acc, course) => {
for(let student of course.students) {
let student_name = student.student_name;
acc[student_name ] = acc[student_name ] || { student_name , courses: []};
acc[student_name ].courses.push(course.name);
}
return acc;
}, {}))
console.log(result)
Use a nested forEach loop
const studentArray = [{
name: "Computer Science",
students: [{
student_name: "A"
},
{
student_name: "B"
}
]
},
{
name: "Math",
students: [{
student_name: "A"
},
{
student_name: "B"
},
{
student_name: "C"
}
]
}
];
const newArr = [];
studentArray.forEach((c) => {
c.students.forEach((s) => {
let studentIndex = newArr.findIndex(el => el.student_name === s.student_name);
studentIndex === -1 ? newArr.push({
student_name: s.student_name,
courses: [c.name]
}) : newArr[studentIndex].courses.push(c.name)
})
})
console.log(newArr);
Another approach using reduce, map, and some ES6 spread syntax:
const courses = [
{
"name" : "Computer Science",
"students" : [{ "student_name" : "A" }, { "student_name" : "B" }]
},
{
"name" : "Math",
"students" : [{ "student_name" : "A" }, { "student_name" : "B" }, { "student_name" : "C" }]
}
]
// Add students from a course to an array if they're not present already
const selectUniqueStudents = (currentStudentList, course) =>
currentStudentList.concat(course.students.filter(newStudent =>
currentStudentList.every(
currentStudent => currentStudent.student_name !== newStudent.student_name
)
))
// Add each course that the student is on to an array and append to the
// student object
const addCourseDetails = (student) => ({
...student,
courses: courses
.filter(course =>
course.students.some(courseStudent => courseStudent.student_name === student.student_name)
)
.map(course => course.name)
})
const transformedResult = courses
.reduce(selectUniqueStudents, [])
.map(addCourseDetails)
console.log(transformedResult)
// Returns:
//
// [
// { student_name: 'A', courses: [ 'Computer Science', 'Math' ] },
// { student_name: 'B', courses: [ 'Computer Science', 'Math' ] },
// { student_name: 'C', courses: [ 'Math' ] }
// ]
Same as with Vineet's answer, Terry's would run faster. This is not as concise or easy to read either. But the demonstration of aggregate array functions and ES6 syntax might be useful.
I want to remove array which is inside of whole array if an object of its array has certain value. I searched the web and I saw removing object or array if it has certain value and I can't find that solves my problem.
I have objects of array which is again wrapped by array. It looks like below:
"products": [
[
{
"product.name": "A",
"remark.name": "Good"
},
{
"product.name": "B",
"remark.name": "Good"
}
],
[
{
"product.name": "A",
"remark.name": "Bad"
},
{
"product.name": "B",
"remark.name": "Good"
}
]
]
What I want
I want to omit the whole array which contains at least remark.name === Bad
So, I should get the final result like below.
"products": [
[
{
"product.name": "A",
"remark.name": "Good"
},
{
"product.name": "B",
"remark.name": "Good"
}
]
]
What I've tried
Below code
let result = [];
products.map((product) => {
var res = _.remove(product, function (n) {
return n["remark.name"] === "Fail";
});
result.push(res);
});
produces following result:
"products": [
[
{
"product.name": "A",
"remark.name": "Good"
},
{
"product.name": "B",
"remark.name": "Good"
}
],
[
{
"product.name": "B",
"remark.name": "Good"
}
]
]
Use Array#filter for filtering and as filter Array#every where remark.name is different from 'Bad'.
Note: Use single or double quotes for propertyname 'remark.name' because of the dot in it`s name.
let products = [
[
{
"product.name": "A",
"remark.name": "Good"
},
{
"product.name": "B",
"remark.name": "Good"
}
],
[
{
"product.name": "A",
"remark.name": "Bad"
},
{
"product.name": "B",
"remark.name": "Good"
}
]
];
let filtered = products.filter(arr => arr.every(obj => obj['remark.name'] !== 'Bad'));
console.log(filtered);
I want to parse json loaded from file const notCleanData = JSON.parse(fs.readFileSync('db.json')); to be able to export to CSV using json2csv. I loaded the file and learn how to export, but I can't figure out how to clean JSON from unnecessary part of JSON, cos it's making CSV to be exported in a wrong way. Instead having data from array in separate columns, I get all data under one column with "group" as header. How to convert A.json to B.json for exporting clean JSON to CSV?
A.json
{
"group" : [
{
"A" : "1",
"B" : "2"
},
{
"A" : "3",
"B" : "4"
}
],
"profile" : {
"C" : "5"
}
}
B.json
{
"A" : "1",
"B" : "2"
},
{
"A" : "3",
"B" : "4"
}
In short: How to extract data only from "group" and add it to variable?
You can use jpath for that:
let A = {
"group" : [
{
"A" : "1",
"B" : "2"
},
{
"A" : "3",
"B" : "4"
}
],
"profile" : {
"C" : "5"
}
}
let jp = require('jsonpath');
let B = jp.query(A, '$.group');
console.log(B)
Output:
[ [ { A: '1', B: '2' }, { A: '3', B: '4' } ] ]
Today I run into a situation I need to sync a mongoDB collection to vertica (SQL Database) where my object keys will be the columns of the table in SQL.
I use mongoDB aggregation framework, first to query, manipulate and project the wanted result document and then I sync it to vertica.
The schema I want to aggregate looks like this:
{
userId: 123
firstProperty: {
firstArray: ['x','y','z'],
anotherAttr: 'abc'
},
anotherProperty: {
secondArray: ['a','b','c'],
anotherAttr: 'def'
}
}
Since array values are not related with other arrays value, what I need is that each value of nested array, will be in a separate result document.
For that I use the following aggregation pipe:
db.collection('myCollection').aggregate([
{
$match: {
$or: [
{'firstProperty.firstArray.1': {$exists: true}},
{'secondProperty.secondArray.1': {$exists: true}}
]
}
},
{
$project: {
userId: 1,
firstProperty: 1,
secondProperty: 1
}
}, {
$unwind: {path:'$firstProperty.firstAray'}
}, {
$unwind: {path:'$secondProperty.secondArray'},
}, {
$project: {
userId: 1,
firstProperty: '$firstProperty.firstArray',
firstPropertyAttr: '$firstProperty.anotherAttr',
secondProperty: '$secondProperty.secondArray',
seondPropertyAttr: '$secondProperty.anotherAttr'
}
}, {
$out: 'another_collection'
}
])
What I expect is the following result:
{
userId: 'x1',
firstProperty: 'x',
firstPropertyAttr: 'a'
}
{
userId: 'x1',
firstProperty: 'y',
firstPropertyAttr: 'a'
}
{
userId: 'x1',
firstProperty: 'z',
firstPropertyAttr: 'a'
}
{
userId: 'x1',
secondProperty: 'a',
firstPropertyAttr: 'b'
}
{
userId: 'x1',
secondProperty: 'b',
firstPropertyAttr: 'b'
}
{
userId: 'x1',
secondProperty: 'c',
firstPropertyAttr: 'b'
}
Instead I get something like that:
{
userId: 'x1',
firstProperty: 'x',
firstPropertyAttr: 'b'
secondProperty: 'a',
secondPropertyAttr: 'b'
}
{
userId: 'x1',
firstProperty: 'y',
firstPropertyAttr: 'b'
secondProperty: 'b',
secondPropertyAttr: 'b'
}
{
userId: 'x1',
firstProperty: 'z',
firstPropertyAttr: 'b'
secondProperty: 'c',
secondPropertyAttr: 'b'
}
What exactly am I missing, and how can I fix it?
This is actually a much "curlier" problem than you might think it is, and it all really boils down to "named keys", which are generally a real problem and your data "should" not be using "data points" in the naming of such keys.
The other obvious problem in your attempt is called a "cartesian product". This is where you $unwind one array and then $unwind another, which results in the items from the "first" $unwind being repeated for every value present in the "second".
Addressing that second problem, the basic approach is to "combine the arrays" in order that you only $unwind from a single source. This is pretty common to all remaining approaches.
As for the approaches, these differ in the MongoDB version you have available and the general practicality of application. So let's step through them:
Remove the named keys
The most simple approach here is to simply not expect named keys in the output, and instead mark them as a "name" identifying their source in the final output. So all we want to do is specify each "expected" key within the construction of an initial "combined" array, and then simply $filter that for any null values resulting from named paths not existing in the present document.
db.getCollection('myCollection').aggregate([
{ "$match": {
"$or": [
{ "firstProperty.firstArray.0": { "$exists": true } },
{ "anotherProperty.secondArray.0": { "$exists": true } }
]
}},
{ "$project": {
"_id": 0,
"userId": 1,
"combined": {
"$filter": {
"input": [
{
"name": { "$literal": "first" },
"array": "$firstProperty.firstArray",
"attr": "$firstProperty.anotherAttr"
},
{
"name": { "$literal": "another" },
"array": "$anotherProperty.secondArray",
"attr": "$anotherProperty.anotherAttr"
}
],
"cond": {
"$ne": ["$$this.array", null ]
}
}
}
}},
{ "$unwind": "$combined" },
{ "$unwind": "$combined.array" },
{ "$project": {
"userId": 1,
"name": "$combined.name",
"value": "$combined.array",
"attr": "$combined.attr"
}}
])
From the data included in your question this would produce:
/* 1 */
{
"userId" : 123.0,
"name" : "first",
"value" : "x",
"attr" : "abc"
}
/* 2 */
{
"userId" : 123.0,
"name" : "first",
"value" : "y",
"attr" : "abc"
}
/* 3 */
{
"userId" : 123.0,
"name" : "first",
"value" : "z",
"attr" : "abc"
}
/* 4 */
{
"userId" : 123.0,
"name" : "another",
"value" : "a",
"attr" : "def"
}
/* 5 */
{
"userId" : 123.0,
"name" : "another",
"value" : "b",
"attr" : "def"
}
/* 6 */
{
"userId" : 123.0,
"name" : "another",
"value" : "c",
"attr" : "def"
}
Merge Objects - Requires MongoDB 3.4.4 minimum
To actually use "named keys" we need the $objectToArray and $arrayToObject operators that were only available since MongoDB 3.4.4. Using these and the $replaceRoot pipeline stage we can simply process to your desired output without explicitly naming the keys to output at any stage:
db.getCollection('myCollection').aggregate([
{ "$match": {
"$or": [
{ "firstProperty.firstArray.0": { "$exists": true } },
{ "anotherProperty.secondArray.0": { "$exists": true } }
]
}},
{ "$project": {
"_id": 0,
"userId": 1,
"data": {
"$reduce": {
"input": {
"$map": {
"input": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$not": { "$in": [ "$$this.k", ["_id", "userId"] ] } }
}
},
"as": "d",
"in": {
"$let": {
"vars": {
"inner": {
"$map": {
"input": { "$objectToArray": "$$d.v" },
"as": "i",
"in": {
"k": {
"$cond": {
"if": { "$ne": [{ "$indexOfCP": ["$$i.k", "Array"] }, -1] },
"then": "$$d.k",
"else": { "$concat": ["$$d.k", "Attr"] }
}
},
"v": "$$i.v"
}
}
}
},
"in": {
"$map": {
"input": {
"$arrayElemAt": [
"$$inner.v",
{ "$indexOfArray": ["$$inner.k", "$$d.k"] }
]
},
"as": "v",
"in": {
"$arrayToObject": [[
{ "k": "$$d.k", "v": "$$v" },
{
"k": { "$concat": ["$$d.k", "Attr"] },
"v": {
"$arrayElemAt": [
"$$inner.v",
{ "$indexOfArray": ["$$inner.k", { "$concat": ["$$d.k", "Attr"] }] }
]
}
}
]]
}
}
}
}
}
}
},
"initialValue": [],
"in": { "$concatArrays": [ "$$value", "$$this" ] }
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": [
[{ "k": "userId", "v": "$userId" }],
{ "$objectToArray": "$data" }
]
}
}
}}
])
Which gets pretty monstrous from converting the "keys" into an array, then the "sub-keys" into an array and mapping the values from those inner arrays onto the pair of keys in output.
The key parts being $objectToArray is essentially needed to "transform" your "nested key" structures into arrays of "k" and "v" representing the "name" of the key and the "value". This gets called twice, being once for the "outer" parts of the document and excluding the "constant" fields such as "_id" and "userId" into such an array structure. Then the second call is processed on each of those "array" elements in order to make those "inner keys" a similar "array".
Matching is then done using $indexOfCP to work out which "inner key" is the one for the value and which is the "Attr". The keys are then renamed here to the "outer" key value, which we can access because that's a "v" courtesy of $objectToArray.
Then for the "inner value" which is an "array", we want to $map each entry into a combined "array" which basically has the form:
[
{ "k": "firstProperty", "v": "x" },
{ "k": "firstPropertyAttr", "v": "abc" }
]
This happens for each "inner array" element, for which $arrayToObject reverses the process and turns each "k" and "v" into "key" and "value" of an object respectively.
Since the output is still an "array of arrays" of the "inner keys" at this point, the $reduce wraps that output and applies $concatArrays while processing each element in order to "join" into a single array for "data".
All that remains is to simply $unwind the array produced from each source document, and then apply $replaceRoot, which is the part that actually allows "different key names" at the "root" of each document output.
The "merging" here is done by supplying an array of object of the same "k" and "v" construction notated for "userId", and "concatentating" that with the $objectToArray transform of the "data". Of course this "new array" is then converted to an object via $arrayToObject one final time, which forms the "object" argument to "newRoot" as an expression.
You do something like that when there is a large number of "named keys" that you can't really name explicitly. And it actually gives you the result you want:
/* 1 */
{
"userId" : 123.0,
"firstProperty" : "x",
"firstPropertyAttr" : "abc"
}
/* 2 */
{
"userId" : 123.0,
"firstProperty" : "y",
"firstPropertyAttr" : "abc"
}
/* 3 */
{
"userId" : 123.0,
"firstProperty" : "z",
"firstPropertyAttr" : "abc"
}
/* 4 */
{
"userId" : 123.0,
"anotherProperty" : "a",
"anotherPropertyAttr" : "def"
}
/* 5 */
{
"userId" : 123.0,
"anotherProperty" : "b",
"anotherPropertyAttr" : "def"
}
/* 6 */
{
"userId" : 123.0,
"anotherProperty" : "c",
"anotherPropertyAttr" : "def"
}
Named Keys without MongoDB 3.4.4 or Greater
Without the operator support as shown in the above listing, it's simply not possible for the aggregation framework to output documents with different key names.
So though it's not possible to instruct the "server" to do this via $out, you can of course simply iterate the cursor and write a new collection
var ops = [];
db.getCollection('myCollection').find().forEach( d => {
ops = ops.concat(Object.keys(d).filter(k => ['_id','userId'].indexOf(k) === -1 )
.map(k =>
d[k][Object.keys(d[k]).find(ki => /Array$/.test(ki))]
.map(v => ({
[k]: v,
[`${k}Attr`]: d[k][Object.keys(d[k]).find(ki => /Attr$/.test(ki))]
}))
)
.reduce((acc,curr) => acc.concat(curr),[])
.map( o => Object.assign({ userId: d.userId },o) )
);
if (ops.length >= 1000) {
db.getCollection("another_collection").insertMany(ops);
ops = [];
}
})
if ( ops.length > 0 ) {
db.getCollection("another_collection").insertMany(ops);
ops = [];
}
Same sort of thing as is being done in the earlier aggregation, but just "externally". It essentially produces and array of documents for each document matching the "inner" arrays, like so:
[
{
"userId" : 123.0,
"firstProperty" : "x",
"firstPropertyAttr" : "abc"
},
{
"userId" : 123.0,
"firstProperty" : "y",
"firstPropertyAttr" : "abc"
},
{
"userId" : 123.0,
"firstProperty" : "z",
"firstPropertyAttr" : "abc"
},
{
"userId" : 123.0,
"anotherProperty" : "a",
"anotherPropertyAttr" : "def"
},
{
"userId" : 123.0,
"anotherProperty" : "b",
"anotherPropertyAttr" : "def"
},
{
"userId" : 123.0,
"anotherProperty" : "c",
"anotherPropertyAttr" : "def"
}
]
These get "cached" into a big array, which when that reaches a length of 1000 or more is finally written to the new collection via .insertMany(). Of course that requires "back and forth" communication with the server, but it does get the job done in the most efficient way possible if you don't have the features available for the previous aggregation.
Conclusion
The overall point here is that unless you actually have a MongoDB that supports it, then you are not going to get documents with "different key names" in the output, solely from the aggregation pipeline.
So when you do not have that support, you either go with the first option and then use $out discarding having named keys. Or you do the final approach and simply manipulate the cursor results and write back to the new collection.
I have got a task to iterate through the complex json file that contains json array. I could not access the array object from the json file.
I need to access the particularly the class-name object from the json
file.
classdetail.json
[ [ { "student" : [
{
"name" : "AAaa",
"class-name" : "A",
"grade-label" : "AA" },
{
"name" : "AAbb",
"class-name" : "A",
"grade-label" : "AB" },
{
"name" : "AAcc",
"class-name" : "A",
"grade-label" : "AB" },
{
"name" : "AAdd",
"class-name" : "B",
"grade-label" : "AA" } ],
"Average" : 2.5 },
{
"student" : [
{
"name" : "BBaa",
"class-name" : "B",
"grade-label" : "AB" },
{
"name" : "BBbb",
"class-name" : "B",
"grade-label" : "AA" },
{
"name" : "BBcc",
"class-name" : "B",
"grade-label" : "AA" },
{
"name" : "BBdd",
"class-name" : "B",
"grade-label" : "AA" } ],
"Average" : 2.5 } ] ]
iterate.js
var fs = require('fs');
var express = require('express');
var http = require('http');
var publicApis;
var item;
var subItem;
classmem = JSON.parse(fs.readFileSync("classdetail.json", "utf8"));
for (item in classmem) {
for (subItem in classmem[item]) {
console.log(classmem[item][subItem]);
}
}
for (item in classmem) {
for (subItem in classmem[item]) {
var student = classmem[item][subItem].student;
for (row in student) {
console.log(student[row]['class-name']);
}
}
}
But read about Array.forEach.
for...in iterates over object properties in arbitrary order. It might not be what you want to use for an array, which stores items in a well-defined order. (though it should work in this case)
Try Array.forEach():
// iterate over `classmem`
classmem.forEach(function(element, index, array) {
// iterate over classmem[index], which is an array too
element.forEach(function(el, idx, arr) {
// classmem[index][idx] contains objects with a `.student` property
el.student.forEach(function(e, i, a) {
console.log(e["name"], e["class-name"], e["grade-label"]);
});
});
});
first check the value is array then access to the "class-name" value
for (item in classmem) {
for (subItem in classmem[item]) {
**if (typeof classmem[item][subItem] === 'object') {
classmem[item][subItem].forEach(function (val, ind) {
console.log(val['class-name']);
});
}**
}
}