Rename keys with ramda js - javascript

const orignalArr = [
{
personName: 'Joe'
}
]
expected output:
const convertedArr = [
{
name: 'Joe'
}
]
I'm thinking the renamed keys are defined in an object (but fine if there's a better way to map them):
const keymaps = {
personName: 'name'
};
How can I do this with Ramda?
Something with R.map

There is an entry in Ramda's Cookbook for this:
const renameKeys = R.curry((keysMap, obj) =>
R.reduce((acc, key) => R.assoc(keysMap[key] || key, obj[key], acc), {}, R.keys(obj))
);
const originalArr = [{personName: 'Joe'}]
console .log (
R.map (renameKeys ({personName: 'name'}), originalArr)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
But with the ubiquity of ES6, it's pretty easy to write this directly:
const renameKeys = (keysMap) => (obj) => Object.entries(obj).reduce(
(a, [k, v]) => k in keysMap ? {...a, [keysMap[k]]: v} : {...a, [k]: v},
{}
)

You can combine Ramda with Ramda Adjunct. Using the renameKeys (https://char0n.github.io/ramda-adjunct/2.27.0/RA.html#.renameKeys) method is very useful. With it you can simply do something like this:
const people = [
{
personName: 'Joe'
}
]
const renameKeys = R.map(RA.renameKeys({ personName: 'name' }));
const __people__ = renameKeys(people);
console.log(__people__) // [ { name: 'Joe' }]
Hope it helped you :)

This is my take on renameKeys. The main idea is to separate the keys and values to two array. Map the array of keys, and replace with values from keyMap (if exist), then zip back to object:
const { pipe, toPairs, transpose, converge, zipObj, head, map, last } = R
const renameKeys = keysMap => pipe(
toPairs, // convert to entries
transpose, // convert to array of keys, and array of values
converge(zipObj, [ // zip back to object
pipe(head, map(key => keysMap[key] || key)), // rename the keys
last // get the values
])
)
const originalArr = [{ personName: 'Joe', lastName: 'greg' }]
const result = R.map(renameKeys({ personName: 'name' }), originalArr)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

My idea to make it is to first check that the old prop I want to rename exists, and the new key I want to create doesn’t.
Then, I will use the S_ common combinator to make it point-free.
Find JS common combinators here
const {
allPass, assoc, compose: B, complement, has, omit, prop, when
} = require('ramda');
const S_ = (f) => (g) => (x) => f (g (x)) (x);
const renameKey = (newKey) => (oldKey) => when(allPass([
has(oldKey)
, complement(has)(newKey)
]))
(B(omit([oldKey]), S_(assoc(newKey))(prop(oldKey))))
const obj = { fullname: 'Jon' };
renameKey('name')('fullname')(obj) // => { name: ‘Jon’ }

Here is my own solution, not too many arrow functions (just one), mostly pure Ramda calls. And it is one of shortest, if not the shortest ;)
First, based on your example
const { apply, compose, either, flip, identity, map, mergeAll, objOf, prop, replace, toPairs, useWith } = require('ramda');
const RenameKeys = f => compose(mergeAll, map(apply(useWith(objOf, [f]))), toPairs);
const originalArr = [
{
personName: 'Joe',
},
];
const keymaps = {
personName: 'name',
};
// const HowToRename = flip(prop)(keymaps); // if you don't have keys not specified in keymaps explicitly
const HowToRename = either(flip(prop)(keymaps), identity);
console.log(map(RenameKeys(HowToRename))(originalArr));
Second option, using any arbitrary lambda with renaming rules:
const { apply, compose, map, mergeAll, objOf, replace, toPairs, useWith } = require('ramda');
const RenameKeys = f => compose(mergeAll, map(apply(useWith(objOf, [f]))), toPairs);
const HowToRename = replace(/(?<=.)(?!$)/g, '_'); // for example
console.log(RenameKeys(HowToRename)({ one: 1, two: 2, three: 3 }));
Yields
{ o_n_e: 1, t_w_o: 2, t_h_r_e_e: 3 }
Third, you can use object-based rename rules from the first example and use fallback strategy, e.g. replace like in the second example, instead of identity.

Related

Can destructuring an array be used to map properties of each elements?

