Combine 2 JSON objects of unequal size with ID - javascript

Problem
I would like to have the below two JSON combined together using the ID and have the expected result as mentioned below. I have tried a few solutions that were available but none worked for my use case. Any suggestions will be great !!
Tried to do:
How to merge two json object values by id with plain Javascript (ES6)
Code
var json1 = [
{
"id":"A123",
"cost":"5020.67",
"fruitName":"grapes"
},
{
"id":"A456",
"cost":"341.30",
"fruitName":"apple"
},
{
"id":"A789",
"cost":"3423.04",
"fruitName":"banana"
}
];
var json2 = [
{
"id":"A123",
"quantity":"7"
},
{
"id":"A789",
"quantity":"10"
},
{
"id":"ABCD",
"quantity":"22"
}
];
Below is the code I tried:
var finalResult = [...[json1, json2].reduce((m, a) => (a.forEach(o => m.has(o.id) && Object.assign(m.get(o.id), o) || m.set(o.id, o)), m), new Map).values()];
Expected result:
[
{
"id":"A123",
"cost":"5020.67",
"fruitName":"grapes",
"quantity":"7"
},
{
"id":"A456",
"cost":"341.30",
"fruitName":"apple"
},
{
"id":"A789",
"cost":"3423.04",
"fruitName":"banana",
"quantity":"10"
},
{
"id":"ABCD",
"quantity":"22"
}
]

You can accomplish this fairly easily without getting too fancy. Here's the algorithm:
Put the items from json1 into an object by id, so that you can look them up quickly.
For each item in json2: If it already exists, merge it with the existing item. Else, add it to objectsById.
Convert objectsById back to an array. I've used Object.values, but you can also do this easily with a loop.
var json1 = [
{
"id":"A123",
"cost":"5020.67",
"fruitName":"grapes"
}, {
"id":"A456",
"cost":"341.30",
"fruitName":"apple"
}, {
"id":"A789",
"cost":"3423.04",
"fruitName":"banana"
}
];
var json2 = [
{
"id":"A123",
"quantity":"7"
}, {
"id":"A789",
"quantity":"10"
}
];
const objectsById = {};
// Store json1 objects by id.
for (const obj1 of json1) {
objectsById[obj1.id] = obj1;
}
for (const obj2 of json2) {
const id = obj2.id;
if (objectsById[id]) {
// Object already exists, need to merge.
// Using lodash's merge because it works for deep properties, unlike object.assign.
objectsById[id] = _.merge(objectsById[id], obj2)
} else {
// Object doesn't exist in merged, add it.
objectsById[id] = obj2;
}
}
// All objects have been merged or added. Convert our map to an array.
const mergedArray = Object.values(objectsById);

I think a few steps are being skipped in your reduce function. And it was a little difficult to read because so many steps are being combined in one.
One critical piece that your function does not account for is that when you add 2 numerical strings together, it concats the strings.
const stringTotal = "5020.67" + "3423.04" // result will be "5020.673423.04"
The following functions should give you the result you are looking for.
// calculating the total cost
// default values handles cases where there is no obj in array 2 with the same id as the obj compared in array1
const calcualteStringTotal = (value1 = 0, value2 = 0) => {
const total = parseFloat(value1) + parseFloat(value2)
return `${total}`
}
const calculateTotalById = (array1, array2) => {
const result = []
// looping through initial array
array1.forEach(outterJSON => {
// placeholder json obj - helpful in case we have multiple json in array2 with the same id
let combinedJSON = outterJSON;
// looping through second array
array2.forEach(innerJSON => {
// checking ids
if(innerJSON.id === combinedJSON.id) {
// calls our helper function to calculate cost
const updatedCost = calcualteStringTotal(innerJSON.cost, outterJSON.cost)
// updating other properties
combinedJSON = {
...outterJSON,
...innerJSON,
cost: updatedCost
}
}
})
result.push(combinedJSON)
})
return result
}
const combinedResult = calculateTotalById(json1, json2)

I figured that by using reduce I could make it work.
var finalResult = [...[json1, json2].reduce((m, a) => (a.forEach(o => m.has(o.id) && Object.assign(m.get(o.id), o) || m.set(o.id, o)), m), new Map).values()];

Related

Find Unique value from an array based on the array's string value (Javascript)

