JavaScript (Node.js) - JSON recursion extracting objects to array with order (faltten) - arrays

I have a JSON config file as follows:
var conf = [
{
"value": "baz",
"threshold": 20,
"other": 123
},
{
"value": "mo",
"other": 456,
"child": {
"value": "foo",
"other": 789,
"child": {
"value": "larry",
"other": 123
}
}
}
];
I have a requirement to extract each of the objects and persist them together in order if they have child objects. For example, object 1 (baz) is stand alone. Object 2 (mo) will have two child objects. These 3 as a set must be extracted together.
There is no limit to the number of child objects.
Im attempting to persist each object using an array to maintain the order. So the required output would look like:
[[{"value":"baz","threshold":20,"other":123}],
[[{"value":"mo","other":456,"child":{"value":"foo","other":789,"child":{"value":"larry","other":123}}}],
[{"value":"foo","other":789,"child":{"value":"larry","other":123}}],
[{"value":"larry","other":123}]]]
A final requirement is to actually remove the child values from the parents so the output can actually be like:
[
[{"value":"baz","threshold":20,"other":123}],
[
[{"value":"mo","other":456}],
[{"value":"foo","other":789}],
[{"value":"larry","other":123}]
]
]
Ive been hung up on this for hours with little progress. I know I need to create a recursive function, push each node to an array, and then check for child object and repeat.
Heres what I have so far. My thinking is if I can take the array id each task is being pushed to (using a loop id), perhaps I can map that when the function is called again.
Appreciate any guidance.
var execSets = [];
function parser(tasks){
// an ordered array of task execution
for (let eachTask in tasks) {
var taskSet = [];
console.log("====================================");
console.log(tasks[eachTask]);
if(!tasks[eachTask].child && typeof(tasks[eachTask]) === 'object'){
console.log(tasks[eachTask]);
taskSet.push(tasks[eachTask]);
execSets.push(taskSet);
}
if(tasks[eachTask].child){
let childAlias = tasks[eachTask].child;
delete tasks[eachTask].child;
taskSet.push(tasks[eachTask]);
execSets.push(taskSet);
parser(childAlias);
}
}
}

The npm registry is your friend; try 'npm search flat,
There are a few modules that can help flatten a json object. For example https://www.npmjs.com/package/flat

You could do it using recursion. Here is my suggestion:
var conf = [
{
"value": "baz",
"threshold": 20,
"other": 123
},
{
"value": "mo",
"other": 456,
"child": {
"value": "foo",
"other": 789,
"child": {
"value": "larry",
"other": 123
}
}
}
];
function getFlattenedObject(object){
var response = [];
flatten(object, response, 0);
return response;
}
function flatten(object, array, index){
if(!array[index]){
array.push([]);
}
array[index].push(object);
if(object.child){
flatten(object.child, array, index + 1);
object.child = undefined;
}
}
//Logs for comparison
console.dir(conf)
console.dir(getFlattenedObject(conf));

The result structure you are looking for wasn't "intuitive" and hence the solution has gotten a little ugly, but here is how you could use object-scan to answer your question
// const objectScan = require('object-scan');
const data = [{"value":"baz","threshold":20,"other":123},{"value":"mo","other":456,"child":{"value":"foo","other":789,"child":{"value":"larry","other":123}}}]
const fn = (haystack) => objectScan(['[*]', '**.child'], {
filterFn: ({
key: [id, ...p],
value: { child, ...node },
context
}) => {
if (!(id in context)) {
context[id] = [];
}
context[id].push(child || p.length !== 0 ? [node] : node);
}
})(haystack, []);
console.log(fn(data));
// => [ [ { value: 'baz', threshold: 20, other: 123 } ], [ [ { value: 'larry', other: 123 } ], [ { value: 'foo', other: 789 } ], [ { value: 'mo', other: 456 } ] ] ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan

Related

How to push data into an same object based on specific key in typescript

