How to groupBy object using lodash? - javascript

I am using ReactJS. I have an object which looks like this:
{
autumn=firstContactPersonName: "John",
autumn=firstContactPersonPhone: "46442644",
autumn=secondContactPersonName: "Jhonny",
autumn=secondContactPersonPhone: "46442644",
christmas=firstContactPersonName: "Tommy",
christmas=firstContactPersonPhone: "46442644",
christmas=secondContactPersonPhone: "Thomas",
winter=firstContactPersonPhone: "46442644",
winter=firstContactPersonName: "Peter",
winter=secondContactPersonName: "Tom",
winter=secondContactPersonPhone: "44664455"
}
How can I group them based on autumn, christmas, winter. So one object with autumn have the autumn fields, the second one christmas have the christmas field, and the third one winter have the winter fields?
And so on. All fields will have a segment name before =, so I can easily see what kind of segment they belong to.

This is achievable with pure JS, no need for lodash (which I'm not familiar with).
This function takes every property of your input object, splits the key around the equals sign for the season part (prefix) and rest (appendix). Afterwards it looks for an object in the output array with the same season and adds the properties to it. If there is no object with specified season, it creates a new one.
function processData(inputObj) {
var data = [];
for (var key in inputObj) {
var prefix = key.split("=")[0];
var appendix = key.split("=")[1];
var dataObject = data.find(o => o.season == prefix);
if (!dataObject) {
dataObject = {season: prefix};
data.push(dataObject);
}
dataObject[appendix] = inputObj[key];
}
return { data };
}
produces this output:
{
"data": [
{
"season": "autumn",
"firstContactPersonName": "John",
"firstContactPersonPhone": "46442644",
"secondContactPersonName": "Jhonny",
"secondContactPersonPhone": "46442644"
},
{
"season": "christmas",
"firstContactPersonName": "Tommy",
"firstContactPersonPhone": "46442644",
"secondContactPersonPhone": "Thomas"
},
{
"season": "winter",
"firstContactPersonPhone": "46442644",
"firstContactPersonName": "Peter",
"secondContactPersonName": "Tom",
"secondContactPersonPhone": "44664455"
}
]
}

You can produce your groups by looping over the keys in the object. Split the key on = and capture the results. The first element should be the group, the second the property to access within this group.
Create a new object if a group doesn't yet exist. Then set the property within the group equal to the value of the original object.
const input = {
"autumn=firstContactPersonName": "John",
"autumn=firstContactPersonPhone": "46442644",
"autumn=secondContactPersonName": "Jhonny",
"autumn=secondContactPersonPhone": "46442644",
"christmas=firstContactPersonName": "Tommy",
"christmas=firstContactPersonPhone": "46442644",
"christmas=secondContactPersonPhone": "Thomas",
"winter=firstContactPersonPhone": "46442644",
"winter=firstContactPersonName": "Peter",
"winter=secondContactPersonName": "Tom",
"winter=secondContactPersonPhone": "44664455"
};
const output = {};
for (const key in input) {
const [season, property] = key.split("=", 2);
if (!output[season]) output[season] = {};
output[season][property] = input[key];
}
console.log(output);
I find my explanation a bit confusion, but I hope that combined with the code snippet you understand what I'm saying.

Use Object.entries() to generate an array of [key, value] pairs, and iterate with Array.reduce(). For each pair, split the key by = to get the group key, and the value key, create the group's object if none exists, and assign the value to the object.
const data = {"autumn=firstContactPersonName":"John","autumn=firstContactPersonPhone":"46442644","autumn=secondContactPersonName":"Jhonny","autumn=secondContactPersonPhone":"46442644","christmas=firstContactPersonName":"Tommy","christmas=firstContactPersonPhone":"46442644","christmas=secondContactPersonPhone":"Thomas","winter=firstContactPersonPhone":"46442644","winter=firstContactPersonName":"Peter","winter=secondContactPersonName":"Tom","winter=secondContactPersonPhone":"44664455"}
const result = Object.entries(data)
.reduce((r, [k, v]) => {
const [gKey, key] = k.split('=') // get the key
r[gKey] = r[gKey] || {} // generate the object if it doesn't exist
r[gKey][key] = v // assign the value to the object
return r
}, {})
console.log(result);

Related

How can I filter an array containing varied object structures dynamically?

My usage will contain 6 different object types (some which contain double nested arrays), and any possibility of number of entries, on the condition that an given entry is unique.
These objects do not have a consistent unique identifier (a unique identifier is applied in backend on submission).
here is an example of what the array may look like (only 2 object types):
arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{timeline: "250", chars[{a},{b},{c}]},
{timeline: "220", chars[{d},{e},{f}]},
]
obj = {name:"aaa",time:22}
My intention is to gain a true or false based on if obj is inside arr
I have tried methods:
I was suggested this method & it errors: #<Object> is not a function
console.log(arr.find(obj))
I also found this suggestion but it will always return false even with the element present
console.log(arr.includes(object))
I tried this method myself, though it will always fail.
console.log(arr.filter((element, index) => element === obj)
With attempt 4, If I was to compare name, this would be insufficient as unique time would be ignored missing valid entries.
If I was to pass every field, this would also not work as each object may or may not have the field and cause error.
Its not really possible to manually pre-filter filter into distinct categories, as every time a new type is added it will need manually adding to the filter.
If there is a library which could do this that you know of, please let me know as that would be perfect. Otherwise any other suggestions (excluding separating arrays) Would be greatly appreciated.
Use arr.some() to check if the required object is present in the array.
To compare the objects, a simpler way is to Stringify both the Objects and compare them.
const arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{name: "aaa", chars: ["a", "b", "c"]},
{name: "bbb", chars: ["d", "e", "f"]},
]
const obj1 = {name:"aaa", time: 15}
const obj2 = {name:"aaa",chars: ["a", "b", "c"]}
console.log(arr.some((element) => JSON.stringify(element) === JSON.stringify(obj1))) // true
console.log(arr.some((element) => JSON.stringify(element) === JSON.stringify(obj2))) // true
Didn't give much thought on performance.
I didn't put much thought on performace here but this might help:
function checkObjectInArray(arr, obj) {
const res = arr.some((el) => deepEqual(el, obj));
console.log(res);
}
function deepEqual(obj1, obj2) {
if (Object.keys(obj1).length !== Object.keys(obj2).length) return false;
for (let prop in obj1) {
if (!obj2.hasOwnProperty(prop) || obj2[prop] !== obj1[prop]) {
return false;
}
}
return true;
}
in your case you can use it like:
arr = [
{ name: "aaa", time: 15 },
{ name: "aaa", time: 22 },
{ timeline: "250", data: ["2", "3", "4"] },
{ timeline: "251", data: ["2", "3", "4"] }, // what is chars[{d},{e},{f}] ?!
];
obj = { name: "aaa", time: 22 };
checkObjectInArray(arr, obj);
Observation : arr is not a valid array. Nested chars is not containing a valid value.
Solution : You can simply achieve the requirement by Just converting the JSON object into a JSON string and by comparing.
This solution works fine as you are just trying to find a single object in the passed arr.
Live Demo :
const arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{timeline: "250", chars: [{a: 1},{b: 2},{c: 3}]},
{timeline: "220", chars: [{d: 4},{e: 5},{f: 6}]},
];
const obj = {name:"aaa",time:22};
const res = JSON.stringify(arr).indexOf(JSON.stringify(obj)) !== -1 ? true : false;
console.log(res);

