I want to combine two Javascript Array with Lodash (not concat) - javascript

I have two array like below
const registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
const dates = ['20171225', '20180914'];
and I want they comebine like this
const result = [
{ user: 'Allen', type: 'phone', date: '20171225' }
{ user: 'Allen', type: 'phone', date: '20180914' }
{ user: 'Eric', type: 'email', date: '20171225' }
{ user: 'Eric', type: 'email', date: '20180914' }
];
I think there is an proper function of Lodash to use in this case,
but I can't find it by myself.
Is there any cool geek can help to get out of this hell. Thanks~

I agree to use of plugins instead of rewrite what people have made available. But when It's simple, using a plugin is complicating things, it's better to use the simplier soluce when there is one.
const registers = [{
user: 'Allen',
type: 'phone',
}, {
user: 'Eric',
type: 'email',
}];
const dates = [
'20171225',
'20180914',
];
const arr = registers.reduce((tmp, x) => [
...tmp,
...dates.map(y => ({
...x,
date: y,
})),
], []);
console.log(arr);

One method is with reduce + forEach, but any double loop should work.
const registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
const dates = ['20171225', '20180914'];
let answer = registers.reduce((acc, n) => {
dates.forEach(x => {
acc.push({user: n.user, type: n.type, date: x});
});
return acc;
}, [])
console.log(answer);

In this case, use of lodash doesn't yield much code reduction (you could simply replace the calls to _.map(a, ...) with a.map(...)). But well, here goes:
const registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
const dates = ['20171225', '20180914'];
const result = _.map(registers, ({ user, type }) => _.map(dates, (date) => ({ user, type, date })));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.core.js"></script>

