How to change structure from array to object with unique objects? - javascript

Input Array
const data = [
{
name: "USD",
value: 200,
type: "sell"
},
{
name: "USD",
value: 50,
type: "buy"
},
{
name: "EUR",
value: 150,
type: "buy"
},
{
name: "USD",
value: 200,
type: "sell"
}
];
I need to create a function which will convert this array (data) to this
{
USD: {
sold: 400,
bought: 50
},
EUR: {
sold: 0,
bought: 150,
}
}
Snippet
const data = [{
name: "USD",
value: 200,
type: "sell"
},
{
name: "USD",
value: 50,
type: "buy"
},
{
name: "EUR",
value: 150,
type: "buy"
},
{
name: "USD",
value: 200,
type: "sell"
}
];
const convert = (data) => {
return data.reduce((acc, item, i) => {
if (!acc.hasOwnProperty(item.name)) {
acc[item.name] = {
sold: item.type === "sell" && item.value ? item.value : 0,
bought: item.type === "buy" && item.value ? item.value : 0
};
} else {
acc[item.name] = {
sold: item.type === "sell" ? (acc[item.name].sold += item.value) : 0,
bought: item.type === "buy" ? (acc[item.name].bought += item.value) : 0 };
}
return acc;
}, {});
}
console.log(convert(data))
But it returned me the not correct information, I don't understand how I can get the sum of the already existing item and the next one. In else I try to sum the existing value with a new one.
But not sure why it returns to me an incorrect value.

This should work:
const result = {};
data.forEach(item => {
if (!result[item.name]) {
result[item.name] = {
sold: 0,
bought: 0
};
}
if (item.type === "sell") {
result[item.name].sold += item.value;
} else if (item.type === "buy") {
result[item.name].bought += item.value;
}
});
console.log(result);

Your code is having problems because on each iteration you're a) replacing the object for each currency b) within that object either just setting the value, or setting zero - you're not adding to the value, just replacing it.
Here's a version using reduce that will work as intended.
const data=[{name:"USD",value:200,type:"sell"},{name:"USD",value:50,type:"buy"},{name:"EUR",value:150,type:"buy"},{name:"USD",value:200,type:"sell"}];
function convert(data) {
return data.reduce((acc, item) => {
// Destructure the properties from `item`, assigning
// `type` a new name
const { name, value, type: oldType } = item;
// Set the new type to either bought/sold depending
// on the old type
const type = oldType === 'buy' ? 'bought': 'sold';
// If the currency doesn't already exist on
// the accumulator add it, and set the value to
// a new object
acc[name] ??= { bought: 0, sold: 0 };
// Update the value of the type
acc[name][type] += value;
// Return the accumulator
return acc;
}, {});
}
console.log(convert(data));
Additional documentation
Nullish coalescing assignment (??=)
Destructuring assignment