I have an jason result as below and I want to read from it and push to specific key as below
JSON result
[
{id:1,Name:"test",billNumber:"123"}
{id:2,Name:"test1",billNumber:"123"}
{id:3,Name:"test2",billNumber:"12345"}
{id:2,Name:"test3",billNumber:"12345"}
{id:3,Name:"test4",billNumber:"12334535"}
]
I want to have array list as below
{
"123":[{id:1,Name:"test",billNumber:"123"}, {id:2,Name:"test1",billNumber:"123"}],
"12345":[ {id:3,Name:"test2",billNumber:"12345"},{id:2,Name:"test3",billNumber:"12345"}],
"12334535":[{id:3,Name:"test4",billNumber:"12334535"}]
}
How to get the above list from the json result. Please do help
You don't need lodash to do that: just a regular Array.prototype.reduce will do the work. At each iteration, you simply check if the billNumber of the current item is in the object:
if it is not (i.e. a new entry), then you assign an array with a single element
if it is (i.e. the billNumber has been encountered before), then you simply push into the array
See proof-of-concept below:
const data = [{
id: 1,
Name: "test",
billNumber: "123"
}, {
id: 2,
Name: "test1",
billNumber: "123"
}, {
id: 3,
Name: "test2",
billNumber: "12345"
}, {
id: 2,
Name: "test3",
billNumber: "12345"
}, {
id: 3,
Name: "test4",
billNumber: "12334535"
}];
const transformedData = data.reduce((acc, cur) => {
if (cur.billNumber in acc) {
acc[cur.billNumber].push(cur);
} else {
acc[cur.billNumber] = [cur];
}
return acc;
}, {});
console.log(transformedData);
Use groupBy from lodash:
const result = groupBy(input, "billNumber")
(input is your array)
you can use reduce method.
[
{id:1,Name:"test",billNumber:"123"},
{id:2,Name:"test1",billNumber:"123"},
{id:3,Name:"test2",billNumber:"12345"},
{id:2,Name:"test3",billNumber:"12345"},
{id:3,Name:"test4",billNumber:"12334535"},
].reduce((acc, value) => {
if (!acc[value.billNumber]) {
acc[value.billNumber] = [];
}
acc[value.billNumber].push(value);
return acc;
}, {})
Here is the mimic code You cann use and get help
var a = [{a:2},{a:3},{a:4}]
let b = {}
let c = 1
a.forEach(obj => {
b[c] = [obj]
c++
})
output will be
{
1: [ { a: 2 } ],
2: [ { a: 3 } ],
3: [ { a: 4 } ]
}
Thanks I hope it will help !

Convert Flat JSON string into hierarchical using Parent ID