so I want to find unique values from an array.
so for example I have this array:
const mainArr = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884']
so I want to find the first matching value for each unique item.
for example, in the array, I have two strings with the shape prefix, six items with the size prefix, and two items with the height prefix.
so I want to output to be something like
const requiredVal = ["shape-10983", "size-2364", "height-3399"]
I want only the first value from any set of different values.
the simplest solution will be to iterate on the list and storing what you got in a dictionary
function removeSimilars(input) {
let values = {};
for (let value of input) {//iterate on the array
let key = value.splitOnLast('-')[0];//get the prefix
if (!(key in values))//if we haven't encounter the prefix yet
values[key] = value;//store that the first encounter with the prefix is with 'value'
}
return Object.values(values);//return all the values of the map 'values'
}
a shorter version will be this:
function removeSimilars(input) {
let values = {};
for (let value of input)
values[value.splitOnLast('-')[0]] ??= value;
return Object.values(values);
}
You could split the string and get the type and use it aks key for an object along with the original string as value. At result take only the values from the object.
const
data = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884'],
result = Object.values(data.reduce((r, s) => {
const [type] = s.split('-', 1);
r[type] ??= s;
return r;
}, {}));
console.log(result);
If, as you mentioned in the comments, you have the list of prefixes already available, then all you have to do is iterate over those, to find each first element that starts with that prefix in your full list of possible values:
const prefixes = ['shape', 'size', 'height'];
const list = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884']
function reduceTheOptions(list = [], prefixes = [], uniques = []) {
prefixes.forEach(prefix =>
uniques.push(
list.find(e => e.startsWith(prefix))
)
);
return uniques;
}
console.log(reduceTheOptions(list, prefixes));
Try this:
function getRandomSet(arr, ...prefix)
{
// the final values are load into the array result variable
result = [];
const randomItem = (array) => array[Math.floor(Math.random() * array.length)];
prefix.forEach((pre) => {
result.push(randomItem(arr.filter((par) => String(par).startsWith(pre))));
});
return result;
}
const mainArr = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884'];
console.log("Random values: ", getRandomSet(mainArr, "shape", "size", "height"));
I modified the #ofek 's answer a bit. cuz for some reason the ??= is not working in react project.
function removeSimilars(input) {
let values = {};
for (let value of input)
if (!values[value.split("-")[0]]) {
values[value.split("-")[0]] = value;
}
return Object.values(values);
}
create a new array and loop over the first array and check the existing of element before in each iteration if not push it to the new array

ES6 Filtering objects by unique attribute

I've an array of errors, each error has a non-unique param attribute.
I'd like to filter the array based on whether the param has been seen before.
Something like this:
const filteredErrors = [];
let params = [];
for(let x = 0; x < errors.length; x++) {
if(!params.includes(errors[x].param)) {
params.push(errors[x].param);
filteredErrors.push(errors[x]);
}
}
But I've no idea how to do this in ES6.
I can get the unique params const filteredParams = Array.from(new Set(errors.map(error => error.param)));
but not the objects themselves.
Pretty sure this is just a weakness in my understanding of higher order functions, but I just can't grasp it
You could destrucure param, check against params and add the value to params and return true for getting the object as filtering result.
As result you get an array of first found errors of the same type.
const
params = [],
filteredErrors = errors.filter(({ param }) =>
!params.includes(param) && params.push(param));
Instead of an array you can make use of an object to keep a map of existing values and make use of filter function
let params = {};
const filteredErrors = errors.filter(error => {
if(params[error.param]) return false;
params[error.param] = true;
return true;
});
i'd probably do it like this with a reduce and no need for outside parameters:
const filteredErrors = Object.values(
errors.reduce((acc, val) => {
if (!acc[val.param]) {
acc[val.param] = val;
}
return acc;
}, {}))
basically convert it into an object keyed by the param with the object as values, only setting the key if it hasn't been set before, then back into an array of the values.
generalized like so
function uniqueBy(array, prop) {
return Object.values(
array.reduce((acc, val) => {
if (!acc[val[prop]]) {
acc[val[prop]] = val;
}
return acc;
}, {}))
}
then just do:
const filteredErrors = uniqueBy(errors, 'param');
If your param has a flag identifier if this param has been seen before then you can simply do this.
const filteredErrors = errors.filter(({ param }) => param.seen === true);
OR
const filteredErrors = errors.filter((error) => error.param.seen);
errors should be an array of objects.
where param is one of the fields of the element of array errors and seen is one of the fields of param object.
You can do it by using Array.prototype.reduce. You need to iterate through the objects in the array and keep the found params in a Set if it is not already there.
The Set.prototype.has will let you find that out. If it is not present in the Set you add it both in the Set instance and the final accumulated array, so that in the next iteration if the param is present in your Set you don't include that object:
const errors = [{param: 1, val: "err1"}, {param: 2, val: "err2"}, {param: 3, val: "err3"}, {param: 2, val: "err4"}, {param: 1, val: "err5"}];
const { filteredParams } = errors.reduce((acc, e) => {
!acc.foundParams.has(e.param) && (acc.foundParams.add(e.param) &&
acc.filteredParams.push(e));
return acc;
}, {foundParams: new Set(), filteredParams: []});
console.log(filteredParams);

Compare two javascript arrays and remove duplicate entries

