Create collection of objects with Underscore - javascript

Basically, I have an array with objects and they would need to be grouped together. It is kinda hard to explain, but it might be easier if I just gave you guys an example.
Result data
[
{
"Category": "Préparé",
"Sandwich": "Martino",
"Ingredient": "Ansjovis",
"Price": 3.1
},
{
"Category": "Préparé",
"Sandwich": "Martino",
"Ingredient": "Tabasco",
"Price": 3.1
},
{
"Category": "Veggie",
"Sandwich": "Gezond",
"Ingredient": "Tomaat",
"Price": 2.5
},
{
"Category": "Veggie",
"Sandwich": "Gezond",
"Ingredient": "Kaas",
"Price": 2.5
}
];
This is a basic example of what my array looks like. I cannot change this structure, it is how our API provides the data.
What I actually need is this structure:
[
{
"CategoryName": "Prépare",
"Sandwiches": [
{
"SandwichName": "Martino",
"Price": 3.1,
"Ingredients": ["Ansjovis", "Tabasco"]
}
]
},
{
"CategoryName": "Veggie",
"Sandwiches": [
{
"SandwichName": "Gezond",
"Price": 2.5,
"Ingredients": ["Tomaat", "Kaas"]
}
]
}
]
I have tried some stuff with Underscore and _.groupBy, _.sortBy, _.countBy
But alas, nothing I have tried actually works. Is this even possible with Underscore (or some other library)?
Also on a sidenote, this example might have some JSON structure mistakes, because I wrote it myself. The data provided by the API has a correct format.
The example only has 2 sandwiches, but in real-time, I'll be retrieving multiple categories with each 20 sandwiches and so on. This is just a minified example, but it provides an idea of what I need.

try this in simple js
var map = {};
results.forEach( function(obj){
map[ obj.CategoryName ] = map[ obj.CategoryName ] || [];
map[ obj.CategoryName ].push( obj );
});
var output = Object.keys(map).map( function(key){
var arr = [];
map[key].forEach(function(obj){
arr.push( {
"SandwichName": obj.SandwichName,
"Price": obj.Price,
"Ingredients": obj.Ingredients
});
});
return { "CategoryName" : key , "Sandwiches" : arr };
});

The following piece of code would do the trick for you:
var data = [...]; // this is your json-data
var result = _.map(_.uniq(_.pluck(data, 'Category')), function(category) {
var sandwiches = _.uniq(_.pluck(_.where(data, { Category: category }), 'Sandwich'));
return {
CategoryName: category,
Sandwiches: _.map(sandwiches, function(sandwich) {
return {
SandwitchName: sandwich,
Price: _.findWhere(data, { Category: category, Sandwich: sandwich }).Price,
Ingredients: _.pluck(_.where(data, { Category: category, Sandwich: sandwich }), 'Ingredient')
};
})
};
});

Related

How do I convert a nested array to a 'keyed' array in JavaScript?

