Reconstruct JSON after duplicates have been removed - javascript

I have the following JSON -
{
"node1":[
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
},
{
"one":"bar",
"two":"bar",
"three":"bar",
"four":"bar"
},
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
}
],
"node2":[
{
"link":"baz",
"link2":"baz"
},
{
"link":"baz",
"link2":"baz"
},
{
"link":"qux",
"link2":"qux"
},
]
};
I have the following javascript that will remove duplicates from the node1 section -
function groupBy(items, propertyName) {
var result = [];
$.each(items, function (index, item) {
if ($.inArray(item[propertyName], result) == -1) {
result.push(item[propertyName]);
}
});
return result;
}
groupBy(catalog.node1, 'one');
However this does not account for dupicates in node2.
The resulting JSON I require is to look like -
{
"node1":[
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
},
{
"one":"bar",
"two":"bar",
"three":"bar",
"four":"bar"
}
],
"node2":[
{
"link":"baz",
"link2":"baz"
},
{
"link":"qux",
"link2":"qux"
},
]
};
However I cannot get this to work and groupBy only returns a string with the duplicates removed not a restructured JSON?

You should probably look for some good implementation of a JavaScript set and use that to represent your node objects. The set data structure would ensure that you only keep unique items.
On the other hand, you may try to write your own dedup algorithm. This is one example
function dedup(data, equals){
if(data.length > 1){
return data.reduce(function(set, item){
var alreadyExist = set.some(function(unique){
return equals(unique, item);
});
if(!alreadyExist){
set.push(item)
}
return set;
},[]);
}
return [].concat(data);
}
Unfortunately, the performance of this algorithm is not too good, I think somewhat like O(n^2/2) since I check the set of unique items every time to verify if a given item exists. This won't be a big deal if your structure is really that small. But at any rate, this is where a hash-based or a tree-based algorithm would probably be better.
You can also see that I have abstracted away the definition of what is "equal". So you can provide that in a secondary function. Most likely the use of JSON.stringify is a bad idea because it takes time to serialize an object. If you can write your own customized algorithm to compare key by key that'd be probably better.
So, a naive (not recommended) implementation of equals could be somewhat like the proposed in the other answer:
var equals = function(left, right){
return JSON.stringify(left) === JSON.stringify(right);
};
And then you could simply do:
var res = Object.keys(source).reduce(function(res, key){
res[key] = dedup(source[key], equals);
return res;
},{});

Here is my version:
var obj = {} // JSON object provided in the post.
var result = Object.keys(obj);
var test = result.map(function(o){
obj[o] = obj[o].reduce(function(a,c){
if (!a.some(function(item){
return JSON.stringify(item) === JSON.stringify(c); })){
a.push(c);
}
return a;
},[]); return obj[o]; });
console.log(obj);//outputs the expected result
Using Array.prototype.reduce along with Array.prototype.some I searched for all the items being added into the new array generated into Array.prototype.reduce in the var named a by doing:
a.some(function(item){ return JSON.stringify(item) === JSON.stringify(c); })
Array.prototype.some will loop trough this new array and compare the existing items against the new item c using JSON.stringify.

Try this:
var duplicatedDataArray = [];
var DuplicatedArray = [];
//Avoiding Duplicate in Array Datas
var givenData = {givenDataForDuplication : givenArray};
$.each(givenData.givenDataForDuplication, function (index, value) {
if ($.inArray(value.ItemName, duplicatedDataArray) == -1) {
duplicatedDataArray.push(value.ItemName);
DuplicatedArray.push(value);
}
});

Related

JSON Group by Name and then Display Latest Record