You could take an object for getting the right property for adding the value by type.
const
data = [{ name: "USD", value: 200, type: "sell" }, { name: "USD", value: 50, type: "buy" }, { name: "EUR", value: 150, type: "buy" }, { name: "USD", value: 200, type: "sell" }],
result = data.reduce((r, { name, value, type }) => {
const types = { sell: 'sold', buy: 'bought' };
r[name] ??= { sold: 0, bought: 0 };
r[name][types[type]] += value;
return r;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Show ID of Duplicate Values in Array of Object

I found here this code and works good. But I also need to know the "id" which is multible in use. e.g. in another array
var data = [
{"id":"1","Group":"Wohnzimmer","Light":"Diele", "type":"ct"},
{"id":"1","Group":"Wohnzimmer","Light":"Diele", "type":"ct"},
{"id":"2","Group":"Wohnzimmer","Light":"Diele", "type":"bri"},
{"id":"3","Group":"Wohnzimmer","Light":"Diele", "type":"color"},
{"id":"3","Group":"Wohnzimmer","Light":"Diele", "type":"color"},
]
var a = data.reduce((accumulator, current) => {
if (checkIfAlreadyExist(current)) {
return accumulator;
} else {
return [...accumulator, current];
}
function checkIfAlreadyExist(currentVal) {
return accumulator.some((item) => {
return (item.id === currentVal.id &&
item.Light === currentVal.Light &&
item.type === currentVal.type);
});
}
}, []);
console.log(a);
Reduced (it works fine!):
[{
Group: "Wohnzimmer",
id: "1",
Light: "Diele",
type: "ct"
}, {
Group: "Wohnzimmer",
id: "2",
Light: "Diele",
type: "bri"
}, {
Group: "Wohnzimmer",
id: "3",
Light: "Diele",
type: "color"
}]
Now, I need also the result of the deleted objects, like the following:
[{
Group: "Wohnzimmer",
id: "1",
Light: "Diele",
type: "ct"
},{
Group: "Wohnzimmer",
id: "3",
Light: "Diele",
type: "color"
}]
You can do this efficiently in linear time:
let key = obj => [obj.id, obj.Light, obj.type].join('##')
let seen = new Set,
unique = [],
removed = []
for (let obj of data) {
let k = key(obj);
(seen.has(k) ? removed : unique).push(obj)
seen.add(k)
}
use array.prototype.map and array.prototype.some
var values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName2' }
];
var valueArr = values.map(function(item){ return item.name });
var isDuplicate = valueArr.some(function(item, idx){
return valueArr.indexOf(item) != idx
});
console.log(isDuplicate);
Using you current logic you could archive your preferred output with small changes, use a object instead with two arrays instead.
var data = [
{ id: "1", Group: "Wohnzimmer", Light: "Diele", type: "ct" },
{ id: "1", Group: "Wohnzimmer", Light: "Diele", type: "ct" },
{ id: "2", Group: "Wohnzimmer", Light: "Diele", type: "bri" },
{ id: "3", Group: "Wohnzimmer", Light: "Diele", type: "color" },
{ id: "3", Group: "Wohnzimmer", Light: "Diele", type: "color" },
];
var { unique, removed } = data.reduce(
(accumulator, current) => {
if (checkIfAlreadyExist(current)) {
return {
...accumulator,
removed: [...accumulator.removed, current],
};
} else {
return {
...accumulator,
unique: [...accumulator.unique, current],
};
}
function checkIfAlreadyExist(currentVal) {
return accumulator.unique.some((item) => {
return (
item.id === currentVal.id &&
item.Light === currentVal.Light &&
item.type === currentVal.type
);
});
}
},
{
unique: [],
removed: [],
}
);
console.log("Unique");
console.log(unique);
console.log("Removed");
console.log(removed);
Just create another array to store deleted items and if checkIfAlreadyExist returns true push current into the array.
var data = [
{"id":"1","Group":"Wohnzimmer","Light":"Diele", "type":"ct"},
{"id":"1","Group":"Wohnzimmer","Light":"Diele", "type":"ct"},
{"id":"2","Group":"Wohnzimmer","Light":"Diele", "type":"bri"},
{"id":"3","Group":"Wohnzimmer","Light":"Diele", "type":"color"},
{"id":"3","Group":"Wohnzimmer","Light":"Diele", "type":"color"},
]
var deleted = []
var a = data.reduce((accumulator, current) => {
if (checkIfAlreadyExist(current)) {
deleted.push(current)
return accumulator;
} else {
return [...accumulator, current];
}
function checkIfAlreadyExist(currentVal) {
return accumulator.some((item) => {
return (item.id === currentVal.id &&
item.Light === currentVal.Light &&
item.type === currentVal.type);
});
}
}, []);
console.log(a);
console.log(deleted)

JavaScript: How to map two objects to get the output which map the Id of first object to the name of other object?

I am trying to make a function that maps the data2 object value to the name of the data1.
I tried to iterate data2 object2 with data1 but it is not working properly. I am able to map them but not getting the value of data1 when it is not there in data2.
Is there any way I can map properly to get the desired output mentioned in the code?
let data1 = {
attributes: [{
Id: 'test1',
Name: 'Test1',
Type: 'date'
},
{
Id: 'test2',
Name: 'Test2',
Type: 'string'
},
{
Id: 'test3',
Name: 'Test3',
Type: 'string'
},
{
Id: 'test4',
Name: 'Test4',
Type: 'boolean'
}
]
};
let data2 = {
value: [{
test1: '10-12-2021',
test2: '4',
dummy: 'ignore me'
},
{
test3: '3',
test4: true,
abc: 'ignore me'
},
{
test1: '12-12-2023',
test3: '42',
dummy1: 'ignore me'
}
]
};
//output
let ouput = {
rows: [{
Test1: '10/12/2021',
Test2: '4',
Test3: '',
Test4: ''
},
{
Test1: '',
Test2: '',
Test3: '3',
Test4: 'Y'
},
{
Test1: '12/12/2023',
Test2: '',
Test3: '42',
Test4: ''
}
]
};
//function
function mapper(data1, data2) {
let formattedValue = [];
data2.value.forEach(val => {
let data = {};
for (let prop in val) {
let name;
const filter = data1.attributes.filter(el => el.Id === prop)[0];
if (filter) {
name = filter.Name;
switch (filter.Type) {
case 'boolean':
data[name] =
val[prop] === true ? 'Y' : val[prop] === false ? 'N' : '';
break;
case 'date':
data[name] = new Date(val[prop]).toLocaleDateString();
break;
default:
data[name] = val[prop];
break;
}
}
}
formattedValue.push(data);
});
return formattedValue;
}
console.log(mapper(data1, data2));
same if I pass data2 as empty value I am looking to get below output
let data1 = {
attributes: [
{
Id: 'test1',
Name: 'Test1',
Type: 'string'
},
{
Id: 'test2',
Name: 'Test2',
Type: 'string'
},
{
Id: 'test3',
Name: 'Test3',
Type: 'string'
},
{
Id: 'test4',
Name: 'Test4',
Type: 'boolean'
}
]
};
let data2 = {
value: [
]
};
//output
let ouput = {
rows: [
{
Test1: '',
Test2: '',
Test3: '',
Test4: ''
},
]
};
const data1 = {attributes:[{Id:'test1',Name:'Test1',Type:'date'},{Id:'test2',Name:'Test2',Type:'string'},{Id:'test3',Name:'Test3',Type:'string'},{Id:'test4',Name:'Test4',Type:'boolean'}]}
data2 = {value:[{test1:'10-12-2021',test2:'4'},{test3:'3',test4:true},{test1:'12-12-2023',test3:'42'}]};
data3 = {value: []}
const mapper = (x, y) => {
const row = x.attributes.map(e => [e.Id, e.Name])
format = e => { switch (true) {
case typeof e === 'boolean':
return e ? 'Y' : 'N'
case isNaN(e) && !isNaN(Date.parse(e)):
return new Date(e).toLocaleDateString()
default: return e
}}
const rows = (y.value.length ? y.value : [1]).map(e =>
Object.fromEntries(row.map(([k, v]) => [v, format(e[k]) ?? '']))
)
return { rows }
}
console.log(mapper(data1, data2))
console.log(mapper(data1, data3))
.as-console-wrapper { max-height: 100% !important; top: 0; }
Explanation:
y.value.length ? y.value : [1]
Checks if data.value is empty, if not returns data.value. If yes returns array with 1 element (can be any value, it is just needed to do 1 iteration to meet your requirement about empty data, so value doesn't really matter here.
Rest of code should be pretty clear, if not, let me know.
UPDATE
because you want minimum one set of values even data2.values is empty. My already given solution will work if we add empty object when array is empty, so adding below will fix it.
// I had to add below lines to get your desired result
// if `data2.value` is empty array
const arrayToLoop = [...data2.value];
if(arrayToLoop.length === 0) {
arrayToLoop.push({});
}
and now we will loop variable arrayToLoop rather than data2.value
also wrote small function used to convert the value as per given type, you can extend it as per your own need
function parseValue(type, value){
if(value.length==0){
return '';
}
switch(type.toLowerCase()){
case 'boolean':
return value === true ? 'Y' : value === false ? 'N' : '';
case 'date':
return new Date(value).toLocaleDateString();
default:
return value;
}
}
first I will get all the key and label mapping, and will look all the values for each key.
notice how I done below.
let me know if you face any prob.
function parseValue(type, value){
if(value.length==0){
return '';
}
switch(type.toLowerCase()){
case 'boolean':
return value === true ? 'Y' : value === false ? 'N' : '';
case 'date':
return new Date(value).toLocaleDateString();
default:
return value;
}
}
function BusinessObjectMapper(data1, data2){
const arrayToLoop = [...data2.value];
if(arrayToLoop.length === 0) {
arrayToLoop.push({});
}
const rows = arrayToLoop.map(v=>{
let r = {};
data1.attributes.forEach(o=>{
let {Id, Name, Type} = o;
r[Name]=parseValue(Type, v[Id]||'');
})
return r;
})
return {
rows
};
}
let data1 = {
attributes: [
{
Id: 'test1',
Name: 'Test1',
Type:'string'
},
{
Id: 'test2',
Name: 'Test2',
Type:'boolean',
},
{
Id: 'test3',
Name: 'Test3',
Type:'date'
},
{
Id: 'test4',
Name: 'Test4',
Type:'un-known',
}
]
};
let data2 = {
value: [
{
test1: '1',
test2: true
},
{
test3: '02/02/2020',
test4: '42'
},
{
test1: 'deepak',
test3: '01/01/2021'
}
]
};
let data2_empty = {
value: []
}
console.log(BusinessObjectMapper(data1, data2));
console.log(BusinessObjectMapper(data1, data2_empty));
I solved the object type problem by using the spread operator and Array.prototype.reduce().
const data1 = {
attributes: [
{
Id: "test1",
Name: "Test1",
Type: "date",
},
{
Id: "test2",
Name: "Test2",
Type: "string",
},
{
Id: "test3",
Name: "Test3",
Type: "string",
},
{
Id: "test4",
Name: "Test4",
Type: "boolean",
},
],
};
const data2 = {
value: [
{
test1: "10-12-2021",
test2: "4",
},
{
test3: "3",
test4: true,
},
{
test1: "12-12-2023",
test3: "42",
},
],
};
//function
const data11 = {
attributes: [
{
Id: "test1",
Name: "Test1",
Type: "string",
},
{
Id: "test2",
Name: "Test2",
Type: "string",
},
{
Id: "test3",
Name: "Test3",
Type: "string",
},
{
Id: "test4",
Name: "Test4",
Type: "boolean",
},
],
};
const data12 = {
value: [],
};
function mapper(data1, data2) {
const columns = data1.attributes.reduce((prev, item) => {
prev[item["Id"]] = item;
return prev;
}, {});
const emptyValues = Object.keys(columns).reduce((prev, item) => {
prev[columns[item].Name] = "";
return prev;
}, {});
const getValueByType = (key, v) => {
switch (columns[key].Type) {
case "boolean":
return v ? "Y" : "N";
case "date":
return new Date(v).toLocaleDateString();
default:
return v;
}
};
return {
rows:
(data2.value &&
(data2.value.length == 0
? [emptyValues]
: data2.value.map((row) =>
Object.entries(row).reduce(
(rlt, [key, v]) => {
rlt[columns[key].Name] = getValueByType(key, v);
return rlt;
},
{ ...emptyValues }
)
))) ||
[],
};
}
const result1 = mapper(data1, data2);
console.log(result1.rows);
const result2 = mapper(data11, data12);
console.log(result2);
#ulou's answer is great!. It can be done using reduce. I updated a source a little.
const mapper = (arr1, arr2) => {
const keys = arr1.attributes.map(item => ({ Id: item.Id, Name: item.Name }));
const format = e => {
switch (true) {
case typeof e === 'boolean': return e ? 'Y' : 'N'
case isNaN(e) && !isNaN(Date.parse(e)):
return new Date(e).toLocaleDateString()
default: return e
}
}
const mapped = (arr2.value ?? [0]).map(item => keys.reduce((prev, cur) => (
{ ...prev, [cur.Name]: format(item[cur.Id] ?? '') }
), {}));
return { rows: mapped };
}
console.log(mapper(data1, data2));
console.log(mapper(data1, []));

Removing Duplicates from Array of Objects

Hi I m trying to remove duplicates from array of object using id, but id's are null then the object should contain those null id's and remove others which are duplicate
Here is the array of objects example:
const arr = [
{
id: 6652,
value: "erger"
},
{
id: 6652,
value: "sdfs"
},
{
id: 6653,
value: "sdgdfg"
},
{
id: 6000,
value: "trgd"
},
{
id: 6667,
value: "asdf"
},
{
id: 6667,
value: "fdg"
},
{
id: 6668,
value: "dfgr"
},
{
id: null,
value: "fg"
},
{
id: null,
value: "dfgdf"
},
{
id: null,
value: "fg"
},
{
id: null,
value: "dfgdf"
}
];
Below is the finalResult
array = [{
id: 6652
value: "sdfs"
},
{
id: 6653
value: "sdgdfg"
},
{
id: 6000
value: "trgd"
},
{
id: 6667
value: "fdg"
},
{
id: 6668
value: "dfgr"
},
{
id: null
value: "fg"
},
{
id: null
value: "dfgdf"
},
{
id: null
value: "fg"
},
{
id: null
value: "dfgdf"
}
]
In the above result the 6652 and 6667 is removed as they were duplicates and but null id are kept as i don't want to remove the null id and remove other repeated values.
Below is the logic i am trying to use but it doesn't work if ids are null
array= array.filter((v,i,a)=>a.findIndex(t=>( v.id !== null && t.id === v.id ))===i)
const filterKeys = {};
const filtered = arr.reduce((acc, item) => {
if (item.id === null || !(item.id in filterKeys)) {
filterKeys[item.id] = true;
acc.push(item);
}
return acc;
}, []);
Create a separate object to keep track of object ids that have been encountered, filterKeys in this case.
Since array is an array, do a reduce to iterate over the array. If the id is nuil or not found in filterKeys, push the item to the accumulator and return it for the next iteration. Since we don't care about ids that are null, they don't have an effect and are thus not filtered out.
Use this below code I tested(working) with your array of objects :
arr.forEach(function (item) {
if (item.id == null) {
result.push(item);
}
else {
var index = result.findIndex(x => x.id == item.id);
if (index == -1)
{
result.push(item);
}
}
});
console.log(result)
You can try this out-
const array = [{ id: 6652, value: 'erger' },{ id: 6652, value: 'sdfs' },{ id: 6653, value: 'sdgdfg' },{ id: 6000, value: 'trgd' },{ id: 6667, value: 'asdf' },{ id: 6667, value: 'fdg' },{ id: 6668, value: 'dfgr' },{ id: null, value: 'fg' },{ id: null, value: 'dfgdf' },{ id: null, value: 'fg' },{ id: null, value: 'dfgdf' },];
let index = 0;
const result = Object.values(
array.reduce(
(acc, curr) =>
curr.id === null ? { ...acc, [index++]: curr } : { ...acc, [curr.id]: curr },
{}
)
);
console.log(result);
.as-console-wrapper {min-height: 100%; top: 0}

I have array of objects and it needs to return array of sum based on condition

I have array of objects and it needs to return array of sum. if className.value identical then add Area.value values otherwise just return single value,
var obj = [{
Area: {
type: 'double',
value: 150
},
className: {
type: 'string',
value: "gold"
}
},
{
Area: {
type: 'double',
value: 130
},
className: {
type: 'string',
value: "silver"
}
},
{
Area: {
type: 'double',
value: 250
},
className: {
type: 'string',
value: "gold"
}
},
];
console.log(obj)
//expecting this array to return
console.log([400,130])
You can perform a reduce operation on the array, summing elements with the same className.value with an object.
var obj = [{ Area: { type: 'double', value: 150 }, className: { type: 'string', value: "gold" } }, { Area: { type: 'double', value: 130 }, className: { type: 'string', value: "silver" } }, { Area: { type: 'double', value: 250 }, className: { type: 'string', value: "gold" } }, ];
const res = Object.values(
obj.reduce((acc,curr)=>{
acc[curr.className.value] = (acc[curr.className.value] || 0) + curr.Area.value;
return acc;
}, {})
);
console.log(res);
Effectively just loop through the object and account for undefined. Use the className.value as element name to avoid duplicates.
function calculateValues(obj) {
var output = [];
for (var key in obj) {
output[obj[key].className.value] = (obj[key].className.value in output ? output[obj[key].className.value] : 0) + obj[key].Area.value;
}
return output;
}
console.log(calculateValues(obj));
Use Array#reduce to sum up the values for the different classes. For this look with Object#hasOwnProperty if there is in the accumulator an property with the classname of the current object. If not so, so add one with this name (e.g. "gold") and add the value from the current object to this. Otherwise add to this property the value directly.
After the summation to this object with reduce I get with Object#values the values from the Object as an array. At the end I just have to look if the array has only 1 entry. If so then return it as value otherwise return the array.
function makeSum(arr) {
let result = Object.values(arr.reduce((acc, cur) => {
let classVal = cur.className.value;
if (!acc.hasOwnProperty(classVal)) {
acc[classVal] = cur.Area.value;
} else {
acc[classVal] += cur.Area.value;
}
return acc;
},{}));
if (result.length===1) result = result[0];
return result;
}
let arr = [{ Area: { type: 'double', value: 150 }, className: { type: 'string', value: "gold" } }, { Area: { type: 'double', value: 130 }, className: { type: 'string', value: "silver" } }, { Area: { type: 'double', value: 250 }, className: { type: 'string', value: "gold" } }, ];
console.log(makeSum(arr));

Rename object keys in cartesian output

Sorry for the bad question title, couldn't figure a better one.
I have this array of options:
const options = [
{
display_name: "Size",
_id: "1",
values: [
{
label: "Small",
_id: "1"
},
{
label: "Extra Large",
_id: "2"
}
]
},
{
display_name: "Colors",
_id: "2",
values: [
{
label: "Red",
value: "#ff0000",
_id: "3"
},
{
label: "Green",
value: "#00ff21",
_id: "4"
},
]
}
];
I run this function against it to get Cartesian Product:
const getCartesian = object => {
return Object.entries(object).reduce(
(r, [key, value]) => {
let temp = [];
r.forEach(s =>
(Array.isArray(value) ? value : [value]).forEach(w =>
(w && typeof w === "object" ? getCartesian(w) : [w]).forEach(x =>
temp.push(Object.assign({}, s, { [key]: x }))
)
)
);
return temp;
},
[{}]
);
};
This will result in an array of objects in the following format (console.log output):
[{0: Object, 1: Object}, {0: Object, 1: Object}, ...]
The desired output is:
[
{
"option":{
"id":1,
"display_name":"Size"
},
"value":{
"label":"Small",
"id": 1
}
},
{
"option":{
"id":2,
"display_name":"Color",
},
"value":{
"id":5,
"label":"Red"
}
}
...
]
here's the playground and what I've tried so far: https://codesandbox.io/s/8nvwm76nnj
You need to map() at the end to convert array to object.
const options = [
{
display_name: "Size",
_id: "1",
values: [
{
label: "Small",
_id: "1"
},
{
label: "Extra Large",
_id: "2"
}
]
},
{
display_name: "Colors",
_id: "2",
values: [
{
label: "Red",
value: "#ff0000",
_id: "3"
},
{
label: "Green",
value: "#00ff21",
_id: "4"
},
]
}
];
const getCartesian = object => {
let t = Object.entries(object).reduce(
(r, [key, value]) => {
let temp = [];
r.forEach(s =>
(Array.isArray(value) ? value : [value]).forEach(w =>
(w && typeof w === "object" ? getCartesian(w) : [w]).forEach(x =>
temp.push(Object.assign({}, s, { [key]: x }))
)
)
);
return temp;
},
[{}]
);
return t.map(({0:val1,1:val2}) => ({option:val1,arr:val2}))
};
console.log(getCartesian(options));
You could wrap the array in an object with a property option. This gives you later an array with objects with option as key for the cartesian product.
const getCartesian = object => {
return Object.entries(object).reduce(
(r, [key, value]) => {
let temp = [];
r.forEach(s =>
(Array.isArray(value) ? value : [value]).forEach(w =>
(w && typeof w === "object" ? getCartesian(w) : [w]).forEach(x =>
temp.push(Object.assign({}, s, { [key]: x }))
)
)
);
return temp;
},
[{}]
);
};
const options = [{ display_name: "Size", _id: "1", values: [{ label: "Small", _id: "1" }, { label: "Extra Large", _id: "2" }] }, { display_name: "Colors", _id: "2", values: [{ label: "Red", value: "#ff0000", _id: "3" }, { label: "Green", value: "#00ff21", _id: "4" }] }];
console.log(getCartesian({ option: options }));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories