Pipes, ramda with dynamic arguments - javascript

cons columnDefs = [
{
label: 'The_First_Name',
value: getProp,
args: ['firstName'] // different number of arguments depending on function
},
{
label: 'City',
value: getNestedProperty,
args: ['location', 'city']
}
]
const data = [
{
firstName: 'Joe',
lastName: 'Smith',
location: {
city: 'London'
}
},
{
firstName: 'Anna',
lastName: 'Andersson',
location: {
city: 'Stockholm'
}
}
]
const getProp = (object, key) => R.prop(key, object);
const getNestedProperty = (obj, args) => R.path(..args, obj);
Ramda pipe to map the data:
const tableBuilder = R.pipe(R.map); // some ramda functions in here
const rows = tableBuilder(data, columnDefs);
The wanted output:
rows output:
[
{
The_First_Name: 'Joe',
city: 'London'
},
{
The_First_Name: 'Anna',
city: 'Stockholm'
}
]
The key of each row is the label property in the columnDefs. The value is fetched from the Ramda function in the value prop together with the arguments defined in the args prop.
https://plnkr.co/edit/rOGh4zkyOEF24TLaCZ4e?p=preview
Totally stuck. Is this even possible to do with Ramda? Or is better to do it in plain javascript?

You can use applySpec to create an object from another one:
const obj = applySpec({
The_First_Name: prop('firstName'),
city: path(['location', 'city'])
})
obj({
firstName: 'Joe',
lastName: 'Smith',
location: {
city: 'London'
}
});
//=> {"The_First_Name": "Joe", "city": "London"}
Then you can use that function to map over you array:
const data = [
{
firstName: 'Joe',
lastName: 'Smith',
location: {
city: 'London'
}
},
{
firstName: 'Anna',
lastName: 'Andersson',
location: {
city: 'Stockholm'
}
}
];
const obj = applySpec({
The_First_Name: prop('firstName'),
city: path(['location', 'city'])
})
console.log(
map(obj, data)
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {applySpec, prop, path, map} = R;</script>
This is how you transform columnDefs into an object that you can use with applySpec:
const spec = def => ({[def.label]: apply(def.value, def.args)});
const specs = compose(mergeAll, map(spec));
const columnDefs = [
{
label: 'The_First_Name',
value: prop,
args: ['firstName'] // different number of arguments depending on function
},
{
label: 'City',
value: path,
args: [['location', 'city']]
}
]
const data = [
{
firstName: 'Joe',
lastName: 'Smith',
location: {
city: 'London'
}
},
{
firstName: 'Anna',
lastName: 'Andersson',
location: {
city: 'Stockholm'
}
}
]
const spec = def => ({[def.label]: apply(def.value, def.args)});
const specs = compose(mergeAll, map(spec));
console.log(
map(applySpec(specs(columnDefs)), data)
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {apply, compose, mergeAll, map, prop, path, applySpec} = R;</script>

The following should work:
const tableBuilder = (objs, spec) => objs .map (
obj => Object .assign ( ...spec.map (
( {label, value, args} ) => ( { [label]: value (obj, args) } )
))
)
const getProp = (object, key) => R.prop (key, object);
const getNestedProperty = (obj, args) => R.path (args, obj);
const columnDefs = [
{label: 'The_First_Name', value: getProp, args: ['firstName']},
{label: 'City', value: getNestedProperty, args: ['location', 'city']}
]
const data = [
{firstName: 'Joe', lastName: 'Smith', location: {city: 'London'}},
{firstName: 'Anna', lastName: 'Andersson', location: {city: 'Stockholm'}}
]
console .log (
tableBuilder (data, columnDefs)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script> <script>
const {prop, path} = R </script>
But it's working mostly by happenstance. You might need to rethink a bit how your functions are defined.
This is calling the equivalent of prop (['firstName'], obj), which happens to work like prop ('firstName', obj), but only for the same reason that 'foo' + ['bar'] yields 'foobar'. It's a coincidence you probably should not depend upon.
The trouble is that you want to treat uniformly functions that take a single argument and ones that take an array of arguments. This is a problem. You probably need to make this consistent.
While you can write a Ramda gloss on this, I'm not sure it will be any more readable. Perhaps replacing Object .assign (...spec.map ( with mergeAll (spec.map ( would be cleaner. And if you don't mind changing parameter orders, there is probably a bit more clean-up. But this is already fairly readable.
Update
The answer from #customcommander convinced me that Ramda really could add some value here. This requires that you be willing to swap the parameter order for your value functions, and to be willing to call it as a fully curried function (tableBuilder (columnDefs) (data)), but it does lead to some pretty nice code.
This is mostly the work of customcommander, but I adjusted it a bit to make a more readable function:
const spec = ({label, value, args}) => ({[label]: value(args)})
const tableBuilder = pipe(
map(spec),
mergeAll,
applySpec,
map
)
const columnDefs = [
{label: 'The_First_Name', value: prop, args: ['firstName']},
{label: 'City', value: path, args: ['location', 'city']}
]
const data = [
{firstName: 'Joe', lastName: 'Smith', location: {city: 'London'}},
{firstName: 'Anna', lastName: 'Andersson', location: {city: 'Stockholm'}}
]
console .log (
tableBuilder (columnDefs) (data)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script><script>
const {prop, path, pipe, map, mergeAll, applySpec} = R </script>
This has the same issue as before with prop, but do note that you can replace it here with path without harm. The main point is that all your value functions should have the same inputs (an array of values and the object to work on.) If they do, then this should work for any of them.

Related

Immutable JS - converting a collection of Maps to a List

I have the following structure in the project im working on (uses immutable JS):
data :[{name: 'john', surname: 'smith', children: [{name: 'sam'}, {name: 'ben'}]},
{name: 'jane', surname: 'jones', children: [{name: 'tim'}, {name: 'amy'}]}]
the object is converted to immutable JS via fromJS().
I need a new object in the following structure:
data :[{name: 'john', surname: 'smith', children: ['sam','ben']},
{name: 'jane', surname: 'jones', children: ['tim','amy']}]
Any pointers much appreciated.
Something like that should works
data.map(d => {d.name, d.surname, children: d.children.map(child => child.name)});
The pure JS answer of Christian doesn't exactly apply to immutable.js objects, so here are a few ways to do it with immutable.js's collection API.
There are more options (e.g. reduce), feel free to check the docs; the method descriptions usually come with examples.
const raw = [{name: 'john', surname: 'smith', children: [{name: 'sam'}, {name: 'ben'}]},
{name: 'jane', surname: 'jones', children: [{name: 'tim'}, {name: 'amy'}]}];
const data = Immutable.fromJS(raw);
function run(name, operation) {
let result;
console.time(name);
for(let i=0; i < 50000; i++){
result = operation();
}
console.timeEnd(name);
console.log(result1.toJS());
}
run('simple', () => {
// simply updating objects inside a mapping
result1 = data.map(
person => person.update('children',
kidObjs => kidObjs.map(
kid => kid.get('name')
)
)
);
});
run('withMutations and setIn', () => {
// using withMutations and setIn
result2 = data.withMutations(list => {
list.forEach((val, idx) => {
list.setIn(
[idx, 'children'],
val.get('children').map(kid => kid.get('name'))
)
});
});
});
run('withMutations and update', () => {
// slightly faster by using withMutations set/update
result2 = data.withMutations(list => {
list.forEach((val, idx) => {
list.set(idx, val.update('children', kids => kids.map(kid => kid.get('name'))))
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/4.0.0-rc.14/immutable.min.js"></script>
I would go with map and reduce:
const data = [{name: 'john', surname: 'smith', children: [{name: 'sam'}, {name: 'ben'}]},
{name: 'jane', surname: 'jones', children: [{name: 'tim'}, {name: 'amy'}]}];
var result = data.map(person => {
return { name: person.name,
surname: person.surname,
children: person.children.reduce((acc, c) => {acc.push(c.name); return acc;}, []) }});
console.log(result)

How to nest element of an object inside the same object in javascript?

I created a form to get some info from User, and I want to move some of their info into a nested object. the reason why is to better organize my data later in front-end.
As a simple example, how to create the following "newInfo" out of "oldInfo" in JavaScript?
oldInfo = {
name: 'John',
Age: '32',
friend1: 'Michael',
friend2: 'Peter',
};
newInfo = {
name: 'John',
Age: '32',
friends: {
friend1: 'Michael',
friend2: 'peter',
},
};
I'm sure this must be a repeated and simple topic, but I couldn't find any as I didn't know what keyword to search for.
You could explicitly assign it
const oldInfo = {
name: "John",
Age: "32",
friend1: "Michael",
friend2: "Peter",
}
const newInfo = {
name: oldInfo.name,
Age: oldInfo.Age,
friends: {
friend1: oldInfo.friend1,
friend2: oldInfo.friend2,
},
}
console.log(newInfo)
You can do this easily with spread operator:
const { name, Age, ...friends } = oldInfo;
newInfo = { name, Age, friends };
It simply extracts all fields except name and age as friends.
Example:
const oldInfo = {
name: 'John',
Age: '32',
friend1: 'Michael',
friend2: 'Peter',
};
const { name, Age, ...friends } = oldInfo;
const newInfo = { name, Age, friends };
console.log(newInfo);
If you have a dynamic number of friend: name key-value pairs and other properties that shouldn't be nested into friends then you can use Object.entries and reduce:
const oldInfo = {
name: 'John',
Age: '32',
friend1: 'Michael',
friend2: 'Peter',
};
const newInfo = Object.entries(oldInfo).reduce((acc, [k, v]) => {
if(k.startsWith('friend')) {
acc.friends ? acc.friends[k] = v : acc.friends = {[k]: v};
} else {
acc[k] = v;
}
return acc;
},{});
console.log(newInfo);

How can we transform a nested array inside an object into one concatenated string value separated by commas?

I have the following sample array:
mainArray = [
{id: 15475, name: 'Ali', gender: 'Male', addresses: [
{address1: 'Lebanon'},
{address2: 'USA'}]
},
{id: 15475, name: 'Emily', gender: 'Female', addresses: [
{address1: 'UK'},
{address2: 'France'}]
},
];
I need to transform it into something like:
mainArray = [
{id: 15475, name: 'Ali', gender: 'Male', addresses: 'Lebanon, USA'},
{id: 15475, name: 'Emily', gender: 'Female', addresses: 'UK, France }
];
In this case, I added all nested arrays inside a an element of the mainArray into one single string value.
What I've done so far is, I extracted the key names of the mainArray:
extractedIndexes = ['id', 'name', 'gender', 'addresses'];
And made a loop to check the type of each element inside of the mainArray, and if it's an object I will concat the values of the nested array into one single string:
for (const idx of this.extractedIndexes) {
console.log(idx)
this.mainArray.forEach((elem) => {
let newItem = '';
if (typeof (elem[idx]) == 'object') {
elem[idx] = Object.keys(elem[idx]).forEach((key) => {
console.log(elem[idx][key])
// Add it to the field
})
console.log(elem[idx])
}
})
}
console.log(this.mainArray)
This line console.log(elem[idx][key]) is always returning the following:
{address1: "Lebanon"}
{address2: "USA"}
{address1: "UK"}
{address2: "France"}
Take note that here address1 and address2 are simple examples, as my real data contain multiple nested arrays, and each one have different new key names.
I tried to do the following:
if (typeof (elem[idx]) == 'object') {
elem[idx] = elem[idx].toString().split(',')
// Add it to the field
console.log(elem[idx])
}
But it returned [Object, Object].
So how can I transform a nested array into single concatenated string value?
Here is a stackblitz.
Just use map and use Object.values to get values from object:
mainArray.map(({addresses, ...rest}) => ({...rest, addresses:
addresses.map(s => Object.values(s)).join(', ')}) );
An example:
let mainArray = [
{id: 15475, name: 'Ali', gender: 'Male', addresses: [
{address1: 'Lebanon'},
{address2: 'USA'}]
},
{id: 15475, name: 'Emily', gender: 'Female', addresses: [
{address1: 'UK'},
{address2: 'France'}]
},
];
const result = mainArray.map(({addresses, ...rest}) => ({...rest, addresses: addresses.map(s => Object.values(s)).join(', ')}) );
console.log(result);
If you don't know whether the key is array, then you can try to use reduce method:
const result = mainArray.reduce((a, c)=> {
for (const key in c) {
if (Array.isArray(c[key]))
c[key] = c[key].map(s => Object.values(s)).join(', ');
}
a.push(c);
return a;
},[])
console.log(result);
An example:
let mainArray = [
{id: 15475, name: 'Ali', gender: 'Male', addresses: [
{address1: 'Lebanon'},
{address2: 'USA'}]
},
{id: 15475, name: 'Emily', gender: 'Female', addresses: [
{address1: 'UK'},
{address2: 'France'}]
},
];
const result = mainArray.reduce((a, c)=> {
for (const key in c) {
if (Array.isArray(c[key]))
c[key] = c[key].map(s => Object.values(s)).join(', ');
}
a.push(c);
return a;
},[])
console.log(result);
You could use recursive function to get addresses that will work on any nested structure and get the value if the key starts with address and value is not an object.
const data =[{"id":15475,"name":"Ali","gender":"Male","addresses":[{"address1":"Lebanon"},{"address2":"USA"}]},{"id":15475,"name":"Emily","gender":"Female","addresses":[{"address1":"UK"},{"address2":"France"}]}]
const flat = (data, prev = '') => {
let sep = prev ? ', ' : ''
let result = '';
for (let i in data) {
if (typeof data[i] == 'object') {
result += flat(data[i], prev + result)
} else if (i.startsWith('address')) {
result += sep + data[i]
}
}
return result
}
const result = data.map(({
addresses,
...rest
}) =>
({ ...rest,
addresses: flat(addresses)
}))
console.log(result)
{id: 15475, name: 'Ali', gender: 'Male', addresses: [
{address1: 'Lebanon'},
{address2: 'USA'}]
},
{id: 15475, name: 'Emily', gender: 'Female', addresses: [
{address1: 'UK'},
{address2: 'France'}]
},
];<br>
function toString(arro) {
return arro.reduce(
(acc, rec) => {
return [...acc, Object.values(rec)]
}, []
).join(',')
}
const res = mainArray.map(
it => {
return Object.keys(it).reduce(
(acc, item) => {
if (typeof it[item] === 'object') {
return {...acc, [item]: toString(it[item])}
}
return {...acc, [item]: it[item]}
}, {}
)
}
)```

group by Date in Javascript

I have an array of birthdays
const birthdays = [
{name: 'John', birthday: '08-08-1960'},
{name: 'James', birthday: '08-25-1960'},
{name: 'Mary', birthday: '01-01-1990'},
]
and I need to generate a new array with the birthdays grouped by month-year
const grouped = [
{'08-1960': [
{name: 'John', birthday: '08-08-1960'},
{name: 'James', birthday: '08-25-1960'},
]},
{'01-1990': [
{name: 'Mary', birthday: '01-01-1990'},
]},
]
I was looking at something like this. using moment and lodash
let groupedResults = _.groupBy(results, (result) => moment(result['Date'], 'DD/MM/YYYY').startOf('isoWeek'));
but I canĀ“t imagine how to generate the new array structure (with the month-year as keys) thank you.
update: it should return an array not an object :facepalm
You can use reduce()
Apply reduce() on array of object and set accumulator to empty object {}
Then split() birthday by - and get only get first and third element.
If the key exist is accumulator that concat() new value to it. Otherwise concat() it to empty array [] and then set it as property of accumulator.
const arr = [
{name: 'John', birthday: '08-08-1960'},
{name: 'James', birthday: '08-25-1960'},
{name: 'John', birthday: '01-01-1990'},
]
let res = arr.reduce((ac,a) => {
let key = a.birthday.split('-');
key = `${key[0]}-${key[2]}`;
ac[key] = (ac[key] || []).concat(a);
return ac;
},{})
res = Object.entries(res).map(([k,v]) => ({[k]:v}))
console.log(res)
As explained here https://www.dyn-web.com/javascript/arrays/associative.php you can create arrays where the indexes are strings, but i won't work as a normal array.
Here is a snippet for doing what you want.
const birthdays = [
{name: 'John', birthday: '08-08-1960'},
{name: 'James', birthday: '08-25-1960'},
{name: 'Mary', birthday: '01-01-1990'},
];
const grouped = birthdays.reduce((prev, cur) => {
const date = new Date(cur.birthday);
const key = `${(date.getMonth() + 1)}-${date.getFullYear()}`;
if(!prev[key]){
prev[key] = [ cur ];
}
else{
prev[key].push(cur);
}
return prev;
}, []);
for(let i in grouped){
console.log(grouped[i]);
}

lodash convert Object of Objects to array of objects including key in it

sorry about poor explanation on the title.
basically i want to know if there's better way or shorter code to do my job. i recently started using lodash library.
so,
i have an object of objects like following.
{
key1:{ firstname: john, lastname: doe},
key2:{ firstname: david, lastname: smith},
}
eventually i wanna make them as following array of objects.
[
{ID: key1, firstname: john, lastname: doe },
{ID: key2, firstname: david, lastname: smith}
]
and this is what i did.
const ArrayOfObjects = [];
_.each(ObjectsOfObjects, (value, key) => {
ArrayOfObjects.push({
UID: key,
...value,
});
})
Lodash's _.map() can iterate objects. The 2nd param passed to the iteratee function is the key:
const input = {
key1:{ firstname: 'john', lastname: 'doe'},
key2:{ firstname: 'david', lastname: 'smith'},
}
const result = _.map(input, (v, UID) => ({ UID, ...v }))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
Use Object.entries and .map:
const input = {
key1:{ firstname: 'john', lastname: 'doe'},
key2:{ firstname: 'david', lastname: 'smith'},
}
const transformed = Object.entries(input).map(([key, obj]) => Object.assign(obj, { ID: key }));
console.log(transformed);

Categories

Resources