Using only JavaScript, I need to
Group by code.
Get latest modifieddate.
Display total grouped code as Count.
Starting JSON Result
[
{"ID":1,"code":"AAA","modifieddate":"2019-06-01","user":"John"},
{"ID":2,"code":"AAA","modifieddate":"2019-06-02","user":"Jane"},
{"ID":3,"code":"AAA","modifieddate":"2019-06-03","user":"Sue"},
{"ID":4,"code":"BBB","modifieddate":"2019-06-10","user":"Rick"},
{"ID":5,"code":"CCC","modifieddate":"2019-06-11","user":"Joe"}
]
Desired JSON Result set
[
{"ID":3,"code":"AAA","modifieddate":"2019-06-03","user":"Sue","Count":"3"},
{"ID":4,"code":"BBB","modifieddate":"2019-06-10","user":"Rick","Count":"1"},
{"ID":5,"code":"CCC","modifieddate":"2019-06-11","user":"Joe","Count":"1"}
]
Tried using reduce method.
I do not have access to modify the server side API code.
I am using Aurelia JS.
You can use Array.reduce to group the result set by each item's code property, incrementing Count as needed, then take the values from the accumulation object. Along the way, we perform a date comparison to determine which the most recent entry to include in the result.
const data = [ {"ID":1,"code":"AAA","modifieddate":"2019-06-01","user":"John"}, {"ID":2,"code":"AAA","modifieddate":"2019-06-02","user":"Jane"}, {"ID":3,"code":"AAA","modifieddate":"2019-06-03","user":"Sue"}, {"ID":4,"code":"BBB","modifieddate":"2019-06-10","user":"Rick"}, {"ID":5,"code":"CCC","modifieddate":"2019-06-11","user":"Joe"} ];
const result = Object.values(data.reduce((a, e) => {
if (!a[e.code]) {
a[e.code] = {...e, Count: 0};
}
if (Date.parse(e.modifieddate) > Date.parse(a[e.code].modifieddate)) {
a[e.code] = {...e, Count: a[e.code].Count};
}
a[e.code].Count++;
return a;
}, {}));
console.log(result);
By the way, this is just a plain JS array we're working with, not JSON.
This should get you:
let array = [
{"ID":1,"code":"AAA","modifieddate":"2019-06-01","user":"John"},
{"ID":2,"code":"AAA","modifieddate":"2019-06-02","user":"Jane"},
{"ID":3,"code":"AAA","modifieddate":"2019-06-03","user":"Sue"},
{"ID":4,"code":"BBB","modifieddate":"2019-06-10","user":"Rick"},
{"ID":5,"code":"CCC","modifieddate":"2019-06-11","user":"Joe"}
]
let result = array.reduce(function(total, currentValue, currentIndex, arr) {
let index = total.findIndex(function(entry) { return entry.code == currentValue.code; })
if (index >= 0) { // entry already exists
// check modified
if (total[index].modifieddate > currentValue.modifieddate) { // already have most recent of the two
total[index].Count += 1;
} else { // need to replace with more recent
currentValue.Count = total[index].Count + 1;
total[index] = currentValue;
}
} else { // first record for this code
currentValue.Count = 1;
total.push(currentValue);
}
return total;
}, []);
console.log(result);
Here is a working js-fiddle
Note: Comments are made in code block

Extract values from JSON

I have the below JSON string. The id-dashes in the file are not optional unfortunately, neither is the syntax. I would like to extract the "dd" values with JavaScript/Node.
{
"a-id":{
"b-id":"random",
"bb-id":"random",
"bbb-id":"random",
"bbbb-id":{
"c":[
{
"d":"random",
"dd":"This_info_is_needed"
},
{
"d":"random",
"dd":"This_info_is_needed"
},
{
"d":"random",
"dd":"This_info_is_needed"
},
{
"d":"random",
"dd":"This_info_is_needed_2"
}
]
},
"bbbbb-id":"random",
"bbbbbb-id":"random"
}
}
I would be open to use any additional helper like lodash, jQuery, etc.
The output should be an array with: This_info_is_needed and This_info_is_needed_2.
Thank you in advance.
You can create custom function that will search your data deep and return value if key is dd using for...in loop.
var obj = {"a-id":{"b-id":"random","bb-id":"random","bbb-id":"random","bbbb-id":{"c":[{"d":"random","dd":"This_info_is_needed"},{"d":"random","dd":"This_info_is_needed"},{"d":"random","dd":"This_info_is_needed"},{"d":"random","dd":"This_info_is_needed"}]},"bbbbb-id":"random","bbbbbb-id":"random"}}
function getDD(data) {
var result = []
for(var i in data) {
if(i == 'dd') result.push(data[i])
if(typeof data[i] == 'object') result.push(...getDD(data[i]))
}
return result
}
console.log(getDD(obj))
If you just interested in the values only, can also just do this:
var obj = {"a-id":{"b-id":"random","bb-id":"random","bbb-id":"random","bbbb-id":{"c":[{"d":"random","dd":"This_info_is_needed"},{"d":"random","dd":"This_info_is_needed"},{"d":"random","dd":"This_info_is_needed"},{"d":"random","dd":"This_info_is_needed"}]},"bbbbb-id":"random","bbbbbb-id":"random"}};
var desiredResults = obj['a-id']['bbbb-id']['c'].map(function(data){return data.dd});
console.log(desiredResults);