I have a nested array that looks like this:
[["Organisation","ID","Name"],["ACME","123456","Bart Simpson"],["ACME","654321","Ned Flanders"],["ACME","1234","Edna Kabappel"],["Yahoogle","666666","Bon Scott"],["Yahoogle","99999","Neil Young"],["Yahoogle","111111","Shania Twain"]]
The first value in each array is the name of an organisation that an ID and name can belong to.
I am trying to find the simplest way to group all instances where an ID and name belong to the same company, under one 'key'.
So the above would result in something like this:
{
"ACME": [
{
"ID": 123456,
"Name": "Bart Simpson"
},
{
"ID": 654321,
"Name": "Ned Flanders"
},
{
"ID": 1234,
"Name": "Edna Kabappel"
}
],
"Yahoogle": [
{
"ID": 666666,
"Name": "Bon Scott"
},
{
"ID": 99999,
"Name": "Neil Young"
},
{
"ID": 111111,
"Name": "Shania Twain"
}
]
}
I have been playing around with for loops but I'm ending up with many many lines of code, trying to detect when the company name is different from the previous, and getting into a real mess with things.
I have searched a lot here trying to find something similar but have not had any luck.
I have only just started coding again for person interest after about 18 years and I'm very novice again.
Thank you in advance for any assistance.
lot of solutions to arrive at same result, one using lambda and reduce: this is a generic solution, just adapt the output push to build your final json.
const datas = [
["Organisation", "ID", "Name"],
["ACME", "123456", "Bart Simpson"],
["ACME", "654321", "Ned Flanders"],
["ACME", "1234", "Edna Kabappel"],
["Yahoogle", "666666", "Bon Scott"],
["Yahoogle", "99999", "Neil Young"],
["Yahoogle", "111111", "Shania Twain"]
];
const titles = datas.shift()
const groupBy = (x,f)=>x.reduce((a,b)=>((a[f(b)]||=[])
.push({[titles[1]]:b[1], [titles[2]]:b[2]}),a),{});
const result = groupBy(datas, v => v[0])
console.log(result)
Using Array.reduce. Please check if this works for you. In the below approach ID and Name is hard coded. You can try writing a generic dynamic approach which handle any number of params like ID, Name, Age etc.
const myArray = [
["Organisation", "ID", "Name"],
["ACME", "123456", "Bart Simpson"],
["ACME", "654321", "Ned Flanders"],
["ACME", "1234", "Edna Kabappel"],
["Yahoogle", "666666", "Bon Scott"],
["Yahoogle", "99999", "Neil Young"],
["Yahoogle", "111111", "Shania Twain"]
];
const obj = myArray.reduce((acc, value, index) => {
if (index === 0) return acc;
const key = value[0];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push({
ID: value[1],
Name: value[2]
});
return acc;
}, {});
console.log(obj);
In order to achieve what you want, you can follow below steps:
Create an object to store your result.
While you are running the loop you have to check whether name of the organization exists as a key in the object and add it if it does not, initializing it to an empty array. Then push the result you want to store into that array.
Below is a sample implementation, assuming your data is stored in data:
var result = {};
for(var i=1; i < data.length; i++){
if(!result.hasOwnProperty(data[i][0])){
result[data[i][0]] = [];
}
result[data[i][0]].push({ "ID": data[i][1], "Name": data[i][2]});
}

How to do a join from 2 JSON by a common ID field in Javascript

I have two JSON files: JSON A has some company properties and the company_id, while JSON B has company names and company ids.
JSON A example:
[
{
"order_name": "Foo",
"company_id": "112233"
},
{
"order_name": "Bar",
"company_id": "123456"
}
]
JSONB example:
[
{
"company_id":"112233",
"name":"ACME company",
},
{
"company_id":"123456",
"name":"John Doe Inc.",
}
]
Which is the most efficient way to do a join by the company_id values? I would like to have the JSON C (merged result) with the company names correctly added, like this:
[
{
"order_name": "Foo",
"company_id": "123456",
"company_name": "John Doe Inc."
},
{
"order_name": "Bar",
"company_id": "112233",
"company_name": "ACME company"
}
]
Is looping and filter for each the only solution? Is there a more efficient way to do this from a performance point of view?
More info:
JSON is not sorted by company_id.
Array A could have more than one object with the same company_id
I'm using Javascript (in a Vue.js app), I don't need to support old browsers
In common modern JavaScript, you can do this as you mentioned with higher-order functions like map, filter, and so on:
const arrayA = [
{
"order_name": "Foo",
"company_id": "112233"
},
{
"order_name": "Bar",
"company_id": "123456"
}
]
const arrayB = [
{
"company_id":"112233",
"name":"ACME company",
},
{
"company_id":"123456",
"name":"John Doe Inc.",
}
]
const mergeAB = arrayA.map( companyA => {
const matched = arrayB.find(companyB => companyB.company_id === companyA.company_id)
if(matched) {
return {...companyA, ...matched}
} else {
// return companyA element or customize it with your case
}
}
)
console.log(mergeAB)
Note 1: Array.find() method complexity is O(n) and Array.map() method complexity is O(n)
Note 2: efficiency is an important thing but not in all situations. sometimes you need to do these types of iteration one time or for a small array size, so no need to worry about the performance.
Note 3: you could compare the answer and find out your best solution since we don't know about your whole code and application.
I hope this will work for you. Let me know if you have any questions.
const arrayOne = [
{
"order_name": "Foo",
"company_id": "112233"
},
{
"order_name": "Bar",
"company_id": "123456"
}
];
const arrayTwo = [
{
"company_id":"112233",
"name":"ACME company",
},
{
"company_id":"123456",
"name":"John Doe Inc.",
}
];
const [source, target] = arrayOne.length > arrayTwo.length
? [arrayOne, arrayTwo]
: [arrayTwo, arrayOne];
const merged = source.map(object =>
{
// Assuming that in the 2nd array, the match is only found 1 time and it EXISTS.
const matched = target.find(element => element.company_id === object.company_id);
// Merge both objects together
return {
...object,
...matched
};
});
console.log(merged);
By having JSONs:
const jsonA = [
{
"order_name": "Foo",
"company_id": "112233"
},
{
"order_name": "Bar",
"company_id": "123456"
}
];
const jsonB = [
{
"company_id":"112233",
"name":"ACME company",
},
{
"company_id":"123456",
"name":"John Doe Inc.",
}
];
you can merge maps into 3rd map with something like this:
const transform = (data, current={}) =>
data.reduce((prev, company) => {
if(!prev[company['company_id']]) prev[company['company_id']] = {};
prev[company['company_id']] = {...prev[company['company_id']], ...company}
return prev;
}, current);
let jsonMap = transform(jsonA, {});
jsonMap = transform(jsonB, jsonMap);
let jsonC = Object.keys(jsonMap).map(companyId => jsonMap[companyId] );
console.log(jsonC);

