I do have an array with objects. Some of the objects properties have to be renamed.
So I have a function which gives me the desired result and I surely know about switch cases but I guess there must be a way less laborius way to do so. So I'm wondering what's the correct way for a shorthand technique to rename some property values and keep the ones which don't have to be changed:
renameMinMaxActual(data) {
const newArray = data.map(element => {
if (element.key.includes('Min')) {
element.key = element.key.replace('Min', '.min');
return element;
}
if (element.key.includes('Max')) {
element.key = element.key.replace('Max', '.max');
return element;
}
if (element.key.includes('Actual')) {
element.key = element.key.replace('Actual', '.actual');
return element;
} else {
return element;
}
});
console.log('newArray', newArray);
}
Indeed there are many ways to minimize the code - you mention 'shorthand' - here's one. I'm interested to see what others offer.
With this approach, the set of keys to transform are defined in keyTransforms, which makes it quite easy to add new keys without changing the transformKeys function.
// Define all keys to transform
const keyTransforms = {
Min: '.min',
Max: '.max',
Actual: '.actual'
};
// transformKeys replaces all keys found in data according to keyTransforms object.
function transformKeys(data) {
return data.map(element => {
// replace if match found, otherwise no change.
element.key = keyTransforms[element.key] || element.key;
return element;
});
}
// sample input data
const data = [{
key: 'Min'
}, {
key: 'Max'
}, {
key: 'Actual'
}];
const result = transformKeys([...data]);
console.log(result);
Create two arrays
fromKeys(present keys) fromKeys=['Min','Max','Actual'];
and toKeys(modified keys) toKeys=['.min','.max','.actual'];
While iterating through entries if key is present in fromKeys get the index and use the same key to get the value from toKeys array and set it in the object to be returned in map() function.
let data=[{Min:0,Max:1,Actual:5,type:"A"},{Min:0,Max:1,type:"B"},{Min:0,Max:1,type:"C"},{Min:0,Actual:5,type:"D"},{Max:1,Actual:5,type:"E"}];
let fromKeys=['Min','Max','Actual'];
let toKeys=['.min','.max','.actual'];
console.log(data.map(obj => {
let newObj={};
for (const [key,value] of Object.entries(obj)){
let index=fromKeys.indexOf(key);
if(index!=-1)newObj[toKeys[index]]=value;
else newObj[key]=value;
}
return newObj;
}));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
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()];
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);
I have a, next to a couple of fixed options, a variable number of yes/no radio inputs named other[index]. Using $(form).serializeArray() I get an array of name/value objects. Using reduce I'm able to reduce em down to an actual object.
const seializedForm = $(event.currentTarget.form).serializeArray();
const gdpr = seializedForm.reduce((aggragation, option) => {
return {
...aggragation,
[option.name]: option.value === 'true'
}}, {});
The problem here is that the result isn't exactly what I need:
{
"canNotify":true,
"canContact":true,
"canProcess":true,
"other[0]":false,
"other[1]":true,
"other[2]":false
}
I'd like it to be:
{
"canNotify":true,
"canContact":true,
"canProcess":true,
"other": [
false,
true,
false
]
}
Any suggestions?
For each name - remove the brackets, and if the key already exists in the array, combine the values to an array using array spread:
const serializedForm = [{"name":"canNotify","value":"true"},{"name":"canContact","value":"true"},{"name":"canProcess","value":"false"},{"name":"other[0]","value":"false"},{"name":"other[1]","value":"true"},{"name":"other[2]","value":"false"}];
const gdpr = serializedForm.reduce((aggragation, { name, value }) => {
const isArray = name.includes('[');
const key = name.replace(/\[.+\]/g, '');
const val = value === 'true';
return {
...aggragation,
[key]: isArray ? [...aggragation[key] || [], val] : val
};
}, {});
console.log(gdpr);
Without knowing what the full object structure looks like, why not just check what the name contains before returning, if the name contains the array syntax. [] or the string other, then we can assume that it is part of the other form collection structure?
const seializedForm = $(event.currentTarget.form).serializeArray();
const gdpr = seializedForm.reduce((aggragation, option) => {
if (isInArrayOfOptions(option)) {
return {
...aggragation,
/* Return a new array combining the current with the next option.value*/
'other': [...aggragation.other, ...[option.value === 'true']]
}
}
return {
...aggragation,
[option.name]: option.value === 'true'
}
}, {});
I'm having trouble understanding the behavior of this javascript code.
const devices = searchResult.results.forEach(device => {
const temp = Object.keys(device.fields);
for(var property in temp) {
if(device.fields.hasOwnProperty(property)) {
if (!usedPropertiesAcrossModels.has(property)) {
delete device.fields[property];
}
}
}
}
I am trying to delete the keys if a javascript object that do not belong to a set. I have stepped through the debugger, and I know that there is only one element in the set and 15 elements in device.fields. No matter what, nothing is being deleted from device.fields, I have no idea why. Moreover, temp seems to be undefined until I am out of the loop. Property is always undefined even though there are items in temp! This doesn't make any sense.
searchResult = {};
searchResult.results = [{
fields:{
name: 'hello',
type:'gekko',
random:'randomString'
}
}
]
usedPropertiesAcrossModels = {
name: 'hello',
random:'hello'
}
const devices = searchResult.results.forEach(device => {
const
temp = Object.keys(device.fields).map((property)=>{
if(device.fields.hasOwnProperty(property)) {
if (!usedPropertiesAcrossModels.hasOwnProperty(property)) {
delete device.fields[property];
}
}
})
})
console.log(searchResult)
Using map fixed the issue as in your case the for in was giving index instead of the keys of the object.Or as martin said you can consider using for of as well.
const temp = Object.keys(o) will give you array of object's keys. You should use for of loop instead of for in, as you need to iterate through values of it, not their keys in temp object:
const o = { a: 1, b: 2, c: 3 };
const temp = Object.keys(o);
console.log(temp);
// this will iterate through `temp` keys, so 0, 1, 2
for (const property in temp) {
console.log('wrong:', property);
}
// this will iterate through `temp` values, so 'a', 'b', 'c'
for (const property of temp) {
console.log('correct:', property);
}
// or you could iterate via `forEach()`
temp.forEach(property => {
console.log('correct:', property);
});
Also with for of loop, you do not need the hasOwnProperty check.
I want to reverse the mapping of an object (which might have duplicate values). Example:
const city2country = {
'Amsterdam': 'Netherlands',
'Rotterdam': 'Netherlands',
'Paris': 'France'
};
reverseMapping(city2country) Should output:
{
'Netherlands': ['Amsterdam', 'Rotterdam'],
'France': ['Paris']
}
I've come up with the following, naive solution:
const reverseMapping = (obj) => {
const reversed = {};
Object.keys(obj).forEach((key) => {
reversed[obj[key]] = reversed[obj[key]] || [];
reversed[obj[key]].push(key);
});
return reversed;
};
But I'm pretty sure there is a neater, shorter way, preferably prototyped so I could simply do:
const country2cities = city2country.reverse();
You could use Object.assign, while respecting the given array of the inserted values.
const city2country = { Amsterdam: 'Netherlands', Rotterdam: 'Netherlands', Paris: 'France' };
const reverseMapping = o => Object.keys(o).reduce((r, k) =>
Object.assign(r, { [o[k]]: (r[o[k]] || []).concat(k) }), {})
console.log(reverseMapping(city2country));
There is no such built-in function in JavaScript. Your code looks fine, but given that there are so many edge cases here that could wrong, I'd suggesting using invertBy from lodash, which does exactly what you describe.
Example
var object = { 'a': 1, 'b': 2, 'c': 1 };
_.invertBy(object);
// => { '1': ['a', 'c'], '2': ['b'] }
You can use something like this to get raid of duplicates first :
function removeDuplicates(arr, key) {
if (!(arr instanceof Array) || key && typeof key !== 'string') {
return false;
}
if (key && typeof key === 'string') {
return arr.filter((obj, index, arr) => {
return arr.map(mapObj => mapObj[key]).indexOf(obj[key]) === index;
});
} else {
return arr.filter(function(item, index, arr) {
return arr.indexOf(item) == index;
});
}
}
and then use this to make it reverse :
function reverseMapping(obj){
var ret = {};
for(var key in obj){
ret[obj[key]] = key;
}
return ret;
}
You could try getting an array of values and an array of keys from the current object, and setup a new object to hold the result. Then, as you loop through the array of values -
if the object already has this value as the key, like Netherlands, you create a new array, fetch the already existing value (ex: Rotterdam), and add this and the new value (Amsterdam) to the array, and set up this array as the new value for the Netherlands key.
if the current value doesn't exist in the object, set it up as a new string, ex: France is the key and Paris is the value.
Code -
const city2country = {
'Amsterdam': 'Netherlands',
'Rotterdam': 'Netherlands',
'Paris': 'France',
};
function reverseMapping(obj) {
let values = Object.values(obj);
let keys = Object.keys(obj);
let result = {}
values.forEach((value, index) => {
if(!result.hasOwnProperty(value)) {
// create new entry
result[value] = keys[index];
}
else {
// duplicate property, create array
let temp = [];
// get first value
temp.push(result[value]);
// add second value
temp.push(keys[index]);
// set value
result[value] = temp;
}
});
console.log(result);
return result;
}
reverseMapping(city2country)
The benefit here is - it adjusts to the structure of your current object - Netherlands being the repeated values, gets an array as it's value in the new object, while France gets a string value Paris as it's property. Of course, it should be very easy to change this.
Note - Object.values() might not be supported across older browsers.
You could use reduce to save the declaration line reduce.
Abusing && to check if the map[object[key]] is defined first before using Array.concat.
It's shorter, but is it simpler? Probably not, but a bit of fun ;)
const reverseMapping = (object) =>
Object.keys(object).reduce((map, key) => {
map[object[key]] = map[object[key]] && map[object[key]].concat(key) || [key]
return map;
}, {});
#Nina Scholz answer works well for this exact question. :thumbsup:
But if you don't need to keep both values for the Netherlands key ("Netherlands": ["Amsterdam", "Rotterdam"]), then this is a little bit shorter and simpler to read:
const city2country = { Amsterdam: 'Netherlands', Rotterdam: 'Netherlands', Paris: 'France' };
console.log(
Object.entries(city2country).reduce((obj, item) => (obj[item[1]] = item[0]) && obj, {})
);
// outputs `{Netherlands: "Rotterdam", France: "Paris"}`