Better way to transform a collection using underscorejs

I'm trying to simply replace the invalid date with an empty string. I'm iterating through an array of objects, but whenever I try to use _.each() I get lost. If someone could show me a way to iterate through all the fieldsToCheck items in my list, that would be rad.
massage.removeBadDates = function(data){
var fieldsToCheck = [
"partsLeadTime",
"statusDate",
"targetDate",
"revisedTargetDate",
"quoteDate",
"dispositionDate",
"serviceDate",
"finalDate",
"receivedDate"]
var newData = []
_.map(data, function(value, index, list){
newData.push(value)
//single
if (list[index].partsLeadTime == "1900-01-01T00:00:00"){
newData[index].partsLeadTime = ""
}
});
return newData
};
You pretty much want something like this:
_.each(fieldsToCheck(function(field) {
if (list[index][field] == "1900-01-01T00:00:00") {
newData[index][field] = ""
}
});

Group objects in array by property

I have just started to pick up coding seriously. :)
I came across a problem that seems too complicated for me.
How to group the following products by promotions type?
var data = [
{
name:'product1',
price:'40',
promotion:[
{
name:'Buy 3 get 30% off',
code:'ewq123'
},
{
name:'Free Gift',
code:'abc140'
}
]
},
{
name:'product2',
price:'40',
promotion:[
{
name:'Buy 3 get 30% off',
code:'ewq123'
}
]
},
{
name:'product3',
price:'40',
promotion:[
{
name:'Buy 3 get 30% off',
code:'ewq123'
}
]
},
{
name:'product4',
price:'40'
},
{
name:'product5',
price:'40',
promotion:[
{name:'30% off', code:'fnj245'}
]
},
{
name:'product6',
price:'0',
promotion:[
{
name:'Free Gift',
code:'abc140'
}
]
}
];
I would like to get result in the following format
result =[
{
name : 'Buy 3 get 30% off',
code: 'ewq123',
products: [
... array of products
]
},
{
name : '30% off',
code: 'fnj245',
products: [
... array of products
]
},
{
...
}
];
I am able to get a list of products by promotion code, but how can I make it generic?
function productHasPromo(product, promotion){
if(!product.hasOwnProperty('promotion')) return false;
var productPromo = product.promotion;
for(var i=0; i<productPromo.length; i++){
if(productPromo[i].code === promotion){
return true;
}
}
return false;
}
function groupProductByPromo(products, promotion){
var arr = [];
for(var i=0; i<products.length; i++){
if(productHasPromo(products[i], promotion)){
arr.push(products[i]);
}
}
return arr;
}
Explanation
You could write a function that loops through your array and search for the unique values within a specified property. That is easily done when working with simple data types, but can be done with more complex structures as arrays of objects (like in your example), using a helper grouping function.
Since you also need the output to be in a specific format after the grouping, we will have to work on a transformer also. This transformer will receive the original data and the unique values extracted by the grouping function, and will generate the desired output.
The following functions were used in the example:
Array.prototype.groupBy = function (property, grouping, transformer) {
var values = [];
this.forEach(function (item) {
grouping.call(this, item, property).forEach(function (item) {
if (!values.contains(property, item[property])) {
values.push(item);
}
});
});
return transformer.call(this, values);
};
Array.prototype.contains = function (key, value) {
return this.find(function (elm) {
return elm[key] === value;
});
};
function transformerFunction(values) {
this.forEach(function (item) {
if (!item.promotion) return;
item.promotion.forEach(function (promotion) {
values.forEach(function (option) {
if (option.code === promotion.code) {
if (option.products) {
option.products.push(item);
} else {
option.products = [item];
}
}
});
});
});
return values;
}
function groupingFunction(item, property) {
if (!item.promotion) return [];
var values = [];
item.promotion.forEach(function (promotion) {
if (!values.contains(property, promotion[property])) {
values.push(promotion);
}
});
return values;
}
Usage as follows:
var items = data.groupBy('code', groupFunction, transformFunction);
Example
Check the example i've prepared at jsfiddle
Welcome to the coding world. A lot of people start off with a problem by trying to write some code, then they wonder why it doesn't work and scratch their heads, don't know the basics of debugging it, and then post here to SO. They're missing the crucial first step in programming which is to figure out how you are going to do it. This is also called designing the algorithm. Algorithms are often described using something called pseudo-code. It has the advantage that it can be looked at and understood and established to do the right thing, without getting bogged down in all the mundane details of a programming language.
There are some algorithms that are figured out by some very smart people--like the Boyer-Moore algorithm for string matching--and then there are other algorithms that programmers devise every day as part of their job.
The problem with SO is that all too often someone posts a question which essentially about an algorithm, and then all the keyboard-happy code jockeys pounce it and come up with a code fragment, which in many cases is so contorted and obtuse that one cannot even see what the underlying algorithm is.
What is the algorithm you propose for solving your problem? You could post that, and people would probably give you reasonable comments, and/or if you also give an actual implementation that doesn't work for some reason, help you understand where you've gone wrong.
At the risk of robbing you the pleasure of devising your own algorithm for solving this problem, here's an example:
Create an empty array for the results.
Loop through the products in the input.
For each product, loop through its promotions.
Find the promotion in the array of results.
If there is no such promotion in the array of results, create a new one, with an empty list of products.
Add the product to the array of products in the promotion entry in the array.
In pseudo-code:
results = new Array // 1
for each product in products (data) // 2
for each promotion in promotions field of product // 3
if results does not contain promotion by that name // 4
add promotion to results, with empty products field // 5
add product to products field of results.promotion // 6
If we believe this is correct, we can now try writing this in JavaScript.
var result = []; // 1
for (var i = 0; i < data.length; i++) { // 2
var product = data[i];
var promotions = product.promotion;
for (var j = 0; j < promotions.length; j++) { // 3
var promotion = promotions[i];
var name = promotion.name;
var result_promotion = find_promotion_by_name(name);
if (!result_promotion) { // 4
result_promotion = { name: name, products: [], code: promotion.code };
result.push(result_promotion); // 5
}
result_promotion.products.push(name); // 6
}
}
This code is OK, and it should get the job done (untested). However, it is still a bit unreadable. It does not follow the pseudo-code very closely. It somehow still hides the algorithm. It is hard to be sure that it is completely correct. So, we want to rewrite it. Functions like Array#foreach make it easier to do this. the top level can simply be:
var result = [];
data.forEach(processProduct);
In other words, call the processProduct function for each element of data (the list of products). It will be very hard for this code to be wrong, as long as `processProduct is implemented incorrectly.
function processProduct(product) {
product.promotion.forEach(processPromotion);
}
Again, this logic is provably correct, assuming processPromotion is implemented correctly.
function processPromotion(promotion) {
var result_promotion = getPromotionInResults(promotion);
result_promotion.products.push(name);
}
This could hardly be clearer. We obtain the entry for this promotion in the results array, then add the product to its list of products.
Now we need to simply implement getPromotionInResults. This will include the logic to create the promotion element in the results array if it doesn't exist.
function getPromotionInResults(promotion) {
var promotionInResults = findPromotionInResultsByName(promotion.name);
if (!promotionInResults) {
promotionInResults = {name: promotion.name, code: promotion.code, products: []};
result.push(promotionInResults);
}
return promotionInResults;
}
This also seems demonstrably correct. But we still have to implement findPromotionInResultsByName. For that, we can use Array#find, or some equivalent library routine or polyfill:
function findPromotionInResultsByName(name) {
return result.find(function(promotion) {
return promotion.name === name;
});
}
The entire solution is thus
function transform(data) {
// Given a product, update the result accordingly.
function processProduct(product) {
product.promotion.forEach(processPromotion);
}
// Given a promotion, update its list of products in results.
function processPromotion(promotion) {
var result_promotion = getPromotionInResults(promotion);
result_promotion.products.push(name);
}
// Find or create the promotion entries in results.
function getPromotionInResults(promotion) {
var promotionInResults = findPromotionInResultsByName(promotion.name);
if (!promotionInResults) {
promotionInResults = {name: promotion.name, code: promotion.code, products: []};
result.push(promotionInResults);
}
return promotionInResults;
}
// Find an existing entry in results, by its name.
function findPromotionInResultsByName(name) {
return result.find(function(promotion) {
return promotion.name === name;
});
}
var result = [];
data.forEach(processProduct);
return result;
}
Ok, after a few hours of works, with lots of help online and offline, I finally made it works. Thanks for the people who has helped.
Please do comment if you have a more elegant solution, always love to learn.
For people who ran into similar problem:
Here is my solution
function groupProductsByPromo(data){
var result = [];
// filter only product with promotion
var productsWithPromo = data.filter(function(product){
return product.hasOwnProperty('promotions');
});
// create promotions map
var mappedProducts = productsWithPromo.map(function(product) {
var mapping = {};
product.promotions.forEach(function(promotion) {
mapping[promotion.code] = {
promotion: promotion
};
});
return mapping;
});
// reduce duplicates in promotion map
mappedProducts = mappedProducts.reduce(function(flattenObject, mappedProducts) {
for (var promoCode in mappedProducts) {
if (flattenObject.hasOwnProperty(promoCode)) {
continue;
}
flattenObject[promoCode] = {
code: promoCode,
name: mappedProducts[promoCode].promotion.name
};
}
return flattenObject;
}, {});
// add products to promo item
for(var promoCode in mappedProducts){
mappedProducts[promoCode].products = productsWithPromo.filter(function(product){
return product.promotions.some(function(promo){
return promo.code === promoCode;
});
});
result.push(mappedProducts[promoCode]);
}
return result;
}
Check out lodash - a nifty library for doing all sorts of transforms.
lodash.groupBy is what you're looking for.

refactor two $.each() loops to one

I have a JSON object like this...
{
"tasks":[
{
"id":"task_3",
"taskName":"Task A",
"assignee":"Barrack Obama",
"timeReqOptimisitic":"4",
"timeReqNormal":"8",
"timeReqPessimistic":"14",
"timeUnit":"Days",
"timeReq":"8.33",
"positionX":493,
"positionY":101,
"lockStatus":"unlocked"
}
],
"milestones":[
{
"id":"task_1",
"milestoneName":"Start",
"positionX":149,
"positionY":109,
"lockStatus":"unlocked",
"milestoneDate":"2015-04-07"
},
{
"id":"task_2",
"milestoneName":"Finish",
"positionX":989,
"positionY":367,
"lockStatus":"unlocked",
"milestoneDate":"2015-04-22"
}
],
"connections":[
{
"connectionId":"con_10",
"pageSourceId":"task_1",
"pageTargetId":"task_3"
},
{
"connectionId":"con_20",
"pageSourceId":"task_3",
"pageTargetId":"task_2"
}
]
}
...this is a minimal version. In practice, there are numerous items in "tasks", "milestones" and "connections".
I need to iterate through the object and determine the "id" of the "milestones" item with the lowest/earliest "milestoneDate", then identify the "connections" item that has the same value for its "pageSourceId" and return its "pageTargetId".
So in the above example:
Step 1) Iterate through the object and determine the "id" of the "milestones" item with the lowest/earliest "milestoneDate".
Answer: milestones.id = "task_1"
Step 2) Identify the "connections" item that has the same value for its "pageSourceId".
Answer: connections.pageSourceId = "task_1"
Step 3) Return its "pageTargetId".
Answer: "task_3"
I have a working example here. However, I would like to know if there is a way to accomplish this without using the extremely high start date and also in one loop.
As you are not parsing the same array on these two loops, there is no way to merge your loops.
Anyway, you can yet remove the loops to access to the arrays:
http://jsfiddle.net/gael/sruvtwre/2/
$.each(object.milestones, function( index, value ) {
if(startDate > parseDate(value.milestoneDate)) {
startDate = parseDate(value.milestoneDate);
id = value.id
}
});
$.each(object.connections, function( index, value ) {
if(id == value.pageSourceId) {
pageTargetId = value.pageTargetId;
}
});
May be also sorting, and indexing your datas. Then you would need no loops:
Elements in milestones should be sorted, so the earliest milestones element would be milestones[0].
Elements in connections should be indexed by their pageTargetId property, so the requested element should be connections[id].
Your two loops would become:
var pageTargetId= object.connections[ object.milestones[0].id ].pageTargetId;
http://jsfiddle.net/gael/sruvtwre/4/
As said in comments, sorting is not an optimal solution, even if that does not really matter for small sets.
Roughly, there is no no needs to sort all the datas, just the latest matters.
You can use array reduce method, as an comparable alternative to a simple loop:
var latestMilestone= object.milestones.reduce(function(milestone1, milestone2){
if( parseDate(milestone1.milestoneDate) > parseDate(milestone2.milestoneDate) )
return milestone1;
else
return milestone2;
//convert date to timestamp
function parseDate(date) {
var parts = date.split('-');
return Date.UTC(parts[0], parts[1]-1, parts[2]); // Note: months are 0-based
}
});
How about this:
Assuming you get the milestones.id = "task_1" in first loop; outside the loop we can have use jQuery grep. As connections will have unique pageSourceId, grep will return an array with only one object.
var filteredData = jQuery.grep('CONNECTIONS_ARRAY', function(element, index){
return element.pageSourceId == 'MILESTONES_ID'; // Which you get in the loop earlier
});
Then we can access pageTargetId like this:
if(filteredData.length){
filteredData[0].pageTargetId;
}
Try
var dates = []
, ids = []
, filtered = $.map(data.milestones, function(value, index) {
dates.push(new Date(value.milestoneDate).getTime());
ids.push(value.id);
if (dates.length === data.milestones.length) {
var id = ids[$.inArray(Math.min.apply(Math, dates), dates)]
, res = $.grep(data.connections, function(task, key) {
return task.pageSourceId === id
})[0].pageTargetId;
return res
}
})[0]; // `"task_3"`
var data = {
"tasks":[
{
"id":"task_3",
"taskName":"Task A",
"assignee":"Barrack Obama",
"timeReqOptimisitic":"4",
"timeReqNormal":"8",
"timeReqPessimistic":"14",
"timeUnit":"Days",
"timeReq":"8.33",
"positionX":493,
"positionY":101,
"lockStatus":"unlocked"
}
],
"milestones":[
{
"id":"task_1",
"milestoneName":"Start",
"positionX":149,
"positionY":109,
"lockStatus":"unlocked",
"milestoneDate":"2015-04-07"
},
{
"id":"task_2",
"milestoneName":"Finish",
"positionX":989,
"positionY":367,
"lockStatus":"unlocked",
"milestoneDate":"2015-04-22"
}
],
"connections":[
{
"connectionId":"con_10",
"pageSourceId":"task_1",
"pageTargetId":"task_3"
},
{
"connectionId":"con_20",
"pageSourceId":"task_3",
"pageTargetId":"task_2"
}
]
};
var dates = []
, ids = []
, filtered = $.map(data.milestones, function(value, index) {
dates.push(new Date(value.milestoneDate).getTime());
ids.push(value.id);
if (dates.length === data.milestones.length) {
var id = ids[$.inArray(Math.min.apply(Math, dates), dates)]
, res = $.grep(data.connections, function(task, key) {
return task.pageSourceId === id
})[0].pageTargetId;
return res
}
})[0];
document.write(filtered);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>

Categories