Taking JSON data, grouping by a property, and aggregating using multiple aggregation functions

Here is a sample of JSON data of fruit weights and prices:
let fruitData = [{"fruit":"apple","weight":12,"price":1.80},
{"fruit":"apple","weight":15,"price":2.00},
{"fruit":"apple","weight":10,"price":1.60},
{"fruit":"banana","weight":22,"price":3.00},
{"fruit":"banana","weight":24,"price":3.20}]
If I want to group by the "fruit" property and return mean "weight" and "price" values for each fruit, what do I do to achieve this? The end result would be something like:
aggFruitData = [{"fruit":"apple","weight":12.3333,"price":1.8},
{"fruit":"banana","weight":23,"price":3.1}]
The big hope is that the data can still be in an easy to manipulate form like JSON following the transformation. I know that SQL provides a groupby method, but I'm wondering if that is more efficient or if using native JS is more effective. Ideally, this could be something that could be scaled up, such as including another property to group by (maybe like month sold/month price was recorded). I'm open to using either vanilla JS methods or a library/framework meant to parse in this way— I just want efficiency with the project's execution
Using vanilla js:
let fruitData = [{
"fruit": "apple",
"weight": 12,
"price": 1.80
},
{
"fruit": "apple",
"weight": 15,
"price": 2.00
},
{
"fruit": "apple",
"weight": 10,
"price": 1.60
},
{
"fruit": "banana",
"weight": 22,
"price": 3.00
},
{
"fruit": "banana",
"weight": 24,
"price": 3.20
}
];
const aggregated = Object.values(fruitData.reduce((current, item) => {
if (!current[item.fruit]) {
current[item.fruit] = {
...item,
count: 1
};
} else {
const i = current[item.fruit];
i.weight = (i.weight * i.count + item.weight) / (i.count + 1);
i.price = (i.price * i.count + item.price) / (i.count + 1);
i.count++;
}
return current;
}, {})).map(({
fruit,
weight,
price
}) => ({
fruit,
weight,
price
}));
console.log(aggregated);
// [ { fruit: 'apple', weight: 12.333333333333334, price: 1.8 },
// { fruit: 'banana', weight: 23, price: 3.1 } ]
You could also use the lodash library (_.groupBy etc) functions for this. Note that the last .map is to strip out the count field only, but you may actually find it useful to have that!

Join JSON attributes