Suppose there is an array like this:
const a = [ {p:1}, {p:2}, {p:3} ];
Is it possible to destructure this array in order to obtain p = [1, 2, 3] ?
Because this does not work :
const [ ...{ p } ] = a; // no error, same as const p = a.p;
// p = undefined;
Edit
In response to all the answers saying that I need to use Array.prototype.map, I am aware of this. I was simply wondering if there was a way to map during the destructuring process, and the answer is : no, I need to destructure the array itself, then use map as a separate step.
For example:
const data = {
id: 123,
name: 'John',
attributes: [{ id:300, label:'attrA' }, { id:301, label:'attrB' }]
};
function format(data) {
const { id, name, attributes } = data;
const attr = attributes.map(({ label }) => label);
return { id, name, attr };
}
console.log( format(data) };
// { id:123, name:'John', attr:['attrA', 'attrB'] }
I was simply wondering if there was a way, directly during destructuring, without using map (and, respectfully, without the bloated lodash library), to retrive all label properties into an array of strings.
Honestly I think that what you are looking for doesn't exist, normally you would map the array to create a new array using values from properties. In this specific case it would be like this
const p = a.map(element => element.p)
Of course, there are some packages that have many utilities to help, like Lodash's map function with the 'property' iteratee
you can destructure the first item like this :
const [{ p }] = a;
but for getting all values you need to use .map
and the simplest way might be this :
const val = a.map(({p}) => p)
Here's a generalized solution that groups all properties into arrays, letting you destructure any property:
const group = (array) => array.reduce((acc,obj) => {
for(let [key,val] of Object.entries(obj)){
acc[key] ||= [];
acc[key].push(val)
}
return acc
}, {})
const ar = [ {p:1}, {p:2}, {p:3} ];
const {p} = group(ar)
console.log(p)
const ar2 = [{a:2,b:1},{a:5,b:4}, {c:1}]
const {a,b,c} = group(ar2)
console.log(a,b,c)

Comparison based on valid string combination in javascript

I have selected values on screen and their value are stored in two variables.
var uval = '100';
var eval = '5';
There are 2 combinations that have values:
let combination1= 'u:100;e:1,4,5,10'
let combination2 = 'u:1000;e:120,400,500,1000'
I want to check if uval and eval are present in any combination and set some Boolean to true otherwise it'll be false.
uval will be compared to u in that combination and eval with e in that combination.
Boolean in this example will be true.
If uval='50'; eval='5' then it'll be false.
I tried using a for loop using split(';') and compare each u with uval and e with eval.
I am looking for some different approach for this apart from using traditional for loops everytime.
you can do something like this
basically I created a function that transform you combination into an object and I used that object inside the check function
const combination1= 'u:100;e:1,4,5,10'
const combination2 = 'u:1000;e:120,400,500,1000'
const uval = '100';
const eval = '5';
const toObject = combination => Object.fromEntries(
combination.split(';').map(c => c.split(':')).map(([k, v]) => [k, v.split(',')])
)
const check = (combination, key , value ) => (toObject(combination)[key] || []).includes(value)
const checkEval = (combination, eval) => check(combination, 'e', eval)
const checkUval = (combination, uval) => check(combination, 'u', uval)
console.log(checkEval(combination1, eval))
console.log(checkEval(combination2, eval))
console.log(checkUval(combination1, uval))
console.log(checkUval(combination2, uval))
I've created a function named getKeyValuePairs that returns key value pairs for a given combination.
So, if we call getKeyValuePairs with the combination "u:100;e:1,4,5,10" it would return the following array of key value pairs:
[
[ 'u', [ '100' ] ],
[ 'e', [ '1', '4', '5', '10' ] ]
]
Then I've created another function named groupCombinations that merges all the combinations passed to it into a single object.
So, if we call groupCombinations with combinations "u:100;e:1,4,5,10" and "u:1000;e:120,400,500,1000", it would return the following object:
{
u: Set(2) {"100", "1000"},
e: Set(8) {"1", "4", "5", "10", "120", "400", "500", "1000"}
}
Finally I've grouped all the combinations together using groupCombinations and then checked if the group contains uVal and eVal.
const getKeyValuePairs = (combination) =>
combination
.split(";")
.map((str) => str.split(":"))
.map(([k, v]) => [k, v.split(",")]);
const groupCombinations = (...combinations) =>
Object.fromEntries(
Object.entries(
combinations.reduce(
(group, combination) => (
getKeyValuePairs(combination).forEach(([k, v]) =>
(group[k] ??= []).push(...v)
),
group
),
{}
)
).map(([k, v]) => [k, new Set(v)])
);
const uVal = "100";
const eVal = "5";
const combination1 = "u:100;e:1,4,5,10";
const combination2 = "u:1000;e:120,400,500,1000";
const allCombinations = groupCombinations(combination1, combination2);
const isUVal = allCombinations["u"].has(uVal);
const isEVal = allCombinations["e"].has(eVal);
const isBoth = isUVal && isEVal;
console.log(isBoth);

