multiselect filter array with nested object - javascript

I use vue-multiselect to let user select items with filtering options:
query = "what user type in input field"
allLinks.filter(l=>l.labelName.includes(query))
and it works, but now I would like to extend the filtering to all properties of my object with this structure :
{
"labelName":"LK000056",
"extPort":{
"aPort":"EXTA-EQ001/board10port02",
"zPort":"EXTZ-EQ012/board09port02"
}
}
I would like with one query to get parent object if query match on labelName, aPort or zPort.
it is possible ? or maybe with a conditional way like :
allLinks.filter(l=>if(!l.labelName.includes(query)){l.extport.aPort.includes(query)}else{l.extport.zPort.includes(query)})
thank for helping me
forgive my approximate English I am French

You could recursively flatten the objects to array of strings, then search in them via Array.filter, Array.some & Array.includes:
const data = [{ "labelName":"LK000056", "extPort":{ "aPort":"EXTA-EQ001/board10port02", "zPort":"EXTZ-EQ012/board09port02" } }, { "labelName":"LK000057", "extPort":{ "aPort":"EXTA-EQ001/board123", "zPort":"EXTZ-EQ012/board333" } }]
const flatten = (obj, a=[]) => Object.values(obj)
.reduce((r,c) => (typeof c == 'object' ? flatten(c,a) : r.push(c), r), a)
const search = (d, t) =>
d.filter(x => flatten(x).some(x => x.toLowerCase().includes(t.toLowerCase())))
console.log(search(data, 'board123'))
console.log(search(data, 'LK000056'))
console.log(search(data, 'EXTZ-EQ012'))
Note that this is a generic approach and will work regardless of the nested levels of data, for example:
const data = [{
"A": {
"B": {
"C": {
"data": '11'
},
}
}
}, {
"D": {
"E": {
"data": '22'
}
}
}, {
"F": "33"
}]
const flatten = (obj, a = []) => Object.values(obj)
.reduce((r, c) => (typeof c == 'object' ? flatten(c, a) : r.push(c), r), a)
const search = (d, t) => d.filter(x =>
flatten(x).some(x => x.toLowerCase().includes(t.toLowerCase())))
console.log(search(data, '22'))
console.log(search(data, '11'))
console.log(search(data, '33'))

You can use Object.values to get the values of the object as an array, then use some to test if one or more items match.
const items = [{
"labelName": "LK000056",
"extPort": {
"aPort": "EXTA-EQ001/board10port02",
"zPort": "EXTZ-EQ012/board09port02"
}
}, {
"labelName": "234234",
"extPort": {
"aPort": "abc123",
"zPort": "1234567890"
}
}]
function search(query, data) {
return data.filter(i => {
if (i.labelName.includes(query)) return true
if (Object.values(i.extPort).some(v => v.includes(query))) return true
return false
})
}
console.log(search("EQ001", items))
console.log(search("1234567890", items))
console.log(search("lalalala", items))

if it can help I think found a functional solution let me know if something better can be done ...
allLinks.filter(l=>{
if (l.sdhPort.zSDHPort!=undefined && l.extPort.aPort.toUpperCase().includes(query.toUpperCase())){return true}
if (l.extport.zPort!=undefined && l.extPort.zPort.toUpperCase().includes(query.toUpperCase())){return true}
if (l.labelName!=undefined && l.labelName.toUpperCase().includes(query.toUpperCase())){return true}
})

wow thanks both of you !
it seems really interesting this technic ... but let me some time to understand it
what do think about what I found ?
best regards

Related

Find string in array of objects

I have two different arrays and I am trying to filter the one while looping the other.
const serviceCodes =
[
{
...otherStuff...
"codes": [
"786410010",
"787885010"
]
}
]
and
const laborCost =
[
{
"laborCode": "786410010",
"estimatedCost": {
"value": -1,
"currency": "USD"
}
},
{
"laborCode": "787885010",
"estimatedCost": {
"value": -1,
"currency": "USD"
}
}
]
I am looping through serviceCodes and trying to return only the object that matches the laborCode
const serviceMatchesRow = serviceCodes[0].codes.forEach((code) => {
return laborCost?.find(
(service) => service.laborCode === code,
)
})
This is returning undefined as forEach only runs through it, but if I use .map instead, then it return the 2 objects within laborCost. If I change for .find instead, than it returns an array with the laborCode itself ["786410010", 787885010].
So how can I get around this?
The desired output would be something like:
[{
"laborCode": "786410010",
"estimatedCost": {
"value": -1,
"currency": "USD"
}
}]
forEach method doesn’t return anything
so you should insert the matches objects in new array.
or use .map method
const serviceMatchesRow = [];
serviceCodes[0].codes.map((code) => {
serviceMatchesRow.push(laborCost.find((service) => {
return service.laborCode === code
}
));
});
Problem: You're trying to return from within the foreach block; that will not return anything thats why you see undefined.
Solution: Use map to return while iterating on an array.
const serviceMatchesRow = serviceCodes[0].codes.map((code) => {
return laborCost.find(
(service) => service.laborCode === code,
)
})
Working fiddle: https://jsfiddle.net/240z5u3f/3/