I am trying to compare two different arrays together, One previous and one current. The previous set of data contains:
[
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"}
]
The new set contains:
[
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"},
{"member_name":"Test1","item":"Shield"}
]
So as you can see here Test1 has obtained a new item. I've tried to compare these two arrays together a few ways but with no avail.
The methods I've tried:
This one just returns the entire array. Not individual items.
Items_Curr.filter(function(item) { return !Items_Prev.includes(item); });
This method just returns 3 Undefined's.
Items_Curr.map(e => { e.member_name });
I've been looking through trying to find a way of doing this but other posts just explain methods to determine change in simpler arrays.
E.G [a,b] - [a, b, c]
Update:
The end goal is I'd like to create a new array 'NewItems' which would contain an array of all the newly added names and items. So if there is change, I would like that to be broadcasted, if there is no change, then ignore until the function has been ran again.
Realistically you want to do something like:
[a, b, c] - [a, b]
Which would give you c. You can achieve this using .some, which allows you to "customize" the includes functionality.
See example below:
const arr1 = [
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"}
];
const arr2 = [
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"},
{"member_name":"Test1","item":"Shield"}
];
const res = arr2.filter(({member_name:a, item:x}) => !arr1.some(({member_name:b, item:y}) => a === b && x === y));
console.log(res);
If you know your properties will always be in the same order you can use JSON.stringify to serialize the objects and compare the results:
const Items_Prev = [
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"}
]
const Items_Curr = [
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"},
{"member_name":"Test1","item":"Shield"}
]
const serialized_Items_Prev = Items_Prev.map(i => JSON.stringify(i));
const NewItems = Items_Curr.filter(i => !serialized_Items_Prev.includes(JSON.stringify(i)));
console.log(NewItems);
I think something like this you need to do if keys of objects are not changing and new items are only added at last.
const array1 = [
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"}
];
const array2 = [
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"},
{"member_name":"Test1","item":"Shield"}
];
const compare = (array1, array2) => {
if (array1.length !== array2.length) {
return false;
}
for (let i = 0; i < array1.length; i += 1) {
if (array1[i].member_name !== array2[i].member_name) {
return false;
}
if (array1[i].item !== array2[i].item) {
return false;
}
}
return true;
};
console.log(compare(array1, array2));
If order of objects are changing, then you need to write sorting algo for array and then compare.
You can acheieve it using array methods filter() and findIndex()
Filter current array with out put of findIndex() function on previous array
var prevItems = [
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"}
]
var currItems = [
{"member_name":"Test1","item":"Sword"},
{"member_name":"Test2","item":"Sword"},
{"member_name":"Test1","item":"Shield"},
{"member_name":"Test2","item":"Shield"}
]
var newItems = currItems.filter(function(currItem ){
return prevItems.findIndex(function(prevItem){
return prevItem.member_name == currItem.member_name &&
prevItem.item == currItem.item
}) == -1
})
console.log(newItems)

Convert JSON to Array of Objects using lodash

