knockout js mapping nested observable arrays - json

Given this json structure:
{
"categoryID" : 1,
"categoryName" : "Stupid Questions",
"questions" : [{
"question" : [{
"questionOptions" : [{
"questionOptionID" : 1,
"optionText" : "It's top secret."
}, {
"questionOptionID" : 2,
"optionText" : "Because I am big and your small. I am right and your wrong."
}, {
"questionOptionID" : 3,
"optionText" : "I will gladly pay you Tuesday for a hamburger today."
},
],
"questionType" : "checkbox",
"questionText" : "Why can't we use more abstract table and column names?",
"summary" : "Question of the year"
}
]
}
]
}
I would like to map both the questions and questionOptions to template and templateOptions:
{
"categoryID" : 1,
"categoryName" : "Stupid Questions",
"templates" : [{
"template" : [{
"templateOptions" : [{
"templateOptionID" : 1,
"optionText" : "It is top secret."
}, {
"QuestionOptionID" : 2,
"OptionText" : "Because we are lazy."
}, {
"QuestionOptionID" : 3,
"OptionText" : "I will gladly pay you Tuesday for a hamburger today."
},
],
"QuestionType" : "checkbox",
"QuestionText" : "Why can't we use more abstract table and column names?",
"Summary" : "Question of the year"
}
]
}
]
}
Here is the start to my knockout mapping object:
var templateMapping = {
'templates': {
templates: function(data) {
return ko.utils.unwrapObservable(data.questions);
}
}
//how do I map observable array of question options to observable array of template options here?
};
The key in this mapping is that the sub objects have a different structure (unlike this question - https://stackoverflow.com/a/7535397/466321). It seems like all of the mapping examples I have found don't cover how this may get done, and I have unsuccessfully tried a couple of theories of my own.

#Jeff Mercado is right. The mapper is not intended for this. To accomplish what you intend, it takes a bit of recursive javascript.
function myTransform(string) {
// case-insensitive replace
return string.replace(/question/i,'template');
}
function transformObject(source) {
var result = {}
for( var key in source ) {
if( !source.hasOwnProperty(key) ) continue;
var value = source[key];
var newKey = myTransform(key);
if( Object.prototype.toString.call(value) == "[object Array]" ) {
result[newKey] = [];
for( var i in value ) {
if( !value.hasOwnProperty(i) ) continue;
result[newKey][i] = transformObject(value[i]);
}
}
else if( Object.prototype.toString.call(value) == "[object Object]" ) {
result[newKey] = transformObject(value);
}
else {
result[newKey] = value;
}
}
return result;
}
var wow = transformObject(json);
See this fiddle

Related

Splitting Json to multiple jsons in NIFI

I have the below json file which I want to split in NIFI
Input:
[ {
"id" : 123,
"ticket_id" : 345,
"events" : [ {
"id" : 3322,
"type" : "xyz"
}, {
"id" : 6675,
"type" : "abc",
"value" : "sample value",
"field_name" : "subject"
}, {
"id" : 9988,
"type" : "abc",
"value" : [ "text_file", "json_file" ],
"field_name" : "tags"
}]
}]
and my output should be 3 different jsons like below:
{
"id" : 123,
"ticket_id" : 345,
"events.id" :3322,
"events.type":xyz
}
{
"id" : 123,
"ticket_id" : 345,
"events.id" :6675,
"events.type":"abc",
"events.value": "sample value"
"events.field_name":"subject"
}
{
"id" : 123,
"ticket_id" : 345,
"events.id" :9988,
"events.type":"abc",
"events.value": "[ "text_file", "json_file" ]"
"events.field_name":"tags"
}
I want to know can we do it using splitjson? I mean can splitjson split the json based on the array of json objects present inside the json?
Please let me know if there is a way to achieve this.
If you want 3 different flow files, each containing one JSON object from the array, you should be able to do it with SplitJson using a JSONPath of $ and/or $.*
Using reduce function:
function split(json) {
return json.reduce((acc, item) => {
const events = item.events.map((evt) => {
const obj = {id: item.id, ticket_id: item.ticket_id};
for (const k in evt) {
obj[`events.${k}`] = evt[k];
}
return obj;
});
return [...acc, ...events];
}, []);
}
const input = [{"id":123,"ticket_id":345,"events":[{"id":3322,"type":"xyz"},{"id":6675,"type":"abc","value":"sample value","field_name":"subject"},{"id":9988,"type":"abc","value":["text_file","json_file"],"field_name":"tags"}]}];
const res = split(input);
console.log(res);

How to get list of documents in the collection till where the sum of 'cost' field has reached a specific value (mongoDb)?

I have a collection:
[
{
_id:1,
dish:pizza,
cost:52
},
{
_id:2,
dish:burger,
cost:33,
},
{
_id:3,
dish:sandwich,
cost:64,
}
{
_id:4,
dish:noodles,
cost:23
},
]
I have a budget set equal to some value say 60.
Is it possible to sort the dishes by cost in asc order and get first n number of documents where sum of costs is less than or equal to 60?
here: cost of 23(noodles)+33(burger)<=60, so these 2 documents are fetched.
output should be:
[
{
_id:4,
dish:noodles,
cost:23
},
{
_id:2,
dish:burger,
cost:33,
}
]
You can try this map-reduce command :
// Variable for map
var _map = function () {
emit(null,this);
};
// Variable for reduce
var _reduce = function (key, values) {
var sum = 0;
var resultArray=[]
for(value in values){
sum=sum+values[value].cost;
if(sum<=60){
resultArray.push(values[value]);
}
}
var result = {};
result.selected=resultArray;
return result;
}
;
db.runCommand({
mapReduce: "01",
map: _map,
reduce: _reduce,
out: { "inline" : 1},
query: {},
sort: {cost:1},
inputDB: "testing",
});
})
Note sort by cost indicated in command. You can filter your doc before map-reduce with the query object.
It will output
{
"_id" : null,
"value" : {
"selected" : [
{
"_id" : 4,
"dish" : "noodles",
"cost" : 23
},
{
"_id" : 2,
"dish" : "burger",
"cost" : 33
}
]
}
}
Quite easy at this point to access to value.selected to get the results.
Hope it helps.