Filter nested object

I receive an object that looks like this:
this.tokensData = {
O: {
id: 0,
name: value1,
organization: organization1,
...,
},
1: {
id: 1,
name: value1,
organization: organization1,
...,
},
2: {
id: 2,
name: value2,
organization: organization2,
...,
},
...
}
I want to filter by id and remove the Object which id matches the id I receive from the store. What I tried so far:
const filteredObject = Object.keys(this.tokensData).map((token) => {
if (this.$store.state.id !== this.tokensData[token].id) {
return this.tokensData[token];
}
});
This replaces the Object with undefined - which would work for my purposes but is obviously not ideal.
Any help is much appreciated!
Try to use Object.entries and then Object.fromEntries() to create an object from a list of key-value pairs:
let store = [0 , 1];
const result = Object.entries(tokensData).filter(([k, v]) => !store.some(s => s == v.id));
console.log(Object.fromEntries(result));
An example:
let tokensData = {
O: {
id: 0,
name: '',
organization: '',
},
1: {
id: 1,
name: '',
organization: '',
},
2: {
id: 2,
name: '',
organization: '',
}
}
let store = [0 , 1];
const result = Object.entries(tokensData).filter(([k, v]) => !store.some(s => s == v.id));
console.log(Object.fromEntries(result));
You can do this by using Object.entries and Object.fromEntries:
const filteredObject = Object.fromEntries(
Object.entries(this.tokensData).filter(
([key, value]) => value.id !== this.$store.state.id
)
)
This can be done by cloning the object and removing the object at the ID:
const removeObjectByID = (obj, id) => {
// first create a copy of the object
const copy = JSON.parse(JSON.stringify(obj))
// next, delete the one entry you don't want
delete copy[id]
// finally, return the new object
return copy
}
// Test
const data = {a:1, b: 2, c: 3}
console.log(removeObjectByID(data, 'b')) // { a: 1, c: 3 }
The problem with undefined is caused by using this in your arrow function. Javascript scoping gives a different meaning to this if the function is a arrow function.
Furthermore I suggest to use filter.
.map is used to transform A -> B.
.filter should be used to filter out objects.
Now if we combine that this would become something like this.
function filterById(token) {
return this.$store.state.id !== this.tokensData[token].id;
}
function getTokenData(token) {
return this.tokensData[token]
}
const token = Object.keys(this.tokensData)
.filter(filterById)
.map(getTokenData);
});
Please note that I'm not using arrow functions. Arrow function can't refer to this due to the way javascript handles scoping.
An alternative approach could be to reference this into a variable, so your arrow function can access the variable.
const self = this;
const token = Object.keys(this.tokensData)
.filter(token => self.$store.state.id !== self.tokensData[token].id)
.map(token => self.tokensData[token]);
Too make it even nicer you could utilize Object.entries. This will return an array of key and value, which you can destructure using es6 syntax as following [key, value].
const self = this;
const token = Object.entries(this.tokensData)
.filter(([key, value]) => self.$store.state.id !== value.id)
.map(([key, value]) => value);

What's the best way of creating a new object with updated nested properties in modern JavaScript