I have a JSON object in NoSql database in this format. We are getting this data after migrating some records from some other database and these are multi-valued fields.(Basically we are trying to clean the data for further processing).
{
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
}
I want to add another key "bpTableDataName" in the same JSON which should have this format and values,
"bpTableDataName": [
{
"name": "aName",
"email": "aEmail",
"pwdid": "aPWID"
},
{
"name": "bName",
"email": "bEmail",
"pwdid": "bPWID"
},
{
"name": "cName",
"email": "cEmail",
"pwdid": "cPWID"
}
],
Is there a way we can achieve this using lodash?
Try following code -
o = {
"BPContName": "aName;bName;cName",
"BPContEmail": "aEmail;bEmail;cEmail",
"BPContPWID": "aPWID;bPWID;cPWID"
}
map = { "BPContName" : "name", "BPContEmail": "email", "BPContPWID": "pwdid" }
const result = _.reduce(o, (arr, v, k) => ( v.split(";").forEach((x,i) => _.set(arr, `${i}.${map[k]}`, x)), arr ), [])
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
You can use split() to split the values into an array.
Then iterate over the array and create the require json and then push that into results.
Check this out.
var data = {
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
}
var names = data.BPContName.split(';');
var emails = data.BPContEmail.split(';');
var pwids = data.BPContPWID.split(';');
var results = [];
for(var i = 0 ; i < names.length; i++) {
var obj = {
name: names[i],
email: emails[i],
pwdid: pwids[i]
}
results.push(obj);
}
console.log(results)
You could reduce the entries returned by Object.entries like this:
let obj = {
"BPContName": "aName;bName;cName",
"BPContEmail": "aEmail;bEmail;cEmail",
"BPContPWID": "aPWID;bPWID;cPWID"
}
let bpTableDataName = Object.entries(obj).reduce((r, [key, value]) => {
let splits = value.split(";");
key = key.replace("BPCont", "").toLowerCase();
splits.forEach((split, i) => (r[i] = r[i] || {})[key] = split)
return r;
}, [])
obj.bpTableDataName = bpTableDataName;
console.log(obj)
Object.entries returns an array of key-value pair. Loop through each of them
split the each value at ;
get the key by removing BPCont part and making it lowerCase
Loop through the splits and update specific keys of objects at each index
Update:
Since you have an extra d in the output's key, you can create a mapping object:
propertyMap = {
"BPContName": "name",
"BPContEmail": "email",
"BPContPWID": "pwdid"
}
And inside the reduce, change the replace code to this:
key = propertyMap[key]
Using Object.assign, Object.entries, Array#map and the spread operator make this trivial
const inputdata = {
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
};
const t1=Object.assign({},...Object.entries(inputdata).map(([k,v])=>({[k]:v.split(';')})));
inputdata.bpTableDataName=t1.BPContName.map((name,i)=>({name,email:t1.BPContEmail[i],pwdid:t1.BPContPWID[i]}));
console.log(inputdata);
Of course, it wouldn't be me without a one-liner
const obj = {
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
};
// one line to rule them all
obj.bpTableDataName=Object.entries(obj).reduce((r,[k,v])=>(v.split(';').forEach((v,i)=>(r[i]=r[i]||{})[{BPContName:'name',BPContEmail:'email',BPContPWID:'pwdid'}[k]]=v),r),[]);
//
console.log(obj);
Basically what you need is to zip it.
Snippet:
let obj = {"BPContName":"aName;bName;cName","BPContEmail":"aEmail;bEmail;cEmail","BPContPWID":"aPWID;bPWID;cPWID"},
res = _.zipWith(
..._.map(obj, v => v.split(';')),
(name, email, pwid) => ({name, email, pwid})
);
console.log(res)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Note, the sequence of the parameters we have to put such a way, the original object give us values when using Object.values or giving us keys when using Object.keys usually it is alphabetical order. But, In case in any env the order is not guranted we can sort it with a sequence of keys as a metadata.
Or else you can explicitly pass the arguments like:
(obj.BPContName.split(';'), obj.BPContEmail.split(';'), obj.BPContPWID.split(';'))
You can use lodash's _.flow() to create a function. Use _.map() with _.overArgs() to create a function that splits the values, format the key, and then converts them to an array of pairs using _.unzip(), for example [['name', 'x'], ['name', 'y']]. Transpose the array of arrays with _.unzip() to combine pairs of different properties. Then use _.map() to iterate, and convert each array of pairs to an object using _.fromPairs().
const { flow, partialRight: pr, map, unzip, overArgs, times, size, constant, split, fromPairs } = _
const keysMap = new Map([['BPContName', 'name'], ['BPContEmail', 'email'], ['BPContPWID', 'pwdid']])
const formatKey = key => keysMap.get(key)
const splitVals = pr(split, ';')
const fn = flow(
pr(map, overArgs(
(vals, k) => unzip([vals, times(size(vals), constant(k))]),
[splitVals, formatKey])
),
unzip, // transpose
pr(map, fromPairs) // convert each pairs array to object
)
const data = {
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
}
const results = fn(data)
console.log(results)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

concatenate 2 object arrays to one

I have an object array containing one property call name like this
var nameArr = [
{
"name":"john"
},
{
"name":"carl"
},
{
"name":"peter"
}
]
I have a another array called ageArr and it only contain property called age
var ageArr = [
{
"age":"22"
},
{
"age":"21"
},
{
"age":"32"
}
]
i want to concat these array and end result should result like this
var result = [
{
"age":"22",
"name":"john"
},
{
"age":"21",
"name":"carl"
},
{
"age":"32",
"name":"peter"
}
]
note that length of the both arrays always equal and dynamic. Is there any way i can do this without looping these array inside one another. Thank you.
You can use Object.assign() and map() and return new array.
var nameArr = [{"name":"john"},{"name":"carl"},{"name":"peter"}]
var ageArr = [{"age":"22"},{"age":"21"},{"age":"32"}]
var result = nameArr.map(function(e, i) {
return Object.assign({}, e, ageArr[i])
})
console.log(result)
Single forEach() function is enough.
var nameArr=[{name:"john"},{name:"carl"},{name:"peter"}],
ageArr=[{age:"22"},{age:"21"},{age:"32"}];
nameArr.forEach((v,i) => v.age = ageArr[i].age)
console.log(nameArr);
You can use the following code snippet.
var result = nameArr.map(function( obj, index ) {
var res = ageArr[index];
res.name = obj.name;
return res;
});
In the map, you can easily use
jQuery.extend()
to create a merge of two same index object.

Categories