Immutable JS - converting a collection of Maps to a List - javascript

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)

Related

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);

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]);
}

Pipes, ramda with dynamic arguments

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.

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);

How to modify a subset of data without using variables

Using functional Javascript like Underscore, Lodhash, Ramda, Immutable JS, if I have some (semi-accurate) data like this:
var data = {
people: [
{name: 'Vishwanathan Anand', age: 46},
{name: 'Garry Kasparov', age: 52},
{name: 'Magnus Carlsen', age: 25},
],
computers: [
{name: 'Deep Blue', age: 26},
{name: 'Deep Fritz', age: 21},
{name: 'Deep Thought', age: 28},
]
}
I wish to transform it to
var data = {
people: [
{name: 'Vishwanathan Anand', age: 46, rank: 0},
{name: 'Garry Kasparov', age: 52, rank: 1},
{name: 'Magnus Carlsen', age: 25, rank 2},
],
computers: [
{name: 'Deep Blue', age: 26},
{name: 'Deep Fritz', age: 21},
{name: 'Deep Thought', age: 28},
]
}
Note how only the people substructure got rank.
I know I can,
_.extend({
computers: _.map(data.people, (p, i) => {
p.rank = i;
return p;
})}, {
computers: data.computers
})
But what if I need to do this without using any variables (no more access to data!) using underscore's chain?
Something like
_.chain(data).subset('people').map((p, i) => {
p.rank = i;
return p;
})
NOTE This is a real problem and not a matter of convenience. I am working on a project that involves creating a sort of environment for functional operators and variables are not allowed.
It seems Underscore and the like operate on the entire structure (Array / List). is there any way I can ask it to operate on a substructure while preserving the rest?
This solution is a bit unpleasant but it works for this case.
_.chain(data)
.mapObject((value, key) => {
if (key==='people') {
return value.map((p,i) => _.extend(p, {rank: i}));
} else {
return value;
}
})
.value();
With Ramda you can use R.evolve to create a function, that accepts a key and an callback (cb), and maps the items of key to the required form:
const { evolve, addIndex, map } = R
const mapPart = (cb, key) => evolve({
[key]: addIndex(map)(cb)
})
const data = {"people":[{"name":"Vishwanathan Anand","age":46},{"name":"Garry Kasparov","age":52},{"name":"Magnus Carlsen","age":25}],"computers":[{"name":"Deep Blue","age":26},{"name":"Deep Fritz","age":21},{"name":"Deep Thought","age":28}]}
const result = mapPart((o, rank) => ({ ...o, rank }), 'people')(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Categories

Resources