Get Unique object of JSON

I have this kind of array:
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
I'd like to filter it to have:
var bar = [ { "a" : "1" }, { "b" : "2" }];
This is my plunker
At line 7 in plunker when i write return JSON.stringify( x ); it is good but returning string JSON.. But when i write return x; it becomes bad and does not return Unique JSON.
You can do this by simply using uniq, not requiring collection:
var uniqueList =_.uniq(foo, function( x ){
return JSON.stringify(x);
});
Updated plunk here: http://plnkr.co/edit/KYW6UybdiBxuvOVX8naP?p=preview
first you can download underscorejs then you can use the following code
var foo = [{ "a": "1" }, { "b": "2" }, { "a": "1" }];
var result = _.uniq(foo, function (obj) {
return JSON.stringify(obj);
});
refere the following url http://underscorejs.org/
You can use var uniqueList =_.uniq(foo, 'a'); Here is doc: https://lodash.com/docs#uniq
here is plunker: http://plnkr.co/edit/HkirGPrs3dGZMUEnEKiT?p=preview

Query for : How many elements of an array are matching within a string in mongoDb

Suppose my JSON is like following:
{ "id":0,"keywords":"amount,debited,account,ticket,not,generated,now" }
{ "id":1,"keywords":"how,safe,gocash" }
{ "id":2,"keywords":"how,referral,program,gocash,works" }
If my array is like
array =["how","safe","gocash"];
then how do I get the count that while checking with first; count should be zero, with second three and with third two. (That means how many elements of an array are present in the string)
Is it possible or what approach I should adopt?
One way of solving this would require some form of modification to your schema by adding an extra field that holds the keywords in an array. This field becomes quite handy when running an aggregation pipeline to return the desired count of elements of an array that match the original string.
To add the additional field you would need the Bulk API operations to update the collection as follows:
var bulk = db.collection.initializeOrderedBulkOp(),
count = 0;
db.collection.find({"keywords": { "$exists": true, "$type": 2 }}).forEach(function(doc) {
var keywordsArray = doc.keywords.split(',');
bulk.find({ "_id": doc._id }).updateOne({
"$set": { "keywordsArray": keywordsArray }
});
count++;
if (count % 100 == 0) {
bulk.execute();
bulk = db.collection.initializeUnorderedBulkOp();
}
});
if (count % 100 != 0) { bulk.execute(); }
The above creates an additional field "keywordsArray" that is a result of splitting the keywords string to an array.
After the operation your sample collection would have the documents:
/* 0 */
{
"_id" : ObjectId("561e24e9ba53a16c763eaab4"),
"id" : 0,
"keywords" : "amount,debited,account,ticket,not,generated,now",
"keywordsArray" : [
"amount",
"debited",
"account",
"ticket",
"not",
"generated",
"now"
]
}
/* 1 */
{
"_id" : ObjectId("561e24e9ba53a16c763eaab5"),
"id" : 1,
"keywords" : "how,safe,gocash",
"keywordsArray" : [
"how",
"safe",
"gocash"
]
}
/* 2 */
{
"_id" : ObjectId("561e24e9ba53a16c763eaab6"),
"id" : 2,
"keywords" : "how,referral,program,gocash,works",
"keywordsArray" : [
"how",
"referral",
"program",
"gocash",
"works"
]
}
On to the next stage, the aggregation framework pipeline, run the following pipeline operation which uses the $let, $size and $setIntersection operators to work out the the desired count result:
var array = ["how","safe","gocash"];
db.collection.aggregate([
{
"$project": {
"id": 1, "keywords": 1,
"count": {
"$let": {
"vars": {
"commonToBoth": { "$setIntersection": [ "$keywordsArray", array ] }
},
"in": { "$size": "$$commonToBoth" }
}
}
}
}
])
Sample Output:
/* 0 */
{
"result" : [
{
"_id" : ObjectId("561e24e9ba53a16c763eaab4"),
"id" : 0,
"keywords" : "amount,debited,account,ticket,not,generated,now",
"count" : 0
},
{
"_id" : ObjectId("561e24e9ba53a16c763eaab5"),
"id" : 1,
"keywords" : "how,safe,gocash",
"count" : 3
},
{
"_id" : ObjectId("561e24e9ba53a16c763eaab6"),
"id" : 2,
"keywords" : "how,referral,program,gocash,works",
"count" : 2
}
],
"ok" : 1
}