I am implementing a Mat-Tree using Angular Material. I have a flat JSON string like:
"Entity": [
{
"ID": 1,
"NAME": "Reports",
"PARENTID": "0",
"ISACTIVE": "Y",
"CREATIONDATE": "2020-03-31T15:08:11",
"UPDATIONDATE": "2020-03-31T15:08:11",
"CREATEDBY": 596241,
"UPDATEDBY": 596241
},
{
"ID": 2,
"NAME": "TMS - Reports",
"PARENTID": 1,
"ISACTIVE": "Y",
"CREATIONDATE": "2020-03-31T15:08:38",
"UPDATIONDATE": "2020-03-31T15:08:38",
"CREATEDBY": 596241,
"UPDATEDBY": 596241
},
{
"ID": 3,
"NAME": "TMS - Beneficiary ",
"PARENTID": 2,
"ISACTIVE": "Y",
"CREATIONDATE": "2020-03-31T15:09:34",
"UPDATIONDATE": "2020-03-31T15:09:34",
"CREATEDBY": 596241,
"UPDATEDBY": 596241
}
]
And I need to convert it into Key-value pairs based on their Parent ID. Something like:
{
Reports:
{
'Type 1 Reports': ['Beneficiary Reports', 'Some Other Report'],
'Type 2 Reports': null //No Children,
},
Some Other Menu Items: {
'My Parent Node': null,
'Some Other Menu Node': ['Child 1', 'Child 2']
}
}
So far, I am able to use this code to convert it into a parent-child hierarchy, but It has pushed all children into Items array which I cannot iterate with Mat-Tree. I need to get rid of the Items and have something like a key-value pair as the one I mentioned above:
generateTreeData(menuResponse)
{
var map = {};
for(var i = 0; i < menuResponse.length; i++){
var obj = menuResponse[i];
var parent = '';
obj.items= [];
map[obj.ID] = obj;
if(obj.PARENTID == "0")
{
parent = '-';
}
else
{
parent = obj.PARENTID;
}
if(!map[parent]){
//Means Parent doesnt exist i.e. node Itself is parent node
map[parent] = {
items: []
};
}
map[parent].items.push(obj);
}
return map['-'].items;
}
Problem:
The code puts children nodes in Items array. I need to get rid of the Items array and place it in key-value pairs like the one I mentioned above. How do I just extract the "NAME" and Items out of this JSON array and make a Key-Value pair? Something like the one I mentioned above?
The expected output structure doesn't seem right. What if there is a child for 'Beneficiary Reports' node.
The output structure for the input provided in the question should be like:
{
"Reports":[
{
"TMS - Reports":[
{
"TMS - Beneficiary ":[]
}
]
}
]
}
Check the following fiddle: https://jsfiddle.net/abby_7700/apkgeo1d/3/
var data = [];//your response goes here
var result = {};
var root = data.find(x => x.PARENTID == 0);
result[root.NAME] = [];
findAndAddChildren(root.ID, result[root.NAME]);
console.log(JSON.stringify(result));
function findAndAddChildren(parentID, collection) {
var rootChildren = data.filter(x => x.PARENTID == parentID);
for(var i = 0; i < rootChildren.length; i++) {
var child = rootChildren[i];
var temp = {};
temp[child.NAME] = [];
collection.push(temp);
findAndAddChildren(child.ID, temp[child.NAME]);
}
}

Mongoose-MongoDb : doc.pull inconsistent when multiple pull

node v7.7.1
mongodb: 2.2.33,
mongoose: 4.13.7
Hello all,
i'm having this unexpected behaviour when trying to update a document with multiple pull request based on matching criterias. here is what i mean
my document schma looks like this
{
"_id": "5a1c0c37d1c8b6323860dfd0",
"ID": "1511781786844",
"main": {
"_id": "5a3c37bfc065e86a5c593967",
"plan": [
{
"field1": 1,
"field2": 1,
"_id": "5a3c30dfa479bb4b5887e56e",
"child": []
},
{
"field1": 1,
"field2": 2,
"_id": "5a3c30e1a479bb4b5887e5c",
"child": []
},
{
"field1": 1,
"field2": 3,
"_id": "5a3c37bfc065e86a5c593968",
"child": []
},
{
"field1": 1,
"field2": 4,
"_id": "5a3c37bfc065e86a5c593655",
"child": []
},
{
"field1": 1,
"field2": 5,
"_id": "5a3c30dfa479bb4b5887e56f",
"child": []
},
{
"field1": 1,
"field2": 6,
"_id": "5a3c30e1a479bb4b6887e545",
"child": []
},
{
"field1": 1,
"field2": 7,
"_id": "5a3c37bfc065e86a5c5939658",
"child": []
},
{
"field1": 2,
"field2": 2,
"_id": "5a3c37bfc065e86a5c593963",
"child": []
},
]
},
...
....
}
and this is my code to update the document:
Schema.findOne({ID: data.ID})
.then(function(doc) {
var array = doc.main.plan;
for (i = 0; i < array.length; i++) {
if ( array[i].field1=== 1 )) {
var id = array[i]._id;
console.log('pulling');
doc.pull( { _id: id });
}
}
doc.save().then(function(doc) {
console.log('saving');
// console.log(doc);
if (doc && doc.docID) {
return { success: true };
} else {
return { success: false, error: 'unknownError'}
}
})
}
now the issue is let's say my array has 7 objects that matches the test (array[i].theField === parseInt(updFields.theField)), when i run this and check the logs i see that it will basically pull half of the objects and do a save.
so i would get
pulling
pulling
pulling
pulling
save.
and then i have to run the code for the remaining 3 objects in the array and get
pulling
pulling
saving
so i have to run it a third time to completely clear the array.
need help get this working
thank you
So i created a little workaround by doing a recursive function to pull all with only one click using lodash functions. not pretty but it does the job.
const delObjArray = (doc, cond) => {
const checkField = cond.field;
const checkVal = cond.value;
_.forEach(doc, (value) => {
if (value && value[checkField] === checkVal) {
doc.pull({ _id: value._id });
}
});
const isFound = _.some(doc, { [checkField]: checkVal });
if (isFound) {
delObjArray(doc, cond);
} else {
return true;
}
return true;
};