As OP stated to use lodash , Here is a solution
Lodash Variant
var registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
var dates = ['20171225', '20180914'];
let result = _.map(registers, function(value, index, _source){
return _.extend(value, { date: (dates[index] || null)});
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.core.min.js"></script>
Vanilla JS Variant
var registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
var dates = ['20171225', '20180914'];
let result = registers.map(function(value, index, _source){
return Object.assign(value, { date: (dates[index] || null)});
});
console.log(result);
Updated Answer
var registers = [{ user: 'Allen', type: 'phone' }, { user: 'Eric', type: 'email' }];
var dates = ['20171225', '20180914'];
const result = _.flatten(_.map(registers, function(value, index, _source){
return _.map(dates, (v, i, _s) => _.extend(value, { date: v}));
}));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.core.js"></script>

Related

Construct an array of objects after doing a filter for an array in JavaScript

I was able to filter the array but when I'm trying to create an array of objects out of the filtered data, the result appears to be undefined. How do I construct an array of objects in the below format. Could anyone please help?
[{ brand: 'BMW'}, { brand: 'Audi'}]
Snippet
const cars = [{
name: 'BMW',
type: 'Sedan'
}, {
name: 'Audi',
type: 'SUV'
}, {
name: 'BMW',
type: 'SUV'
}]
const result = cars.filter(({
type
}) => type === 'SUV').map((car) => {
brand: car.name
})
console.log(result)
If you want to return an object literal from the arrow function, you need to enclose that object literal in parentheses to distinguish it from a code block, which also happens to be enclosed in curly braces:
result = cars.map(car => ({
brand: car.name
}));
It's funny that your code doesn't cause an error. It's just because there's a label syntax in JavaScript, so your code inside the arrow function basically creates a brand label to a loose value of car.name.
Basically you need to wrap the object in parentheses to distinguish it from a block statement.
const
cars = [{ name: 'BMW', type: 'Sedan' }, { name: 'Audi', type: 'SUV' }, { name: 'BMW', type: 'SUV' }],
result = cars
.filter(({ type }) => type === 'SUV')
.map(({ name: brand }) => ({ brand }));
// ^^^^^^^^^^^ wrap it
console.log(result);
You are missing a pair of parenthesis around the new implicitly returned object from the map function. This is a tricky syntax of es6.
const cars = [{
name: 'BMW',
type: 'Sedan'
}, {
name: 'Audi',
type: 'SUV'
}, {
name: 'BMW',
type: 'SUV'
}]
const result = cars.filter(({
type
}) => type === 'SUV').map((car) => ({
brand: car.name
}))
console.log(result)
For doing this you can declare a variable and return it.
const cars = [{
name: 'BMW',
type: 'Sedan'
}, {
name: 'Audi',
type: 'SUV'
}, {
name: 'BMW',
type: 'SUV'
}]
const result = cars.filter(({
type
}) => type === 'SUV').map((car) => {
let obj = {brand: car.name}
return obj
})
console.log(result)

Transform an array of data by status

I need to filter an array by property:
Here's the data, which I get from the server:
const mockResults = [
{
user: {
firstName: '1',
lastName: '1'
},
status: 'WRONG'
},
{
user: {
firstName: '2',
lastName: '2'
},
status: 'WRONG'
},
{
user: {
firstName: '3',
lastName: '3'
},
status: 'CORRECT'
}
];
To display the data, I need to transform it to a required by ReactNative SectionList format:
const requiredFormat = [
{
status: 'WRONG',
data: [{ user: {firstName: '1', lastName: '1'}}, { user: {firstName: '2', lastName: '2'}}],
},
{
status: 'CORRECT',
data: [{ user: {firstName: '3', lastName: '3'}}],
},
];
Basically, the mockResults should be sorted by status. There can be maximum of 4 statuses: correct, wrong, missed, chosen. All these statuses should include all the data marked with them.
What is the right way to implement this?
I've tried to filter the array, but I'm stuck at this point:
const transformArray = mockResults.filter(item => {
return {
answerStatus: item.status,
data: [item.user]
}
})
You may walk through the array (using Array.prototype.reduce() method) and create new element of resulting array once you see there's no such with current status or append current element data if one exists:
const mockResults = [{user:{firstName:'1',lastName:'1'},status:'WRONG'},{user:{firstName:'2',lastName:'2'},status:'WRONG'},{user:{firstName:'3',lastName:'3'},status:'CORRECT'}],
result = mockResults.reduce((r,{status, ...rest}) => {
const common = r.find(e => e.status == status)
common ?
common.data.push(rest) :
r.push({status, data:[rest]})
return r
}, [])
console.log(result)
.as-console-wrapper {min-height:100%}
You can use reduce() to achieve that:
const mockResults = [{user: { firstName: '1', lastName: '1'}, status: 'WRONG'},{user: {firstName: '2',lastName: '2'},status: 'WRONG'},{user: { firstName: '3',lastName: '3'},status: 'CORRECT'}];
const result = mockResults.reduce((a, {user, status}) => {
const temp = a.find(e => e.status === status);
if (temp) {
temp.data.push({user});
} else {
a.push({status, data: [{user}]});
}
return a;
}, []);
console.log(result);
Read from the Array.prototype.reduce() documentation:
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
I hope that helps!
You can reduce the array onto an object with its keys being the status property and its value an array of users with that status. Then, map over the entries to turn it back into an array of objects.
Note: The reduce function is creating a new object (accumulator) at each index. This is probably not practical for large data sets as it would be really slow.
const toSectionList = results =>
Object.entries(
results.reduce(
(obj, { user, status }) => ({
...obj,
[status]: [...(obj[status] || []), { user }],
}),
{}
)
).map(([status, data]) => ({ status, data }))
// example use:
const requiredFormat = toSectionList(mockResults)
The reduce function in this one modifies the accumulator object instead of creating a new one. It should preform better with larger data sets.
const toSectionList = results =>
Object.entries(
results.reduce((obj, { user, status }) => {
obj[status] = obj[status] || []
obj[status].push({ user })
return obj
}, {})
).map(([status, data]) => ({ status, data }))
That's what I did.
const mockResults = [
{
user: {
firstName: '1',
lastName: '1'
},
status: 'WRONG'
},
{
user: {
firstName: '2',
lastName: '2'
},
status: 'WRONG'
},
{
user: {
firstName: '3',
lastName: '3'
},
status: 'CORRECT'
}
]
function format(data) {
const resultDict = {}
for (let i of data) {
if (!resultDict[i.status]) {
resultDict[i.status] = { data: [] }
}
resultDict[i.status].data.push(i)
delete resultDict[i.status].data[resultDict[i.status].data.length - 1].status
}
const result = []
for (let i in resultDict) {
const res = {
status: i,
data: resultDict[i].data
}
result.push(res)
}
return result
}
console.log(format(mockResults))

How to use array filter criteria agaist object in lodash?

How to filter object ageist another object in lodash? but the criteria object is array..
var form = { type: ['xyz', 'abc'], name: 'pre-1' }
var items = [
{ type: 'xyz', name: 'pre-1' },
{ type: 'abc', name: 'pre-1' },
{ type: 'xyz', name: 'pre-2' },
]
const results = lodash.filter(items, form);
The results should be xyz and abc:
results = [{ type: 'xyz', name: 'pre-1' }, { type: 'abc', name: 'pre-1' }]
Is it something I can do using filter method?
Well, you can use custom filter method with lodash. Try below:
var form = { type: ['xyz', 'abc'], name: 'pre-1' }
var items = [
{ type: 'xyz', name: 'pre-1' },
{ type: 'abc', name: 'pre-1' },
{ type: 'xyz', name: 'pre-2' },
]
let filtered = _.filter(items, (item)=>{
return form.type.indexOf(item.type) >= 0 && form.name == item.name
});
console.log(filtered);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
var form = { type: ['xyz', 'abc'], name: 'pre-1' }
var items = [
{ type: 'xyz', name: 'pre-1' },
{ type: 'abc', name: 'pre-1' },
{ type: 'xyz', name: 'pre-2' },
]
var item = _.filter(items, i => {
return form.type.indexOf(i.type) > -1 && form.name === i.name
});
console.log(item)
Check if form.name === item.name, and if so use Array.includes() to check if the item.type appears in form.type.
Note: I've used the vanilla JS filter and includes methods, but you can substitute them with lodash's _.filter() and _.includes().
const form = { type: ['xyz', 'abc'], name: 'pre-1' }
const items = [{"type":"xyz","name":"pre-1"},{"type":"abc","name":"pre-1"},{"type":"xyz","name":"pre-2"}]
const result = items.filter(item =>
form.name === item.name && form.type.includes(item.type)
)
console.log(result)
Another option with lodash is to use _.intersectionWith(), and in the comperator function check if the item's name is identical to the form's name, and the type exists in the types.
const form = { type: ['xyz', 'abc'], name: 'pre-1' }
const items = [{"type":"xyz","name":"pre-1"},{"type":"abc","name":"pre-1"},{"type":"xyz","name":"pre-2"}]
const result = _.intersectionWith(items, form.type, (item, type) =>
form.name === item.name && item.type === type
)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
If you reduce your items to an object map where items can be found by a combination of the type and name then it can be a simple map by key with or without lodash:
var form = { type: ['xyz', 'abc'], name: 'pre-1' }
var items = [
{ type: 'xyz', name: 'pre-1' },
{ type: 'abc', name: 'pre-1' },
{ type: 'xyz', name: 'pre-2' },
].reduce((r,c) => (r[`${c.type}-${c.name}`] = c, r), {}) // convert to object map
let result = form.type.map(t => items[`${t}-${form.name}`]) // map by key
console.log(result)
With lodash you can use _.keyBy and _.map:
var form = { type: ['xyz', 'abc'], name: 'pre-1' }
var items = _.keyBy([
{ type: 'xyz', name: 'pre-1' },
{ type: 'abc', name: 'pre-1' },
{ type: 'xyz', name: 'pre-2' },
], x => `${x.type}-${x.name}`)
let result = _.map(form.type, t => items[`${t}-${form.name}`])
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
This way from performance stand point you are in the O(N) territory.
Simple in pure JS:
var form = { type: ['xyz', 'abc'], name: 'pre-1' }
var items = [
{ type: 'xyz', name: 'pre-1' },
{ type: 'abc', name: 'pre-1' },
{ type: 'xyz', name: 'pre-2' },
]
const res = items.filter(({ type, name }) => form.type.includes(type) && form.name == name);
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: auto; }
If you really want to, you can use Lodash's _.filter method like so:
var form = { type: ['xyz', 'abc'], name: 'pre-1' }
var items = [
{ type: 'xyz', name: 'pre-1' },
{ type: 'abc', name: 'pre-1' },
{ type: 'xyz', name: 'pre-2' },
]
const res = _.filter(items, ({ type, name }) => form.type.includes(type) && form.name == name);
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: auto; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Filter array within array using single key = single value and single key = multiple value

const personData = {
personID: 1234567,
personList: [{
name: ['name1', 'name2'],
address: false,
pin: 12345
},
{
name: ['name1', 'name2', 'name3'],
address: true,
pin: 45678
},
{
name: ['name1'],
address: false,
pin: 12345
},
]
};
const keys = ['name', 'address', 'pin']
const values = ['name1', 'name2', false, 12345]
let personDataArr = personData.personList.filter(function(e) {
return keys.every(function(a) {
return e[a] &&
e[a].length > 0 ? values.every(x => e[a].includes(x)) : values.includes(e[a])
});
});
console.log(personDataArr);
The code shows the person Data object which includes the information like name, address and pin.
I tried using filter but I am not getting a properly filtered result.
I am executing the code I am getting null, but I am expecting the first person List object.
Could some one take a look into the code and modify if possible and send me the expected result as output.
I will suggest you to have your key value as an object instead of two different arrays
const {personList} = {personID: 1234567,personList: [{name: ['name1', 'name2'],address: false,pin: 12345},{name: ['name1', 'name2', 'name3'],address: true,pin: 45678},{name: ['name1'],address: false,pin: 12345},]};
const filterBy = { name:['name1', 'name2'],address: false,pin:12345 }
let personDataArr = personList.filter(e => {
return Object.keys(e).every(key => {
return Array.isArray(e[key]) ? filterBy[key].every(value=> e[key].includes(value))
: e[key] === filterBy[key]
})
})
console.log(personDataArr);
It will get really hard to reliably filter with the filter criteria you've provided. How should the code determine which of the values are needed for every given key? Not possible.
It'll be better to provide the values in a well structured way, such as
const filterCriteria = [
{
key: 'name',
values: ['name1', 'name2']
}, {
key: 'address',
value: false
}, {
key: 'pin',
value: 12345
}
];
const getPerson = (list, criterias) =>
list.filter(element =>
criterias.every(criteria =>
element.hasOwnProperty(criteria.key) &&
criteria.hasOwnProperty('value') ?
element[criteria.key] === criteria.value :
(criteria.values.every(value => element[criteria.key].includes(value))) && element[criteria.key].every(value => criteria.values.includes(value)))
);
console.log(getPerson(personData.personList, filterCriteria));
<script>
const personData = {
personID: 1234567,
personList: [
{
name: ['name1', 'name2'],
address: false,
pin: 12345
},
{
name: ['name1', 'name2', 'name3'],
address: true,
pin: 45678
},
{
name: ['name1'],
address: false,
pin: 12345
},
]
};
const filterCriteria = [
{
key: 'name',
values: ['name1', 'name2']
}, {
key: 'address',
value: false
}, {
key: 'pin',
value: 12345
}
];
</script>

How to get a subset of a Javascript object with nested properties?

I'm developing with Angular and I have the following Typescript array of objects:
docs = [
{
id: '1',
type: {
id: 1
desc: 'Category 1',
}
title: 'Foo",
date: '2018-06-21',
attachments: [
{ id: 51, filename: 'foo.pdf', title: 'Foo' },
{ id: 20, filename: 'bar.doc', title: 'Bar' }
]
},
{
id: '2',
type: {
id: 2
desc: 'Category 2',
}
title: 'Bar",
date: '2018-06-21',
attachments: [
{ id: 15, filename: 'foobar.xls', title: 'Foobar' },
{ id: 201, filename: 'example.doc', title: 'Example' }
]
}
]
I need to get only a subset of the properties, something like this:
docs = [
{
id: '1',
type: {
id: 1
desc: 'Category 1',
}
attachments: [
{ id: 51 },
{ id: 20 }
]
},
{
id: '2',
type: {
id: 2
desc: 'Category 2',
}
attachments: [
{ id: 15 },
{ id: 201 }
]
}
]
How can I achieve this?
Have I to create a parser or does exist any smart way (such as Lodash) to extract a lite version of the array?
var docs = [{"id":"1","type":{"id":1,"desc":"Category 1"},"title":"Foo","date":"2018-06-21","attachments":[{"id":51,"filename":"foo.pdf","title":"Foo"},{"id":20,"filename":"bar.doc","title":"Bar"}]},{"id":"2","type":{"id":2,"desc":"Category 2"},"title":"Bar","date":"2018-06-21","attachments":[{"id":15,"filename":"foobar.xls","title":"Foobar"},{"id":201,"filename":"example.doc","title":"Example"}]}];
const result = docs.map(({id,type,attachments})=>{
let doc={id,type};
doc.attachments=attachments.map(({id})=>({id}));
return doc;
});
console.log(result);
have a look at this. this works perfectly!
You can use array.map and object destructuring to extract only the wanted properties.
Also use JSON.parse and JSON.stringify to make a copy and avoid side effetcs.
docs2 = JSON.parse(JSON.stringify(docs)).map(
({id, type, attachements}) =>
({ id,
type,
attachements: attachements.map(({id}) => ({id})
})
)
You can use Array.map with object spreading, something like this:
const mapSubset = ({ id, type, attachments }) => {
return { id, type, attachments: attachments.map( {id} => id ) };
};
const subset = docs.map( mapSubset );
I was looking for a non-specific way to accomplish this or any other similar cases, so far I've thought of the following:
Have an IMapping<T> type, that defines the way to map each property.
Have an IMappingFunction<T> interface, that determines how to map a specific thing:
The following code demonstrates it:
type IMapping<T> = {
[P in keyof T]: IMapping<T[P]> | IMappingFunction<T[P]>;
}
interface IMappingFunction<T>{
(t: T): T | Partial<T>
}
class Person{
name: string;
lastName: string;
}
const obj: IMapping<Person> = {
name: s => s.toUpperCase(),
lastName: s => s
}
function map<T>(obj: T, mapping: IMapping<T>) {
return Object.keys(obj)
.map(prop => {
const propMapping = mapping[prop];
return {
key: prop,
value: typeof propMapping === 'function' ?
propMapping(obj[prop]) :
map(obj, propMapping)
};
})
.reduce((acc, prop) => ({...acc, [prop.key]: prop.value}), { });
}
console.log(map({ name: 'Name', lastName: 'LastName'}, obj));
For a runnable snippet check here
do you need to leave the original array intact? If not you can iterate through the list of objects using a for loop and use the 'delete' operator to delete the properties you no longer want.
For example:
var Employee = {
firstname: "Mohammed",
lastname: "Haddad"
}
delete Employee.firstname;
console.log(Employee);
// expected output: { lastname: "Haddad" }

Categories

Resources