I have a JavaScript object with some nested properties that I want to update based on some conditions. The starting object could be something like:
const options = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
}
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
}
}
};
I have come up with a solution using Object.keys, reduce and the spread operator, but I would like to know if this is the best / more concise way as of today or if there is a better way. I'm not looking for the most performing option, but for a "best practice" (if there is one) or a more elegant way.
EDIT 30/01/20
As pointed out in the comments by #CertainPerformance my code was mutating the original options variable, so I am changing the line const option = options[key]; to const option = { ...options[key] };. I hope this is correct and that the function is not mutating the original data.
const newObject = Object.keys(options).reduce((obj, key) => {
const option = { ...options[key] };
const newVal = getNewValue(option.label); // example function to get new values
// update based on existence of new value and key
if (option.selections && option.selections.value && newVal) {
option.selections.value = newVal;
}
return {
...obj,
[key]: option,
};
}, {});
getNewValue is an invented name for a function that I am calling in order to get an 'updated' version of the value I am looking at. In order to reproduce my situation you could just replace
the line const newVal = getNewValue(option.label); with const newVal = "bla bla";
Since you tagged this q with functional-programming here is a functional approach. Functional Lenses are an advanced FP tool and hence hard to grasp for newbies. This is just an illustration to give you an idea of how you can solve almost all tasks and issues related to getters/setters with a single approach:
// functional primitives
const _const = x => y => x;
// Identity type
const Id = x => ({tag: "Id", runId: x});
const idMap = f => tx =>
Id(f(tx.runId));
function* objKeys(o) {
for (let prop in o) {
yield prop;
}
}
// Object auxiliary functions
const objSet = (k, v) => o =>
objSetx(k, v) (objClone(o));
const objSetx = (k, v) => o =>
(o[k] = v, o);
const objDel = k => o =>
objDelx(k) (objClone(o));
const objDelx = k => o =>
(delete o[k], o);
const objClone = o => {
const p = {};
for (k of objKeys(o))
Object.defineProperty(
p, k, Object.getOwnPropertyDescriptor(o, k));
return p;
};
// Lens type
const Lens = x => ({tag: "Lens", runLens: x});
const objLens_ = ({set, del}) => k => // Object lens
Lens(map => ft => o =>
map(v => {
if (v === null)
return del(k) (o);
else
return set(k, v) (o)
}) (ft(o[k])));
const objLens = objLens_({set: objSet, del: objDel});
const lensComp3 = tx => ty => tz => // lens composition
Lens(map => ft =>
tx.runLens(map) (ty.runLens(map) (tz.runLens(map) (ft))));
const lensSet = tx => v => o => // set operation for lenses
tx.runLens(idMap) (_const(Id(v))) (o);
// MAIN
const options = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
}
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
}
}
};
const nameLens = lensComp3(
objLens("formatOption"))
(objLens("selections"))
(objLens("name"));
const options_ = lensSet(nameLens) ("foo") (options).runId;
// deep update
console.log(options_);
// reuse of unaffected parts of the Object tree (structural sharing)
console.log(
options.heightOptions === options_.heightOptions); // true
This is only a teeny-tiny part of the Lens machinery. Functional lenses have the nice property to be composable and to utilize structural sharing for some cases.
If you want to set a value for a nested property in a immutable fashion,
then you should consider adopting a library rather than doing it manually.
In FP there is the concept of lenses
Ramda provides a nice implementation: https://ramdajs.com/docs/
const selectionsNameLens = R.lensPath(
['formatOption', 'selections', 'name'],
);
const setter = R.set(selectionsNameLens);
// ---
const data = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
},
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
},
},
};
console.log(
setter('Another Specific Format', data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
The first comment from CertainPerformance made me realize that I was mutating the original options variable. My first idea was to make a copy with the spread operator, but the spread operator only makes a shallow copy, so even in my edit I was still mutating the original object.
What I think is a solution is to create a new object with only the updated property, and to merge the two objects at the end of the reducer.
EDIT
The new object also needs to be merged with the original option.selections, otherwise I would still overwrite existing keys at that level (ie I would overwrite option.selections.name).
Here is the final code:
const newObject = Object.keys(options).reduce((obj, key) => {
const option = options[key];
const newVal = getNewValue(option.label); // example function to get new values
const newOption = {}; // create a new empty object
// update based on existence of new value and key
if (option.selections && option.selections.value && newVal) {
// fill the empty object with the updated value,
// merged with a copy of the original option.selections
newOption.selections = {
...option.selections,
value: newVal
};
}
return {
...obj, // accumulator
[key]: {
...option, // merge the old option
...newOption, // with the new one
},
};
}, {});
A more concise version that has been suggested to me would be to use forEach() instead of reduce(). In this case the only difficult part would be to clone the original object. One way would be to use lodash's _.cloneDeep(), but there are plenty of options (see here).
Here is the code:
const newObject = _.cloneDeep(options);
Object.keys(newObject).forEach(key => {
const newVal = getNewValue(newObject[key].label); // example function to get new values
// update based on existence of new value and key
if (newObject[key].selections && newObject[key].selections.value && newVal) {
newObject[key].selections.value = newVal;
}
});
The only problem is that forEach() changes values that are declared outside of the function, but reduce() can mutate its parameter (as it happened in my original solution), so the problem is not solved by using reduce() alone.
I'm not sure that this is the best solution, but it surely is much more readable for the average developer than my first try or the other solutions.

Get union of keys of all objects in js array using reduce

Assume , we have :
var all=[
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 }
{fullname: 'Xod BOD', children: 1}
];
The expected result is ['firstname','age', 'children', 'fullname']: the union of keys of all objects of that array:
all.map((e) => Object.keys(e) ).reduce((a,b)=>[...a,...b],[]);
This is work fine , However, i am seeking a solution more performance using directly reduce method without map , I did the following and it is failed.
all.reduce((a,b) =>Object.assign([...Object.keys(a),...Object.keys(b)]),[])
You can use Set, reduce() and Object.keys() there is no need for map.
var all=[
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 },
{fullname: 'Xod BOD', children: 1}
];
var result = [...new Set(all.reduce((r, e) => [...r, ...Object.keys(e)], []))];
console.log(result)
Here's a solution using generic procedures concat, flatMap, and the ES6 Set.
It's similar to #NenadVracar's solution but uses higher-order functions instead of a complex, do-it-all-in-one-line implementation. This reduces complexity in your transformation and makes it easier to re-use procedures in other areas of your program.
Not that ... spread syntax is bad, but you'll also notice this solution does not necessitate it.
var all = [
{firstname:'Ahmed', age:12},
{firstname:'Saleh', children:5 },
{fullname: 'Xod BOD', children: 1}
];
const concat = (x,y) => x.concat(y);
const flatMap = f => xs => xs.map(f).reduce(concat, []);
const unionKeys = xs =>
Array.from(new Set(flatMap (Object.keys) (xs)));
console.log(unionKeys(all));
// [ 'firstname', 'age', 'children', 'fullname' ]
Just out of curiosity, I've been benchmarking some solutions to your problem using different approaches (reduce vs foreach vs set). Looks like Set behaves well for small arrays but it's extremely slow for bigger arrays (being the best solution the foreach one).
Hope it helps.
var all = [{
firstname: 'Ahmed',
age: 12
}, {
firstname: 'Saleh',
children: 5
}, {
fullname: 'Xod BOD',
children: 1
}],
result,
res = {};
const concat = (x,y) => x.concat(y);
const flatMap = f => xs => xs.map(f).reduce(concat, []);
const unionKeys = xs =>
Array.from(new Set(flatMap (Object.keys) (xs)));
for(var i = 0; i < 10; i++)
all = all.concat(all);
console.time("Reduce");
result = Object.keys(all.reduce((memo, obj) => Object.assign(memo, obj), {}));
console.timeEnd("Reduce");
console.time("foreach");
all.forEach(obj => Object.assign(res, obj));
result = Object.keys(res);
console.timeEnd("foreach");
console.time("Set");
result = [...new Set(all.reduce((r, e) => r.concat(Object.keys(e)), []))];
console.timeEnd("Set");
console.time("Set2");
result = unionKeys(all);
console.timeEnd("Set2");
Try this code:
var union = new Set(getKeys(all));
console.log(union);
// if you need it to be array
console.log(Array.from(union));
//returns the keys of the objects inside the collection
function getKeys(collection) {
return collection.reduce(
function(union, current) {
if(!(union instanceof Array)) {
union = Object.keys(union);
}
return union.concat(Object.keys(current));
});
}

Categories