I am working with javascript and I would like to join two JSON file into a single JSON object that contains all the attributes. Right now the two JSON file have separate information but I need to combine them.
Station Information JSON - Example Below:
{
"last_updated":1493307962,
"ttl":10,
"data":{
"stations":[
{
"station_id":"219",
"name":"Central Square - East Boston",
"short_name":"A32036",
"lat":42.37454454514976,
"lon":-71.03837549686432,
"region_id":10,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":19,
"eightd_has_key_dispenser":false
},
{
"station_id":"220",
"name":"Test 1",
"short_name":"Test 1",
"lat":0,
"lon":0,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":0,
"eightd_has_key_dispenser":false
}
]
}
}
Station Status JSON - Example Below:
{
"last_updated":1493308075,
"ttl":10,
"data":{
"stations":[
{
"station_id":"219",
"num_bikes_available":7,
"num_bikes_disabled":1,
"num_docks_available":11,
"num_docks_disabled":0,
"is_installed":1,
"is_renting":1,
"is_returning":1,
"last_reported":1493283725,
"eightd_has_available_keys":false
},
{
"station_id":"220",
"num_bikes_available":0,
"num_bikes_disabled":0,
"num_docks_available":0,
"num_docks_disabled":0,
"is_installed":0,
"is_renting":0,
"is_returning":0,
"last_reported":0,
"eightd_has_available_keys":false
}
]
}
}
Specifically, I looked at this post (How to join two json object in javascript, without using JQUERY) but the two JSON files have more complex structure, so I could not make it work.
Any suggestion will be really appreciated.
This code behaves like a join on the 2nd object (but can be extended to perform a full outer join)
It handles conflicts by appending a string _conflict to the key name
I've written this one to get you started but you'll have to customize it to support your exact structure
The combined object isn't a list anymore but has the same indexes as the array.
var obj1 = {
"conflicting_key":1493307962,
"concurrent_key":10,
"data":{
"listOfEvents":[
{
"event_id":219,
"name":"Central Square - East Boston",
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":19
},
{
"event_id":220,
"name":"Test 1",
"lat":0,
"lon":0,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":0,
"eightd_has_key_dispenser":false
}
]
}
};
var obj2 = {
"conflicting_key":1493308075,
"concurrent_key":10,
"data":{
"listOfEvents":[
{
"event_id":219,
"num_bikes_available":7,
"num_bikes_disabled":1,
"last_reported":1493283725,
"eightd_has_available_keys":false
},
{
"event_id":220,
"num_bikes_available":0,
"num_bikes_disabled":0,
"num_docks_available":0,
"is_returning":0,
"last_reported":0,
"eightd_has_available_keys":false
}
]
}
};
function combine(obj1, obj2) {
var combinedObject = Object.assign({}, obj1);
for(var key in obj2) {
if(typeof obj2[key] !== "object") {
if(obj1[key] !== obj2[key]) {
var keyName = key;
if(key in obj1) {
keyName = keyName + "_conflict";
}
combinedObject[keyName] = obj2[key];
}
} else {
combinedObject[key] = combine(obj1[key], obj2[key]);
}
}
return combinedObject;
}
console.log(combine(obj1, obj2));
I guess you want to merge stations. If both stations arrays are in the same order (as your example shows) you can do it easily this way:
First parse both JSON using JSON.parse and then merge each station object using Object.assign
var obj1 = JSON.parse('your-first-json');
var obj2 = JSON.parse('your-second-json');
obj1.data.stations.forEach(function(item, i) {
Object.assign(item, obj2.data.stations[i])
});
//obj1 will have the obj2 sation data.
If the arrays are not in the same order (same index - same ID) you'll have to perform a lookup by ID before performing the merge.
You could use Array.find for that:
obj1.data.stations.forEach(function(station, i){
var station2 = obj2.data.stations.find(function(item) {
return item.station_id === station.station_id;
});
Object.assign(station, station2);
});
I don't know where you're running this, if in node or in the browser, but there are polyfills for both Object.assign & Array.find in the links I've provided. Also there are many similar functions using jQuery or other similar libraries.
var obj1 = {
"last_updated":1493307962,
"ttl":10,
"data":{
"stations":[
{
"station_id":"219",
"name":"Central Square - East Boston",
"short_name":"A32036",
"lat":42.37454454514976,
"lon":-71.03837549686432,
"region_id":10,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":19,
"eightd_has_key_dispenser":false
},
{
"station_id":"220",
"name":"Test 1",
"short_name":"Test 1",
"lat":0,
"lon":0,
"rental_methods":[
"KEY",
"CREDITCARD"
],
"capacity":0,
"eightd_has_key_dispenser":false
}
]
}
};
var obj2 = {
"last_updated":1493308075,
"ttl":10,
"data":{
"stations":[
{
"station_id":"219",
"num_bikes_available":7,
"num_bikes_disabled":1,
"num_docks_available":11,
"num_docks_disabled":0,
"is_installed":1,
"is_renting":1,
"is_returning":1,
"last_reported":1493283725,
"eightd_has_available_keys":false
},
{
"station_id":"220",
"num_bikes_available":0,
"num_bikes_disabled":0,
"num_docks_available":0,
"num_docks_disabled":0,
"is_installed":0,
"is_renting":0,
"is_returning":0,
"last_reported":0,
"eightd_has_available_keys":false
}
]
}
};
obj1.data.stations.forEach(function(item, i) {
Object.assign(item, obj2.data.stations[i])
});
console.log(obj1)
I assume you're not interested in the parameters surrounding the stations. You could use this code to get an array of stations with all the information based on station_id
obj1.data.stations.map(el1 => Object.assign({},el1,obj2.data.stations.filter(el2 => el2.station_id === el1.station_id)));
Where obj1 and obj2 are your JSONs.
"[
{
"0": {
"station_id": "219",
"num_bikes_available": 7,
"num_bikes_disabled": 1,
"num_docks_available": 11,
"num_docks_disabled": 0,
"is_installed": 1,
"is_renting": 1,
"is_returning": 1,
"last_reported": 1493283725,
"eightd_has_available_keys": false
},
"station_id": "219",
"name": "Central Square - East Boston",
"short_name": "A32036",
"lat": 42.37454454514976,
"lon": -71.03837549686432,
"region_id": 10,
"rental_methods": [
"KEY",
"CREDITCARD"
],
"capacity": 19,
"eightd_has_key_dispenser": false
},
{
"0": {
"station_id": "220",
"num_bikes_available": 0,
"num_bikes_disabled": 0,
"num_docks_available": 0,
"num_docks_disabled": 0,
"is_installed": 0,
"is_renting": 0,
"is_returning": 0,
"last_reported": 0,
"eightd_has_available_keys": false
},
"station_id": "220",
"name": "Test 1",
"short_name": "Test 1",
"lat": 0,
"lon": 0,
"rental_methods": [
"KEY",
"CREDITCARD"
],
"capacity": 0,
"eightd_has_key_dispenser": false
}
]"

Angular bind to filtered count

I have this array:
[
{
type: "hhh",
items: [
{
"name": "EGFR",
"type": "a",
"selected": true
}
]
},
{
type: "aaa",
items: [
{
"name": "mm",
"type": "b",
"selected": false
}
]
},
{
type: "ii",
items: [
{
"name": "pp",
"type": "bb",
"selected": true
}
]
}
]
I want to show a counter of the items with selected property "true".
I want it to be changed real time when change.
(Without watch and function)
Thnaks!
Here is the way:
var current_selected = {
get amount(){
var res = 0;
arr.forEach(function(item, i, arr) {
if (item.items[0].selected) res++;
})
return res;
}
}
Calling:
current_selected.amount
Fiddle
You can use JsonPath to get the count. Also using JsonPath has an added advantage of working on complex json structure. For the example you gave, you just need to include jsonpath js file and use the following in your script:
console.log(arr);
var filtered = jsonPath(arr, "$.[*].items[?(#.selected==true)]");
console.log(filtered);
console.log(filtered.length);
where arr is your json structure.
JsonPath can be got from :
https://code.google.com/archive/p/jsonpath/downloads
JsonPath help:
http://goessner.net/articles/JsonPath/
There might be updated version in other sources but that was the one I had worked on

Categories