Group list of objects by JSON key

I am trying to reformat my list of objects by grouping a certain key pair in javascript.
Data Format
[{
"sccode": "sccode1",
"val": "1ADA"
}, {
"sccode": "sccode2",
"val": "1ADB"
}, {
"sccode": "sccode1",
"val": "1ADC"
}]
Expected Result
[{
"scCode": "sccode1",
"valCodes": ["1ADA", "1ADC"]
},
{
"scCode": "sccode2",
"valCodes": ["1ADB"]
}
]
I believe I could loop through the array and match my keys, but is there a quick way to reformat this without having to explicitly loop through? I've tried using a reduce function below, but it gives undefined errors with find, which i think has something to do with my formatting.
Tried (?) Code
const resp = data.reduce((acc, ele) => {
const ant = acc.find(x => x.sccode === ele.sccode);
}, []);
Would this do?
const src = [{"sccode":"sccode1","val":"1ADA"},{"sccode":"sccode2","val":"1ADB"},{"sccode":"sccode1","val":"1ADC"}],
result = src.reduce((r,{sccode,val}) => {
const match = r.find(({scCode}) => scCode == sccode)
match ?
match.valCodes.push(val) :
r.push({scCode:sccode, valCodes: [val]})
return r
}, [])
console.log(result)
.as-console-wrapper{min-height:100%;}
Try the following, I use a map to store a partial state to improve performances preventing to search sccode in an array for every initial object.
let partial = [{
"sccode": "sccode1",
"val": "1ADA"
}, {
"sccode": "sccode2",
"val": "1ADB"
}, {
"sccode": "sccode1",
"val": "1ADC"
}].reduce((map, obj) => {
if (!map[obj.sccode]) {
map[obj.sccode] = [obj.val];
} else {
map[obj.sccode].push(obj.val);
}
return map;
}, {})
Object.keys(partial).map(sccode => ({
sccode, valCodes: partial[sccode]
}));
try loaddash/groupby
let groupByResult = groupBy(data, function (n) {
return n.sccode
});
Check this code:
array.reduce(function(res, value){
if(!res[value.sccode]) {
res[value.sccode] = value;
res[value.sccode]['valCodes'] = []
result.push(res[value.sccode]);
}
res[value.sccode]['valCodes'].push(value.val);
return res;
} ,{});
I tested here and it works fine!

How to map items without undefined value in javascript with Array.prototype.map and without Array.prototype.filter()

How to map items without undefined value in javascript with Array.prototype.map and without Array.prototype.filter()
Code:
var testArray = [
{
name: "toto",
totoNumber: "1",
},
{
name: "tata",
totoNumber: "2",
},
{
mame: "error",
},
]
var listOfToto = testArray.map(x => x.totoNumber);
var listOfToto2 = testArray.map(x => x.totoNumber ? x.totoNumber : undefined);
var listOfToto3 = testArray.filter(x => x.totoNumber).map(x => x.totoNumber);
console.log(listOfToto);
console.log(listOfToto2);
console.log(listOfToto3);
Values:
Array ["1", "2", undefined]
Array ["1", "2", undefined]
Array ["1", "2"]
I want to get the last array (["1", "2"]) without using Array.prototype.filter()
(maybe with something else than Array.prototype.map())
Other source:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Why does javascript map function return undefined?
https://codewithhugo.com/avoiding-falsy-values-in-javascript-arrays/
You can use Array.flatMap() and return an empty array [] if the value doesn't exist:
const testArray = [{"name":"toto","totoNumber":"1"},{"name":"tata","totoNumber":"2"},{"mame":"error"}]
const listOfToto2 = testArray.flatMap(x => x.totoNumber ? x.totoNumber : []);
console.log(listOfToto2);
You can use reduce.
let data = testArray.reduce((result, item) => item.totoNumber ? result.concat(item.totoNumber) : result, []);
// Or a slightly more efficient solution
// if you are worried about concat creating a new temporary array at each iteration
let data = testArray.reduce((result, item) => {
if (item.totoNumber) {
result.push(item.totoNumber);
}
return result;
}, []);
But why do you want to avoid filter? It makes the code much more readable (in my opinion), and unless you are dealing with a huge array or working in a very time-sensitive function, this will be a micro-optimization for which you should not sacrifice clarity.
This looks much cleaner in my opinion:
let data = testArray.map(item => item.totoNumber).filter(e => e);
Here are few options you can consider all which require one loop.
var arr = [ { name: "toto", totoNumber: "1", }, { name: "tata", totoNumber: "2", }, { mame: "error", }, ]
// via Array.forEach
let fe = []
arr.forEach(({totoNumber}) => totoNumber ? fe.push(totoNumber) : 0)
// via Array.reduce
let r = arr.reduce((acc,{totoNumber}) => (totoNumber ? acc.push(totoNumber) : 0, acc), [])
// via Array.map acting like Array.forEach
let m = []
arr.map(({totoNumber}) => (totoNumber ? m.push(totoNumber) : 0, totoNumber))
console.log(fe)
console.log(r)
console.log(m)
In reality only the Array.reduce make sense and the usual way to handle your scenario would be via Array.filter and then Array.map but since you asked if you can do it with map only ... you could but map is supposed to return the exact same array length so it is not the best fit really.