How to filter following JSON with ionic filter bar

Using Ionic Filter Bar for implementing search in ionic app, I need to to search on "parent_type" and "child_type" from following json,
{
"status": {
"code": 200,
"message": "all data grabbed successfully",
"cause": ""
},
"ParentData": [
{
"id": 67,
"parent_type": "Parent Type",
"child": [
{
"id": 86,
"child_type": "Child Type"
},
{
"id": 93,
"child_type": "Child Type1"
}
]
},
{
"id": 68,
"parent_type": "Parent Type 1",
"child": [
{
"id": 87,
"child_type": "Child Type 1"
},
{
"id": 94,
"child_type": "Child Type 2"
}
]
}
]
}
its working fine for "parent_type" with following code
Here is the filter code
$scope.showFilterBar = function () {
filterBarInstance = $ionicFilterBar.show({
//setting parentData in following way after rest service call
//$scope.parentData = jsondata.ParentData
items: $scope.parentData,
update: function (filteredItems, filterText) {
$scope.parentData = filteredItems;
if (filterText) {
console.log(filterText);
}
},
filterProperties: ['parent_type']
});
};
But not able to search on "child_type"
So is there any way to set filterProperties so it will work for both "parent_type" and "child_type" or any other way to search this Json?
Thanks.
You should remove the "filterProperties" key and then add "expression" key.
Your "filterBarInstance" will be something like this:
$scope.showFilterBar = function () {
filterBarInstance = $ionicFilterBar.show({
items: $scope.parentData,
update: function (filteredItems, filterText) {
$scope.parentData = filteredItems;
if (filterText) {
console.log(filterText);
}
},
expression: function(filterText,value,index,array){
// This function is called for every ParentData object.
// "value" will be the current ParentData object,
// you'll need to iterate over all the "child" array in order to
// check if any of the "child_type" keys contains the "filterText"
// so if "parent_type" contains the "filterText" and any(? or all)
// of the the "child[x].child_type" values also contains the
// "filterText" return true
}
});
};
Check https://github.com/djett41/ionic-filter-bar for the original documentation.
Sorry for my english :)

Insert/Update/Push Array in existing Document in mongodb [duplicate]

