I need to implement a search function for a table.
I got an array of objects with unnecessary object properties.
I need to map the array to get necessary properties and then do the filtration.
This is my code.
const items = [
{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
const tableColumns = ['name', 'country.name'];
const onSearch = (e) => {
e = e.toLowerCase();
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
if (!tableColumns.includes(key)) delete item[key];
});
return item;
});
if (e) {
const result = mappedItems.filter((item) => {
const str = JSON.stringify(item).toLowerCase();
if (str.search(e) >= 0) return item;
});
return result;
} else {
return mappedItems;
}
};
console.log(onSearch('GERMANY'));
In an item object, I only need to get these two fields
const tableColumns = ['name', 'country.name'];
But this only gives me the name property
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
if (!tableColumns.includes(key)) delete item[key];
});
return item;
});
My first question is how to map to expect a result like this
{
name: 'pathum',
country: {
name: 'SL',
},
},
Second question is JSON.stringtfy map whole object. So If I search "name" it will return all the objects becasue "name" is there in the all records in the stringtify string.
How do I avoid keys in the object when doing the stringify?
Hope my question is clear to you all.
How do I modify this code to get that expected functionality?
const tableColumns = ['name', 'country'];
const deleteProp = ['code'];
const mappedItems = items.map((item) => {
Object.keys(item).forEach((key) => {
console.log(key);
if (!tableColumns.includes(key)) delete item[key];
if(key == 'country') delete item[key][deleteProp[0]];
});
return item;
});
This may answer your first question.
You can check if the object has any of the tableColumns paths which includes the searched text. And then get a subset of the filtered objects and only include the tableColumns properties
const items=[{name:"pathum",id:1,status:true,createdAt:"KKKK",country:{name:"SL",code:12,},},{name:"kasun",id:1,status:true,createdAt:"KKKK",country:{name:"USA",code:23,},},{name:"hansi",id:1,status:true,createdAt:"KKKK",country:{name:"GERMANY",code:34}}],
tableColumns = ['name', 'country.name'];
function onSearch(array, e) {
const output = [];
for (const o of array) {
const hasProp = tableColumns.some(path => getProperty(o, path).includes(e))
if (hasProp)
output.push(subSet(o, tableColumns))
}
return output
}
function getProperty(o, path) {
return path.split('.').reduce((acc, p) => acc?.[p], o) || ''
}
function subSet(o, paths) {
const output = {}
for (const path of paths) {
let keys = path.split('.'),
last = keys.pop(),
value = o;
const final = keys.reduce((acc, k) => {
value = value?.[k]
return acc[k] ||= {}
}, output);
final[last] = value?.[last];
}
return output;
}
console.log(onSearch(items, 'pat'));
console.log(onSearch(items, 'kasun'));
First, don't change data. You can clone the data and change it.
And, search should be search. Don't put the data formation in it.
Let's start.
const items = [
{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
// We will use object to get the fields you want. To reuse, you can add more fields you want.
const tableColumns = {
// id: 1,
name: 1,
country: {
name: 1
}
}
// getting the mapped items
const mappedItems = items.map((item) => {
const temp = {};
Object.keys(item).forEach((key) => {
const target = tableColumns[key];
if (target) {
if (typeof target === 'number'){
temp[key] = item[key];
} else {
temp[key] = {};
Object.keys(target).forEach(subKey => temp[key][subKey] = item[key][subKey]);
}
}
});
return temp;
});
// search function, use local varibles
const onSearch = (array, countryName) => {
return array.find(element => element.country.name.toLowerCase() === countryName.toLowerCase())
}
const searchResult = onSearch(mappedItems, 'germany');
console.log(searchResult);
You can just create a new array using Array.map
const items = [{
name: 'pathum',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'SL',
code: 12,
},
},
{
name: 'kasun',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'USA',
code: 23,
},
},
{
name: 'hansi',
id: 1,
status: true,
createdAt: 'KKKK',
country: {
name: 'GERMANY',
code: 34,
},
},
];
let minItems = items.map(function(item) {
return {
"name": item.name,
"country": {
"name": item.country.name
}
}
});
console.log(minItems);
Related
I have an array of objects sample for example :
const data = [
{
Name: 'test_1',
Value: '175',
Description: 'desc_1'
},
{
Name: 'test_2',
Value: '175',
Description: 'desc_2'
}
]
And an env file where I mark the data I want :
Name=true
Value=true
Description=false
How do I filter out the data file to only return the values of the keys Name and Value?
Desired output :
[
{
Name: 'test_1',
Value: '175'
},
{
Name: 'test_2',
Value: '175',
}
]
There are different ways in order to achieve the required output, here I'm making use of Array.map and Array.reduce methods.
const data = [{ Name: 'test_1', Value: '175', Description: 'desc_1', }, {Name: 'test_2', Value: '176', Description: 'desc_2', }];
//In order to access from the env file
/*
const fields = {
Name: process.env.Name,
Value: process.env.Value,
Description: process.env.Description
}
*/
//For the snippet purpose i'm using this hardcoded values
const fields = {
Name: true,
Value: true,
Description: false
}
//Convert the object to an array of keys whose values are needed from the original data
const getConfigData = fields => Object.keys(fields).filter(key => fields[key])
let config = getConfigData(fields);
const getSelectedKeyValues = (data, config) => {
return data.map(obj => config.reduce((acc, c) => (acc[c] = obj[c], acc), {}));
}
console.log("Name & Value:", getSelectedKeyValues(data, config));
.as-console-wrapper {
max-height: 100% !important;
}
You can do it like this:
const data = [
{ Name: 'test_1', Value: '175', Description: 'desc_1' },
{ Name: 'test_2', Value: '175', Description: 'desc_2' }
]
let results = data.map((item) => {
new_item = {};
if (process.env.Name) new_item.Name = item.Name;
if (process.env.Value) new_item.Value= item.Value;
if (process.env.Description) new_item.Description= item.Description;
return new_item;
})
Since your example is a JS array and no JSON string, you can use map:
const result = data.map(d => { return {Name: d.name, Value: d.Value }});
Another solution would be filtering the objects entries when mapping and creating a new object from those entries.
const data = [{
Name: 'test_1',
Value: '175',
Description: 'desc_1'
},
{
Name: 'test_2',
Value: '175',
Description: 'desc_2'
}
]
const status = {
Name: true,
Value: true,
Description: false
}
// props you want to keep
const keep = Object.keys(status).filter((k) => status[k]);
const result = data.map((d) => {
// create object from entries based on what you want to keep
return Object.fromEntries(Object.entries(d).filter(([k]) => {
return keep.includes(k)
}));
})
console.log(result);
This is what my array of logged in users looks like:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
}];
I will concat this table when a new user logs in to my system:
this.connectedUsers = [
...this.connectedUsers,
{
...payload,
id: client.id
},
];
Then my array looks like this: (I give this to better understand)
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
}
];
If the user with the uuid like 663-dda updates his point, I perform this method again.
When I leave it as it is, something like this will be done:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}
];
I want to write a very nice (use ES6+) algorithm that first checks if such an object exists in this array (check by id or by user.uuid). If so, update. If not, add a new object. So it should be like this:
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}
];
In the code where you're updating the array when a user logs in, you could do like this:
if (!this.connectedUsers.find(user => user.user.uuid === payload.user.uuid) {
this.connectedUsers = [
...this.connectedUsers,
{ ...payload, id: client.id },
];
}
const connectedUsers = [{
user: {
uuid: 'b62-2dw',
points: 1,
},
id: "1234567"
},
{
user: {
uuid: '663-dda',
points: 5,
},
id: "33332"
},
{
user: {
uuid: '663-dda',
points: 6,
},
id: "33332"
}];
//let take some sample object
const newObDuplicateUuid = {
user: {
uuid: '663-dda',
points: 6,
},
id: "3333290"
}
const newObDuplicateId = {
user: {
uuid: '756-dda',
points: 6,
},
id: "33332"
}
const newObFresh = {
user: {
uuid: '756-dda',
points: 6,
},
id: "3333290"
}
let checkRule = connectedUsers.every(item => item.user.uuid != newObDuplicateUuid.user.uuid && item.id != newObDuplicateUuid.id)
//return false for same uuid
checkRule = connectedUsers.every(item => item.user.uuid != newObDuplicateId.user.uuid && item.id != newObDuplicateId.id)
//return false for same id
checkRule = connectedUsers.every(item => item.user.uuid != newObFresh.user.uuid && item.id != newObFresh.id)
//return true
console.log('Passed validation :'+checkRule);
const result = checkRule ? [...connectedUsers,newObFresh] : 'There is duplicate value';
console.log(result);
This is my approach. One function that covers both cases. Just pass the array and the new entry and it will return the updated list
function updateOrAddUser(listOfUsers, newEntry) {
let found = false;
const updatedUserList = listOfUsers.map(entry => {
if (entry.user.uuid === newEntry.user.uuid) {
found = true;
return newEntry;
}
return entry;
});
if (!found) {
updatedUserList.push(newUser);
}
return updatedUserList;
}
I need to filter a nested structure, that looks like this, based on query. I need to return all objects, including the parent object of the subtree, which match the query string in object name. Please help, i am stuck.
[
{
name: 'bob',
type: 1,
children: [
{
name: 'bob',
type: 2,
children: [
{
name: 'mike',
type: 3,
children: [
{
name:'bob',
type: 7,
children: []
},
{
name: 'mike',
type: 9,
children: []
}
]
}
]
},
{
name: 'mike',
type: 2
}
]
}
]
Right now i am able to find a match in the tree recursively, but the function returns the object on the first match and does not search deeper on sub levels in the same object. Any suggestions, how i can modify the code to go search through all levels recursively?
return tree.map(copy).filter(function filterNested(node) {
if (node.name.toLowerCase().indexOf(query) !== -1) {
return true;
}
if (node.children) {
return (node.children = node.children.map(copy).filter(filterNested))
.length;
}
});
if I am searching for query 'bob', the expected result should be,
const arr = [
{
name: 'bob',
type: 1,
children: [
{
name: 'bob',
type: 2,
children: [
{
name: 'mike',
type: 3,
children: [
{
name:'bob',
type: 7
},
]
}
]
},
]
}
]
You could reduce the array and build new objects with optional children, if they have a length not zero.
function filter(array, fn) {
return array.reduce((r, o) => {
var children = filter(o.children || [], fn);
if (fn(o) || children.length) r.push(Object.assign({}, o, children.length && { children }));
return r;
}, []);
}
var data = [{ name: 'bob', type: 1, children: [{ name: 'bob', type: 2, children: [{ name: 'mike', type: 3, children: [{ name: 'bob', type: 7 }, { name: 'same', typ: 9 }] }] }, { name: 'mike', type: 2 }] }],
result = filter(data, ({ name }) => name.toLowerCase() === 'bob');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Something like this, perhaps?
function nestedFilter(query, nodes) {
return nodes.reduce((result, node) => {
const filteredChildren = node.children && nestedFilter(query, node.children);
const nameMatches = node.name.toLowerCase().indexOf(query) !== -1;
const childrenMatch = filteredChildren && filteredChildren.length;
const shouldKeep = nameMatches || childrenMatch;
return shouldKeep
? result.concat({ ...node, children: filteredChildren })
: result;
}, []);
}
You can use a recursive reduce for this, where you check if there are any children or if the name matches before you accumulate the object:
const example = [{
name: 'bob',
type: 1,
children: [{
name: 'bob',
type: 2,
children: [{
name: 'mike',
type: 3,
children: [{
name: 'bob',
type: 7,
children: []
},
{
name: 'mike',
type: 9,
children: []
}
]
}]
},
{
name: 'mike',
type: 2
}
]
}];
function reduceName(accum, item, matcher) {
item.children = (item.children || []).reduce((a,i)=>reduceName(a,i,matcher),[]);
if (!item.children.length) delete item.children;
if (matcher(item) || item.children) accum.push(item);
return accum;
}
console.log(example.reduce((a,i)=>reduceName(a,i,x=>x.name==='bob'),[]));
I would just use good old visitor pattern to traverse and build new tree.
class Visitor {
constructor(predicate) {
this.predicate = predicate;
}
visit(item) {
if (Array.isArray(item)) {
return this.visitArray(item);
} else if (item) {
return this.visitItem(item);
}
}
visitArray(item) {
const result = [];
for (let e of item) {
const item = this.visit(e);
if (item) {
result.push(item);
}
}
return result;
}
visitItem(item) {
const children = this.visit(item.children);
const hasChildren = (children && children.length > 0);
if (hasChildren || this.predicate(item)) {
return {
name: item.name,
type: item.type,
children: children
}
}
return null;
}
}
const visitor = new Visitor((item) => item.name === "bob");
const result = visitor.visit(data);
console.log(result);
A wonderful time to learn about mutual recursion and continuation passing style
const data =
[{name:'bob',type:1,children:[{name:'bob',type:2,children:[{name:'mike',type:3,children:[ {name:'bob',type:7,children:[]},{name:'mike',type:9,children:[]}]}]},{name:'mike',type:2}]}]
const identity = x => x
const search = (all = [], query = identity, pass = identity) =>
all.flatMap(v => search1(v, query, pass))
const search1 = (one = {}, query = identity, pass = identity) =>
query(one)
? pass([ { ...one, children: search(one.children, query) } ])
: search
( one.children
, query
, children =>
children.length === 0
? pass([])
: pass([ { ...one, children } ])
)
const result =
search(data, x => x.name === "bob")
console.log(JSON.stringify(result, null, 2))
I do think that this question will always be relevant so here is how I did it !
After a few hours ;)
var res = yourArray.filter(function f(el) {
if (el.children.length > 0) {
el.children = el.childreb.filter(f);
}
if (el.name === "bob") return true; // you can put the condition you need here.
})
//res is your returned array
Trully hope this code will benefit some of youl :)
I have 2 arrays of objects
NOTE: status and original-language can't be set manually as they change all the time, these are custom fields. The slugs from fields make up all custom fields.
const items = [
{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
original-language: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
original-language: '7a1cac74217747933bb3915888dea090',
},
];
const fields = [
{
slug: 'status',
options: [
{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f',
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3',
},
],
},
{
slug: 'original-language',
options: [
{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090',
},
],
},
];
status and original-language in [items] have an id value which matches an option in the corresponding fields array.
I am trying to return a new array for [items] with the name from options with the matching id.
eg:
[
{
name: 'Surviving the Game',
status: 'Released',
original-language: 'de',
},
{
name: 'Some Movie',
status: 'Upcoming',
original-language: 'en',
},
];
How would I go about this with ES6/7?
I am not sure where to start
I would accomplish this by creating a lookup object that houses lookups for both your statuses and languages. You can then use this lookup object when mapping through your items.
var items = [
{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
"original-language": 'b4ebbe06702794d1cf375274197267b2'
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
"original-language": '7a1cac74217747933bb3915888dea090'
}
];
var fields = [
{
slug: 'status',
options: [
{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f'
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3'
}
]
},
{
slug: 'original-language',
options: [
{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2'
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090'
}
]
}
];
const lookup = {};
fields.forEach(field => {
lookup[field.slug] = field.options.reduce((all, option) => ({
...all,
[option.id]: option.name
}), {})
});
const translatedItems = items.map(item => {
return Object.entries(item)
.reduce((all, [key, val]) => ({
...all,
[key]: lookup[key] ? lookup[key][val] : val
}),{});
});
console.log(translatedItems);
I'd define a function that obtains the value for a field, like so:
function valueForField(field, id) {
const field = fields.find((itemfields) => itemfields.slug === field);
if(!field) return null;
const option = field.options.find(option => option.id === id);
if(!option) return null;
return option.name;
}
This can then be used like so:
const newItems = items.map(item => {
const { name } = item;
const newItem = {name};
newItem["original-language"] = valueForField('original-language', item["original-language"]);
newItem.status = valueForField('status', item.status);
return newItem;
});
Use map to create a new array of objects having name, status and originalLanguage fields along with the find method to get the name from fields for every status identifier.
const items = [{
name: 'Surviving the Game',
status: 'ffdb29ba075fcbc0b71295c31a13d64f',
originalLanguage: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'Some Movie',
status: 'cd53c082c6ca9e7d3ec66890e66c01f3',
originalLanguage: '7a1cac74217747933bb3915888dea090',
},
];
const fields = [{
slug: 'status',
options: [{
name: 'Released',
id: 'ffdb29ba075fcbc0b71295c31a13d64f',
},
{
name: 'Upcoming',
id: 'cd53c082c6ca9e7d3ec66890e66c01f3',
},
],
},
{
slug: 'original-language',
options: [{
name: 'de',
id: 'b4ebbe06702794d1cf375274197267b2',
},
{
name: 'en',
id: '7a1cac74217747933bb3915888dea090',
},
],
},
],
newArr = items.map(i => ({
name: i.name,
status: fields.find(f => f.slug == 'status').options.find(o => o.id == i.status).name,
originalLanguage: fields.find(f => f.slug == 'original-language').options.find(l => l.id == i.originalLanguage).name
}));
console.log(newArr);
Suppose there are two objects.
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
and the result
{
'1-1':[
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
],
'1-2':[
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
],
'2-1':[
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' },
]
}
Basically, I want to group the data.
I use includes to check if the item from b to match the id from a. Then construct the new array.
This is my attempt(fiddle):
return b.map(item => a.map(jtem => {
if(jtem.id.includes(item)){
return {
[item]: jtem
}
}
}))
For somehow, it doesn't work.
and, is there a clever way to avoid the nested for loop or map function?
You can do that in following steps:
Apply reduce() on the array b
During each iteration use filter() on the the array a
Get all the items from a which starts with item of b using String.prototype.startsWith()
At last set it as property of the ac and return ac
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
let res = b.reduce((ac,b) => {
ac[b] = a.filter(x => x.id.startsWith(b));
return ac;
},{})
console.log(res)
As suggested by #Falco is the comments that It would be better to scan over the a once as its large. So here is that version.Actually its better regarding performance
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
let res = a.reduce((ac,x) => {
let temp = b.find(y => x.id.startsWith(y))
if(!ac[temp]) ac[temp] = [];
ac[temp].push(x);
return ac;
},{})
console.log(res)
Note: startsWith is not supported by I.E. So you can create polyfill using indexOf
if(!String.prototype.startWith){
String.prototype.startsWith = function(str){
return this.indexOf(str) === 0
}
}