Javascript How to find a complete class object in an array

I have an array of objects that I use for a materials table datasource. I need to see if this row already exists in the datasource array before adding it.
There is no key value in this array. So I need to check if all items are unique
var datasource = [
{ Name: Jon, Address: 123 something }, {Name: Tyler , Address: 333 Something}
]
var rowtoAdd = [
{ Name: Jon, Address: 123 something }
]
const found = datasource.find(x => x.name == rowtoAdd.Name && x.Address == rowtoAdd.Address)
Is there a better way?
One way you can do this is by hashing the elements. Your current solution takes O(n) time each time you need to check. if you create a hashtable it will take 0(n) to create the table but the run time will O(1) for all consecutive run.
var datasource = [
{ "Name": "Jon", "Address": "123 something" }, {"Name": "Tyler" , "Address": "333 Something"}
]
var map = datasource.reduce(function(map, obj) {
map[obj.Name+obj.Address] = true;
return map;
}, {});
rowtoAdd = { "Name": "Jon", "Address": "123 something" }
if(!map[rowtoAdd.Name+rowtoAdd.Address])
datasource.push(rowtoAdd)
So, in sort reduce your array to a hashmap

Merge objects with corresponding key values from two different arrays of objects

I've got two arrays that have multiple objects
[
{
"name":"paul",
"employee_id":"8"
}
]
[
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
}
]
How can I achieve the following with either ES6 or Lodash?
[
{
"name":"paul",
"employee_id":"8"
"data": {
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
}
]
I can merge but I'm not sure how to create a new child object and merge that in.
Code I've tried:
school_data = _.map(array1, function(obj) {
return _.merge(obj, _.find(array2, {employee_id: obj.e_id}))
})
This merges to a top level array like so (which is not what I want):
{
"name":"paul",
"employee_id":"8"
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
The connector between these two is "employee_id" and "e_id".
It's imperative that it's taken into account that they could be 1000 objects in each array, and that the only way to match these objects up is by "employee_id" and "e_id".
In order to match up employee_id and e_id you should iterate through the first array and create an object keyed to employee_id. Then you can iterate though the second array and add the data to the particular id in question. Here's an example with an extra item added to each array:
let arr1 = [
{
"name":"mark",
"employee_id":"6"
},
{
"name":"paul",
"employee_id":"8"
}
]
let arr2 = [
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
},
{
"years_at_school": 12,
"department":"Arr",
"e_id":"6"
}
]
// empObj will be keyed to item.employee_id
let empObj = arr1.reduce((obj, item) => {
obj[item.employee_id] = item
return obj
}, {})
// now lookup up id and add data for each object in arr2
arr2.forEach(item=>
empObj[item.e_id].data = item
)
// The values of the object will be an array of your data
let merged = Object.values(empObj)
console.log(merged)
If you perform two nested O(n) loops (map+find), you'll end up with O(n^2) performance. A typical alternative is to create intermediate indexed structures so the whole thing is O(n). A functional approach with lodash:
const _ = require('lodash');
const dataByEmployeeId = _(array2).keyBy('e_id');
const result = array1.map(o => ({...o, data: dataByEmployeeId.get(o.employee_id)}));
Hope this help you:
var mainData = [{
name: "paul",
employee_id: "8"
}];
var secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
var finalData = mainData.map(function(person, index) {
person.data = secondaryData[index];
return person;
});
Sorry, I've also fixed a missing coma in the second object and changed some other stuff.
With latest Ecmascript versions:
const mainData = [{
name: "paul",
employee_id: "8"
}];
const secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
// Be careful with spread operator over objects.. it lacks of browser support yet! ..but works fine on latest Chrome version for example (69.0)
const finalData = mainData.map((person, index) => ({ ...person, data: secondaryData[index] }));
Your question suggests that both arrays will always have the same size. It also suggests that you want to put the contents of array2 within the field data of the elements with the same index in array1. If those assumptions are correct, then:
// Array that will receive the extra data
const teachers = [
{ name: "Paul", employee_id: 8 },
{ name: "Mariah", employee_id: 10 }
];
// Array with the additional data
const extraData = [
{ years_at_school: 6, department: "Mathematics", e_id: 8 },
{ years_at_school: 8, department: "Biology", e_id: 10 },
];
// Array.map will iterate through all indices, and gives both the
const merged = teachers.map((teacher, index) => Object.assign({ data: extraData[index] }, teacher));
However, if you want the data to be added to the employee with an "id" matching in both arrays, you need to do the following:
// Create a function to obtain the employee from an ID
const findEmployee = id => extraData.filter(entry => entry.e_id == id);
merged = teachers.map(teacher => {
const employeeData = findEmployee(teacher.employee_id);
if (employeeData.length === 0) {
// Employee not found
throw new Error("Data inconsistency");
}
if (employeeData.length > 1) {
// More than one employee found
throw new Error("Data inconsistency");
}
return Object.assign({ data: employeeData[0] }, teacher);
});
A slightly different approach just using vanilla js map with a loop to match the employee ids and add the data from the second array to the matching object from the first array. My guess is that the answer from #MarkMeyer is probably faster.
const arr1 = [{ "name": "paul", "employee_id": "8" }];
const arr2 = [{ "years_at_school": 6, "department": "Mathematics", "e_id": "8" }];
const results = arr1.map((obj1) => {
for (const obj2 of arr2) {
if (obj2.e_id === obj1.employee_id) {
obj1.data = obj2;
break;
}
}
return obj1;
});
console.log(results);

How to create a new json out of three jsons?

I have 3 different jsons, I need to extrapolate some data from each and create a new json with it. The three jsons have an id identifier in common, a unique identifier, so We could use that as a match since they are actually three different big jsons.
On json one we have "id":"265", on two and three "article_id":"265", so these can be the reference point when we loop.
I never worked with json this way so I wouldn't know how to approach it. I have put jQuery and JS as tags as they're what I know best.
1
{
"id":"265",
"title":"Battle of Gettysburg",
"page_id":"4849",
"language_id":"en",
"original_time":"July 1\u20133, 1863"
}
2
{
"id":"185",
"original_name":"United States",
"country_id":"24",
"article_id":"265"
}
3
{
"id":"73",
"month":"July",
"year":"1863",
"suffix":"",
"article_id":"265"
}
So the end result I am looking for is a single json exactly like this, we take id and title as objects from json 1, then we grab original_name from json two and year object from json three and we'll have:
{
"id":"265",
"title":"Battle of Gettysburg",
"original_name":"United States",
"year":"1863"
}
NOTE
The json above are just examples, in reality they are three huge lists, what I could do (manually), is to join them in order to have a single json.
There is some terminology confusion here; based on your comments you could be asking one of two very different questions. Fortunately one of them is very simple to answer so let's do both.
(I am handwaving past the details of loading json strings into the browser and converting them into javascript objects.)
If you have three objects
...then this is just a matter of plucking out the fields you need individually when constructing an output object:
var in1 = {
"id": "265",
"title": "Battle of Gettysburg",
"page_id": "4849",
"language_id": "en",
"original_time": "July 1\u20133, 1863"
};
var in2 = {
"id": "185",
"original_name": "United States",
"country_id": "24",
"article_id": "265"
}
var in3 = {
"id": "73",
"month": "July",
"year": "1863",
"suffix": "",
"article_id": "265"
}
// construct a new object using the selected fields
// from each object in1, in2, or in3:
var out = {
id: in1.id,
title: in1.title,
original_name: in2.original_name,
year: in3.year
}
console.log(out);
If you have three lists of objects:
...in this case it's a lot more complicated (and a lot more interesting). In this case you would need to match fields from the objects in each list which share the same IDs.
The following is definitely not the most efficient or memory-conserving way to do this; I've spread things out to (hopefully) make it easier to follow what it's doing.
I'm making two assumptions:
within each list, all IDs are unique (meaning you won't have two objects with the same ID in one JSON file)
Every ID will appear in all three lists (meaning you don't need to handle missing fields in output)
/* Again handwaving past loading JSON strings and parsing
them into javascript objects, we'll just start with
three arrays: */
var input1 = [{
"id": "265",
"title": "Battle of Gettysburg",
"page_id": "4849",
"language_id": "en",
"original_time": "July 1\u20133, 1863"
},
{
"id": "1",
"title": "Foo",
"page_id": "123",
"language_id": "en",
"original_time": "July 1\u20133, 1863"
}
];
var input2 = [{
"id": "1",
"original_name": "Bar",
"country_id": "24",
"article_id": "265"
},
{
"id": "265",
"original_name": "United States",
"country_id": "24",
"article_id": "265"
}
]
var input3 = [{
"id": "1",
"month": "July",
"year": "Baz",
"suffix": "",
"article_id": "265"
},
{
"id": "265",
"month": "July",
"year": "1863",
"suffix": "",
"article_id": "265"
}
]
/* It would be much easier to find corresponding IDs
across these arrays if they weren't arrays. We'll
start by converting them into objects keyed by the
item ids: */
var convertArray = function(arr) {
var output = {};
arr.forEach(function(o) {
output[o.id] = o;
});
return output;
}
var obj1 = convertArray(input1);
var obj2 = convertArray(input2);
var obj3 = convertArray(input3);
/* Now if we need to find (say) the object with id "foo", we don't
need to search the whole array, but can just use `obj1["foo"]` or
`obj1.foo`.
The last step is to iterate over the list of IDs and repeatedly
do basically the same thing as in the "if you have three objects"
part above. The only difference is that we need to access the
object with the same ID in each of the input lists: */
var constructOutput = function(in1, in2, in3) {
var output = []; // we'll be outputting a list of objects again.
// step through every ID (assuming in1 contains all of them):
Object.keys(in1).forEach(function(id) {
var obj = {
id: id,
title: in1[id].title,
original_name: in2[id].original_name,
year: in3[id].year
}
output.push(obj);
});
return output;
}
var final = constructOutput(obj1, obj2, obj3)
console.log(final)
Essentially what you have to do is mimic a SQL JOIN using JavaScript objects:
Use JSON.parse() on all three JSON collections to turn them into arrays of objects.
Iterate through JSON 1 objects; for each object...
Iterate through JSON 2 objects, testing if article ID matches the ID from JSON 1 that we are iterating over. Save this object.
Iterate through JSON 3 objects, testing if ID matches the ID of the object we found from JSON 2. Save this object.
After you have all three objects, make a new object literal that contains only the fields you want:
{
Id: obj1.id,
Title: obj1.title,
Original_name: obj2.original_name,
Year: obj3.year
}
Should you want to combine n number of JSON objects, e.g. a list of objects you can take a functional approach and utilise reduce + filter.
const data = [{
"id":"265",
"title":"Battle of Gettysburg",
"page_id":"4849",
"language_id":"en",
"original_time":"July 1\u20133, 1863"
},
{
"id":"185",
"original_name":"United States",
"country_id":"24",
"article_id":"265"
},
{
"id":"73",
"month":"July",
"year":"1863",
"suffix":"",
"article_id":"265"
}];
const final = data.reduce((accu, { id, title }, index, array) => {
// Find any related objects
const matches = array.filter(data => data.article_id === id);
if (matches.length) {
// Flatten them for ease of access. Duplicate keys will override.
const flat = matches.reduce((arr, item) => ({ ...arr, ...item }), [])
// Return new object
return accu.concat({
...flat,
id,
title,
});
}
return accu;
}, []);
console.log(final, '<<')
// Witness
document.getElementById('results').innerHTML = JSON.stringify(final);
<div id="results" style="font-family: Courier; font-size 14px; color: #fff; background: #000; padding: 20px; max-width: 80vw;"></div>
Edited*
Maybe this is what you need?
let arrPages = [{
"id":"265",
"title":"Battle of Gettysburg",
"page_id":"4849",
"language_id":"en",
"original_time":"July 1\u20133, 1863"
}];
let arrArticles = [{
"id":"185",
"original_name":"United States",
"country_id":"24",
"article_id":"265"
},
{
"id":"73",
"month":"July",
"year":"1863",
"suffix":"",
"article_id":"265"
}];
let getResult = (arrInput, arrCompare) => {
let joinedItems = [];
arrInput.forEach(item => {
let newItem = { id: item.id, title: item.title };
arrCompare.forEach(subItem => {
if(subItem.article_id !== undefined && subItem.article_id === item.id){
if(subItem.original_name !== undefined)
newItem.original_name = subItem.original_name;
if(subItem.year !== undefined)
newItem.year = subItem.year;
}
});
joinedItems.push(newItem);
});
return joinedItems;
};
let result = getResult(arrPages, arrArticles);
console.log(result);
In the first part of the code i create a var that has the json data.
To solve the problema i create 2 functions, the order of the creation dosen't metter, the first function getJSONData() take the json data as parameter and return a object filtered by the keys defined in the array keys. The secound function just check if the current key is present in the array of keys, this function could be replaced by the jQuery.inArray() method.
// JSON data
var json = [{
"id":"265",
"title":"Battle of Gettysburg",
"page_id":"4849",
"language_id":"en",
"original_time":"July 1\u20133, 1863"
},
{
"id":"185",
"original_name":"United States",
"country_id":"24",
"article_id":"265"
},
{
"id":"73",
"month":"July",
"year":"1863",
"suffix":"",
"article_id":"265"
}]
// keys that i want
var keys = ["title", "original_name", "year"];
// var that will have the filtered data
var newJSON = getJSONData(json);
console.log(JSON.stringify(newJSON))
// this is the main function of the code
// here we iterate in the json creating a new object that has all the tags definid in the keys array
function getJSONData(arrayJSON){
var JSONFiltered = {};
for(var i in arrayJSON){
for(var key in arrayJSON[i]){
if(hasElement(key)){
JSONFiltered[key] = arrayJSON[i][key];
}
}
}
return JSONFiltered;
}
// this function is used to check a key is present in the array of keys
function hasElement(key){
for(var elem in keys){
if(keys[elem] == key) return true;
}
return false;
}

acessing new object from Object.keys and forEach method javascript

I'm trying to create a new object (newobj) with new keys and props from a poorly structured existing array of object (arrays?) ex.
[{"product":["1009", "name", "price", "image", "description"]},
{"product":["1004", "name2", "price2", "image2", "description2"]}]
I'm getting result I want but newobj does not update outside of the scope of the forEach method (more than 1 result). My question is what am I not getting ? Is forEach incorrect method with this type obj?
var newobj = {};
Object.keys(oldobj).forEach(function(prop) {
newobj["id"] = Number(oldobj[prop]["product"][0]),
newobj["name"] = oldobj[prop]["product"][1],
newobj["price"] = Number(oldobj[prop]["product"][3]),
newobj["image"] = "url" + oldobj[prop]["product"][0] + ".jpg",
newobj["description"] = oldobj[prop]["product"][2];
// this works
// console.log(JSON.stringify(newobj));
});
// this only updated with one
app.locals.newobj = newobj;
I've also tried mapping (w/ underscore) but I have the same result, I can't access outside scope.
_.each(mappedobj, function(prop) {
_.each(prop["product"][0], function(vals){
newobj["id"] = Number(prop["product"][0]);
console.log(JSON.stringify(newobj));
});
});
If you want all the values from the old object, you need to make newobj an array of objects. You can use .map() to do this transformation.
Object and array destructuring is a convenient way to avoid all those hard-coded indexes. And by naming the parameter variables properly, you can use object literal shorthand to create the resulting objects more easily.
var oldobj = [{
"product": ["1009", "name", "price", "image", "description"]
},
{
"product": ["1004", "name2", "price2", "image2", "description2"]
}
];
var newobj = oldobj.map(({product: [id, name, price, url, description]}) =>
({id: Number(id), name, price: Number(price), url: `url${url}.jpg`, description})
);
console.log(newobj);
Try this
let newObject = _.map(oldObject, (item) => {
return {
id: item.product[0],
name: item.product[1],
price: item.product[2],
image: item.product[3],
description: item.product[4]
};
});
If you want to convert an array of poorly structured object to an array of well structured ones, you can use Array.prototype.map from VanillaJS:
const data = [
{"product": ["1009", "name", "120", "image", "description"]},
{"product": ["1004", "name2", "250", "image2", "description2"]},
{"product": ["1012", "name3", "85", "image3", "description3"]}
];
const products = data.map(({ product }) => {
const [id, name, price, image, description] = product;
return {
id: Number(id),
name,
price: Number(price),
image: `url${image}.jpg`,
description
};
});
console.log(products);

Categories