I have a document like this:
_id: ObjectId("559c1d2ad8291bc9368b4568")
tablename: "IWEO_IWBB"
out_user: "pb"
out_email: "email"
out_date: "15.05.2015"
and want to add array like this:
"inventar": [
{
"ean": "2",
"name": "name2",
"runtime": "0",
"art": "null",
"marker": "null",
"stammkost": "null",
"accepted": "0"
},
{
"ean": "1",
"name": "name1",
"runtime": "0",
"art": "null",
"marker": "null",
"stammkost": "null",
"accepted": "0"
}
],
In my old PHP-Server I used the code below to insert it.
The right command is "update". In node.js it seems to be another command.
foreach($jArray as $value){
//$uuid = uniqid('', true);
$tablename = $value['tablename'];
$ean = $value["ean"];
$runtime = $value["runtime"];
$art = $value["art"];
$marker = $value["marker"];
$stammkost = $value["stammkost"];
$new_data = array(
//array (
'ean' => $ean,
'runtime' => $runtime,
'art' => $art,
'marker' => $marker,
'stammkost' => $stammkost,
'accepted' => '0'
//)
);
try {
$collection->update(array("tablename"=>$tablename),array('$push' => array("inventar" => $new_data)));
echo json_encode($collection);
}
catch ( MongoConnectionException $e ) {
echo '<p>Update failed</p>';
exit();
}
}
In my new node.js I use the code below:
tables.forEach(function(table) {
var tablename = table.tablename;
var name = table.name ;
var ean = table.ean;
var runtime= table.runtime;
var art = table.art;
var marker = table.marker;
var stammkost = table.stammkost;
console.log(tablename+" "+ean+" "+name+" "+runtime+" "+art+" "+marker+" "+stammkost);
OutAccept.update(function (err, data) {
if (err) console.log(err);
else {
console.log(data);
}
});
response.end();
//}
});
});
The output in console is:
IWEO_IWBB_01062015 1 name1 11337 A null null
{ ok: 0, n: 0, nModified: 0 }
IWEO_IWBB_01062015 2 name2 A null null
{ ok: 0, n: 0, nModified: 0 }
Why it isnt updated/inserted? Its the wrong command?
There are a few things wrong in your code here. First and foremost to note is that you are running in an "async" environment now and you need to change the thinking on how you do some things.
Your previous PHP code is "blocking", which means that every line of code must complete before moving on to the next line of code. This includes waiting for a database server to perform an update and return the response.
You cannot use basic control loops with functions inside them that perform asynchronously. Instead you need something that can call the next iteration of the loop (or at least signal that a single iteration is complete ) once the asynchronous function "update" has actually returned a result.
The second point here is that "nothing updated" because you did not tell the function what to update or what to update the matched document with.
The following is analogous to you original PHP listing, but adjusted for "async" methods also use the async.eachSeries for the loop control from the async library:
async.eachSeries(
tables,
function(table,callback) {
var tablename = table.tablename;
delete table.tablename; // just remove the key rather than re-construct
OutAccept.update(
{ "tablename": tablename },
{ "$push": { "inventar": table } },
function(err,numAffected) {
console.log( numAfftected ); // tells you how many are updated or nothing
callback(err)
}
);
},
function(err) {
// comes here on completion of all array items
}
);
The .findOneAndUpdate() command instead returns the document that was modified and with the modifications only if you ask for them with { "new": true }
async.eachSeries(
tables,
function(table,callback) {
var tablename = table.tablename;
delete table.tablename;
OutAccept.findOneAndUpdate(
{ "tablename": tablename },
{ "$push": { "inventar": table } },
{ "new": true },
function(err,doc) {
console.log( doc ); // shows the modified document
callback(err)
}
);
},
function(err) {
// comes here on completion of all array items
}
);
If you want to add Multiple array elements at once, or if you have even a single element directly in an array then use the $each modifier to $push:
var inventor = [
{
"ean": "2",
"name": "name2",
"runtime": "0",
"art": "null",
"marker": "null",
"stammkost": "null",
"accepted": "0"
},
{
"ean": "1",
"name": "name1",
"runtime": "0",
"art": "null",
"marker": "null",
"stammkost": "null",
"accepted": "0"
}
];
OutAccept.update(
{ "tablename": tablename },
{ "$push": { "inventar": { "$each": inventar } } },
function(err,numAffected) {
// work in here
}
);

Resources