JavaScript - Filter object based on multiple values

I need to filter some data based on multiple values. Language, title and slug
[
{
de: "4567uy55",
en: "654321",
lang: [
{
id: "654321",
language: "English",
title: "Title1"
},
{
id: "4567uy55",
language: "German",
title: "Title2"
}
],
slug: 'some-slug'
},
...
]
What I have now returns all objects which have one or part of the filters(in case title is This is a title, the word this should match), but I need to return objects which have all of them.
I used an object flattner just to get all properties and values in one object, but I can't get it to filter the way I need it.
multiFilter = (arr, filters) => {
console.log(filters)
console.log(arr)
let newArray = []
for (let c of arr) {
let flatCourse = flatten(c)
for (let k in flatCourse) {
const keyArr = k.split('/')
const filterKeys = Object.keys(filters)
Object.keys(filters).map((key) => {
if (keyArr.includes(key)) {
const flatVal = flatCourse[k].toString().toLowerCase()
const filterVal = filters[key].toString().toLowerCase()
console.log(flatVal)
console.log(filterVal)
if (flatVal.includes(filterVal)) {
arr = []
arr.push(c)
newArray.push(c)
}
}
})
}
}
return newArray
}
Filters look like this:
[
language:["English"],
title: ["Some title"],
slug:["some slug"]
]
Instead of mixing for loops and functional chaining you could just go with one of them:
multiFilter = (arr, filters) =>
arr.map(flatten).filter(el => // filter out elements from arr
Object.entries(filters).every(([fKey, fValues]) => // ensure that every key is included in the object
Object.entries(el).some(([oKey, oValue]) =>
oKey.split("/").includes(fKey) && fValues.includes(oValue)// make sure that at least one of the values equals the elements value
)
)
);
arr.filter(course => {
// Returns an array of booleans corresponding to whether or not each filter condition is satisfied
return Object.keys(filters).map(key => {
return filters[key].map(filter => {
// Special case here because lang is an array
if (key == 'language' && course.lang != undefined) {
return course.lang.some(lang => lang[key].includes(filter))
}
if (course[key] == undefined) {
return false
}
return course[key].includes(filter)
}).every(result => result == true)
}).every(result => result == true)
})

Filter object of object with RxJS

I have the following stream and I would like to filter and keep only the params that are true(boolean).
{
"colors": {
"red": "",
"yellow": true,
"green": false
},
"size": {
"t5": true,
"t10": "",
"t20": true
}
}
I need the output to look like this:
{
"colors": ["yellow"],
"size": ["t5,t20"]
}
Here are my thougt when I try to deal with this:
I tried to use map but without success. I guess it's because it's an
object and not an array.
All keys and values are dynamic in this object so I can't use them to
manipulate the object.
flatMap() help me a bit but I don't know what to do with it as I
don't know the name of the keys.
this.form.valueChanges
.debounceTime(400)
.flatMap(x => Object.values(x))
.subscribe(console.log)
This is not an rxjs issue, just a plain js mapping:
getTruthyKeys(obj: any): Array<string> {
return Object.keys(obj).filter(key => obj[key] === true);
}
mainKeysToTruthyLists(obj: any): Array<{key: string, truthy: Array<string>}> {
let result = {};
Object.keys(obj).forEach(key => result[key] = getTruthyKeys(obj[key]);
return result;
}
and now you can apply it as a map on your stream:
this.form.valueChanges
.debounceTime(400)
.map(mainKeysToTruthyLists)
.subscribe(console.log)
This isn't really a RxJS question. All that RxJS should do here is map. This can be done with Object.entries:
this.form.valueChanges
.debounceTime(400)
.map(obj => {
return Object.entries(obj)
.reduce((newObj, [key, subObj]) => {
const subArr = Object.entries(subObj)
.filter(([, subValue]) => subValue)
.map(([subKey]) => subKey);
return Object.assign(newObj, { [key]: subArr });
}, {})
})

Categories

Resources