Angularjs: Filtering JSON

The structure of the json response I have to work with is giving me all sorts of trouble.
I would like to filter children elements based on the entityType field.
For example I want only element of Folder type.
JSON:
{
"id" : "df1d2550-1442-41b8-9588-785e229c5728",
"path" : "",
"name" : "",
"entityType" : "Folder",
"children" : {
"child1" : {
"id" : "02427ae5-364e-47d0-8998-0876c596d586",
"name" : "child1",
"entityType" : "Book"
},
"child2" : {
"id" : "2bcef8b3-d3a3-410e-a481-a69ec7dce24d",
"name" : "child2",
"entityType" : "Letter"
},
"child3" : {
"id" : "12ee8334-d596-4b59-a09d-c286117f3966",
"name" : "child2",
"entityType" : "Book"
},
"Art" : {
"id" : "e3f2980e-433c-4eaa-b444-ed9702949ffc",
"name" : "Art",
"entityType" : "Folder"
},
"English" : {
"id" : "8fe0f14a-6f76-41aa-9ab3-3e63cd5a900b",
"name" : "English",
"entityType" : "Folder"
}
},
"properties" : { },
"ancestors" : [ ]
};
The JS code:
$scope.isFolder = function(item) {
return item.entityType === "Folder";
};
HTML
<div ng-repeat="item in library.children | filter:isFolder">
<pre>{{item.name}} - {{item.entityType}}</pre>
</div>
This code will display all the children when I only want 2.
Any idea what I am doing wrong ?
Plunker
Thanks
The reason your filter doesn't work is that Filter works on arrays but you have an object literal.
So you can either convert your object literal into an array like:
{
"id" : "df1d2550-1442-41b8-9588-785e229c5728",
"path" : "",
"name" : "",
"entityType" : "Folder",
"children" : [
{
"id" : "02427ae5-364e-47d0-8998-0876c596d586",
"name" : "child1",
"entityType" : "Book"
},
{
"id" : "2bcef8b3-d3a3-410e-a481-a69ec7dce24d",
"name" : "child2",
"entityType" : "Letter"
},
{
"id" : "12ee8334-d596-4b59-a09d-c286117f3966",
"name" : "child2",
"entityType" : "Book"
},
{
"id" : "e3f2980e-433c-4eaa-b444-ed9702949ffc",
"name" : "Art",
"entityType" : "Folder"
},
{
"id" : "8fe0f14a-6f76-41aa-9ab3-3e63cd5a900b",
"name" : "English",
"entityType" : "Folder"
}
],
"properties" : { },
"ancestors" : [ ]
};
You can use below code to convert children's properties to an array:
var arr = [];
for (var key in library.children) {
if (obj.hasOwnProperty(key)) {
obj[key]["key"] = key
arr.push(obj[key]);
}
};
library.children = arr;
Or create your own filter than takes in the object literal as callum linington do.
I went through the documentation. Although it is not as explicit as you would expect, looks like the filter function will only work on Array elements and not objects.
In your example, library.children is an object, not an array. Hence, the filter function is never called (which you can check by adding a console statement inside the function).
The specific part in the documentation which mentions that the filter function is called on Array can be found here. You will find it in the "Argument" section, "expression" row - look for the function description. It says that the function is called on Arrays and not Objects, which is your scenario.
So you can easily solve this by writing your own filter here is your adapted plnkr
Simple filter:
app.filter('byKey', function () {
return function (array, key, value) {
var take = [];
angular.forEach(array, function (e) {
if (e[key] === value){
take.push(e);
}
});
return take;
}
});
Simple usage:
<div ng-repeat="item in library.children | byKey: 'entityType' : 'Folder'">
<pre>{{item.name}} - {{item.entityType}}</pre>
</div>
The breakdown:
return function (input, arg1.....argn) {
}
The above return function in the filter is what drives the filter, the input is what you specify on the left side of the | (pipe), in your case: item in library.children.
It's the right side that confuses people. The first string should be the filter name, in my case byKey as that is what I have named it. Each time you use : you are allowing yourself to pass an arg.
So, I have specified this:
return function (array, key, value) {}
The first argument - array, is specified by the left of the |. The remaining args are specified each time you use :, in your case
byKey : 'entityType' : 'Folder'
byKey = filter, 'entityType' = key, 'Folder' = the value of the key