deep filtering on array of objects - javascript

I have data like this one below
let data = [
{
name: 'basic',
to: 'aaa',
subMenus: [
{
name: 'general conc',
to: 'geneal',
},
{
name: 'example view',
to: 'example',
},
{
name: 'fancy',
to: 'bbb',
innerSubMenus: [
{
name: 'adding',
to: 'add',
},
{
name: 'getting',
to: 'get',
},
]
}
]
}
]
I need to filter data based on name (in main, subMenus, and innerSubMenus)
Here is the piece of code
function deepFilter(inputText){
data.filter(items => items.name.toLowerCase().includes(inputText.toLowerCase()))
}
As you can see, the function filters the first prop (name --> basic in this case when inputText = 'basic', doesn't work when inputText = 'general conc') but I want to be able to filter names in subMenus and innerSubMenus as well. Please, guide me on how to do it. Thanks
expected outputs:
deepFilter('basic') -> true // only this part is covered by my implementation
deepFilter('general conc') -> true
deepFilter('adding') -> true
deepFilter('ffff') -> false //since there is not name with value of 'ffff' in data

I think this should work well.
function deepFilter(inputText, datas) {
return datas.filter(data => {
function checkInsideObj(object, inputText) {
for (let value of Object.values(object)) {
if (object.name && object.name.toLowerCase() === inputText.toLowerCase()) {
return true;
}
if (Array.isArray(value)) {
return value.some(item => {
return checkInsideObj(item, inputText)
}
)
}
}
return false;
}
return checkInsideObj(data, inputText)
}
)
}
deepFilter("input", data)

Related

Create new object from array

I'm trying to create new object with different properties name from Array.
Array is:
profiles: Array(1)
0:
column:
name: "profileName"
title: "Profile name"
status: "Active"
I want to create new function that return object with two properties:
id: 'profileName',
profileStatus: 'Active'
The function that I have create is returning only one property as undefined undefined=undefined.
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.reduce((obj, profile) => {
console.log('profiles', profile);
return ({
...obj,
id: profile.column.name,
profileStatus: profile.status,
});
}, {});
}
The function getProfile is taking as input array 'profiles' from outside,
I've just tested here and this seems to be working actually
const getProfile1 = (p) => p.reduce((obj, profile) =>({
...obj,
id: profile.column.name,
profileStatus: profile.status,
}), {});
You can use map as an alternative.
var profiles = [{"column":{"name": "profileName3","title": "3Profile name"},"status": "Active"},{"column":{"name": "profileName","title": "Profile name"},"status": "Active"}];
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.map(function(profile,v){
return {id:profile.column.name,profileStatus: profile.status};
});
}
console.log(getProfile(profiles));
Whenever I use reduce in this way, I usually index the final object by some sort of an id. As noted in another answer, you could use map in this situation as well. If you really want your final data structure to be an object, however, you could do something like this:
/**
* returns object indexed by profile id
*/
const formatProfiles = (profiles) => {
return profiles.reduce((obj, profile) => {
return {
...obj,
[profile.id]: {
id: profile.column.name,
profileStatus: profile.status,
}
};
}, {});
};
const profiles = [
{
id: 0,
status: 'active',
column: {
name: "profile_name_1",
title: "profile_title_1",
},
},
{
id: 1,
status: 'inactive',
column: {
name: "profile_name_2",
title: "profile_title_2",
}
}
];
const result = formatProfiles(profiles);
/**
* Result would look like this:
*/
// {
// '0': { id: 'profile_name_1', profileStatus: 'active' },
// '1': { id: 'profile_name_2', profileStatus: 'inactive' }
// }

How can i check if object is following exactly a certain type format in javascript

I have to check if some items object follow a certain format as below. These items are input to a component and I want to check the validity of the input.
I already wrote some code to check for the validity of the items, but I want to know if there could be a better way to write it?
Thanks!
{
main: {
id: string,
name: string,
},
drilldowns: [
{
id: string,
name: string,
elements: [
{
id: string,
name: string,
}
],
}
],
}
export const isValidItem = (item) => {
if (!item.main || (item.main && !item.main.id))
return false;
if (item.drilldowns) {
const invalidDrilldowns = item.drilldowns.filter(drilldown => {
const invalidDrilldownElements =
drilldown.elements &&
drilldown.elements.filter(element => {
return !element.id;
});
return (
!drilldown.id &&
!drilldown.elements &&
invalidDrilldownElements.length !== 0
);
});
return invalidDrilldowns.length === 0;
}
return true;
};

search in nested array doesn't work as it should work

I have the following function that performs the searches.
public static containsDeep = (text: string) => (value?: any): any => {
if (!value) {
return false;
}
const valueType = typeof value;
if (valueType === 'string') {
return value.toLowerCase().indexOf(text.toLowerCase()) > -1;
}
if (Array.isArray(value)) {
return value.some(VimboUtils.containsDeep(text));
}
if (valueType === 'object') {
return Object.values(value).some(VimboUtils.containsDeep(text));
}
return false;
// tslint:disable-next-line
};
public static searchDeepInArray(array: Array<any>, text: string): any {
if (!array || !text) {
return null;
}
return array.filter(VimboUtils.containsDeep(text));
}
example of an array I get that I search for:
const value = [
{
'config_vimbo': [
{
_id: '1',
title: 'Estrutura FrontEnd - Códigos',
path: '/apps/general-settings/frontend-structure-code',
hidden: 'this._rolesService.hide.isNotVimbo()'
},
{
_id: '2',
title: 'Unidade de medidas CTE',
path: '/apps/general-settings/units-measurement-cte',
hidden: 'this._rolesService.hide.isNotVimbo()'
}
]
},
{
'Sua equipe': [
{
_id: '1',
title: 'Gerencie usuários e permissões',
path: '/apps/general-settings/user-business',
hidden: '!this._rolesService.modules.canView("configuracao")'
}
]
},
{
'Ajustes da sua conta': [
{
_id: '1',
title: 'Unidades de medidas',
path: '/apps/general-settings/units-measurement',
hidden: '!this._rolesService.modules.canView("configuracao")'
}
]
}]
I call the function as follows:
searchDeepInArray(value,'argument');
what is happening that it is filtering, but only if I type the first whole word.
If I type a word that is in the middle of the sentence, it does not search correctly.
I couldn't identify where the error is.
Edit 1
I could reproduce the error better. What happens, when it has more than one item per position, when you type if the word has something that matches, it returns all the items of that possession where the word was found. And should return the position with only the corresponding items. see the example on this link, if I type recebimento comes the position with all items(2) https://i.imgur.com/Uj7MlvK.gif
Solution found, if someone has a better solution, you can leave your comment :)
public static searchDeepInArray(
array: Array<any>,
text: string,
field: string
): any {
if (!array || !text) {
return null;
}
return array.filter(this.containsDeep(text)).map(element => {
const idx = Object.keys(element).length
? Object.keys(element)[0]
: null;
if (!idx) {
return element;
}
return Object.assign({}, element, {
[idx]: element[idx].filter(
subElement =>
subElement[field]
.toLowerCase()
.indexOf(text.toLowerCase()) > -1
)
});
});
}

How to resolve incorrect return result from Ramda reduce function (javascript)

I have a data structure I call a 'spec' which looks like this:
const spec = {
command: {
name: 'name',
description: 'description',
alias: 'alias',
arguments: '_children/Arguments'
},
arguments: {
name: 'name',
alias: 'alias',
optional: 'optional',
description: 'description'
}
};
So the elements inside of command and arguments are properties mapped to paths. The best illustration of this is spec.command.arguments. What I need to do is translate this into another object with the same shape, but the paths are converted into Ramda lenses (using R.lensPath).
So conceptually, this is translated into something like this:
const spec = {
command: {
name: lens('name'),
description: lens('description'),
alias: lens('alias'),
arguments: lens('_children/Arguments')
},
arguments: {
name: lens('name'),
alias: lens('alias'),
optional: lens('optional'),
description: lens('description')
}
};
The above is not meant to be taken literally, it is a pseudo structure. For example lens('_children/Arguments') just represents a lens built using Ramda lensPath.
So here is my code:
const spec = {
command: {
name: 'name',
description: 'description',
alias: 'alias',
arguments: '_children/Arguments'
},
arguments: {
name: 'name',
alias: 'alias',
optional: 'optional',
description: 'description'
}
};
function lensify (spec) {
const result = R.pipe(
R.toPairs,
R.reduce((acc, pair) => {
const field = pair[0];
const path = pair[1];
const lens = R.compose(
R.lensPath,
R.split('/')
)(path);
acc[field] = lens; // Is there something wrong with this, if so what?
return acc;
}, { dummy: '***' }) // list of pairs passed as last param here
)(spec);
// The following log should show entries for 'name', 'description', 'alias' ...
console.log(`+++ lensify RESULT: ${JSON.stringify(result)}`);
return result;
}
function makeLenses (spec) {
const result = {
command: lensify(spec.command),
arguments: lensify(spec.arguments)
};
return result;
}
makeLenses(spec);
The key point of failure I think is inside the reducer function, which returns the updated accumulator (acc[field] = lens;). For some reason which I can't understand, this assignment is being lost, and the accumulator is not being correctly populated on each iteration. As you can see from the code sample, the initial value passed into reduce is an object with a single dummy property. The result of the reduce is incorrectly just this single dummy value and not all the fields with their respective Ramda lenses.
However, what's really gonna bake your noodle is that the exact same code running in Ramda repl exhibits different behaviour, see this code in the repl at: Ramda code
I'm running node version 10.13.0
The result that the Repl code produces is this:
{
'arguments': {
'alias': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'description': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'dummy': '***',
'name': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'optional': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
}
},
'command': {
'alias': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'arguments': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'description': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
},
'dummy': '***',
'name': function (r) {
return function (e) {
return z(function (t) {
return n(t, e)
}, r(t(e)))
}
}
}
}
As you can see, the result looks a bit complicated because the values of each property is the lens created by lensProp.
This is in contrast to the following (note that order of command and arguments is reversed, but this shouldn't be significant):
{
'command': {
'dummy': '***'
},
'arguments': {
'dummy': '***'
}
}
which is being returned in my unit test.
I've wasted about 2 days on this and have now admitted defeat, so hopefully, somebody can shed some light on this. Cheers.
This shows the simplest usage of your output I can imagine, mapping view on the lenses against a common object. It seems to work properly both in the REPL, here in a snippet, and in Node 10.13.0:
const {map, pipe, split, lensPath, view} = ramda
const makeLenses = map ( map ( pipe ( split ('/'), lensPath )))
const applyLensSpec = (lensSpec) => (obj) =>
map ( map ( f => view (f, obj) ), lensSpec)
const spec = {command: {name: "name", description: "description", alias: "alias", arguments: "_children/Arguments"}, arguments: {name: "name", alias: "alias", optional: "optional", description: "description"}};
const myTransform = applyLensSpec(
makeLenses(spec),
)
const testObj = {
name: 'foo',
alias: 'bar',
description: 'baz',
optional: false,
_children: {
Arguments: ['qux', 'corge']
}
}
console .log (
myTransform (testObj)
)
<script src="https://bundle.run/ramda#0.26.1"></script>
An addendum to this post and to conform what Scott said, the reason for this post is the deficiency on JSON.stringify and this is in fact the moral of this story; do not always trust the output of JSON.stringify. Here is a test case that confirms this:
context('JSON.stringify', () => {
it.only('spec/lensSpec', () => {
const spec = {
command: {
name: 'name',
description: 'description',
alias: 'alias',
arguments: '_children/Arguments'
},
arguments: {
name: 'name',
alias: 'alias',
optional: 'optional',
description: 'description'
}
};
const makeLensSpec = R.map(R.map(R.pipe(
R.split('/'),
R.lensPath
)));
const lensSpec = makeLensSpec(spec);
console.log(`INPUT spec: ${JSON.stringify(spec)}`);
// The following stringify does not truly reflect the real value of lensSpec.
// So do not trust the output of JSON.stringify when the value of a property
// is a function as in this case where they are the result of Ramda.lensProp.
//
console.log(`RESULT lensSpec: ${JSON.stringify(lensSpec)}`);
const rename = {
'name': 'rename',
'alias': 'rn',
'source': 'filesystem-source',
'_': 'Command',
'describe': 'Rename albums according to arguments specified.',
'_children': {
'Arguments': {
'with': {
'name': 'with',
'_': 'Argument',
'alias': 'w',
'optional': 'true',
'describe': 'replace with'
},
'put': {
'name': 'put',
'_': 'Argument',
'alias': 'pu',
'optional': 'true',
'describe': 'update existing'
}
}
}
};
// NB, if the output of JSON.stringify was indeed correct, then this following
// line would not work; ie accessing lensSpec.command would result in undefined,
// but this is not the case; the lensSpec can be used to correctly retrieve the
// command name.
//
const name = R.view(lensSpec.command.name, rename);
console.log(`COMMAND name: ${name}`);
});
});
The log statements of note are:
console.log(INPUT spec: ${JSON.stringify(spec)});
which displays this:
INPUT spec: {"command":{"name":"name","description":"description","alias":"alias","arguments":"_children/Arguments"},"arguments":{"name":"name","alias":"alias","optional":"optional","description":"description"}}
console.log(RESULT lensSpec: ${JSON.stringify(lensSpec)});
This is the one at fault (lensSpec contains properties whose values are functions which stringify can't display, so misses them out entirely, giving an incorrect representation:
RESULT lensSpec: {"command":{},"arguments":{}}
console.log(COMMAND name: ${name});
This works as expected:
COMMAND name: rename
NB: I just found this: Why doesn't JSON.stringify display object properties that are functions?

How to create an observable stream of custom objects using RxJS operators

I currently have input which looks as follows
const config = {
'mainA': { sub1: { name: 'test1'}, sub2: { name: 'test2'}},
'mainB': { sub1: { name: 'test3'}, sub2: { name: 'test4'}}
};
I'm trying to write a function (createCustomObservable) which would create an observable using standard RsJS operators as follows
var observable = createCustomObservable(config);
observable.subscribe((x) => console.log(x));
The console output should read as follows
{'mainA': 'test1'} -> {'mainA': 'test2'} -> {'mainB': 'test3'} -> {'mainB': 'test4'}
A series of objects with a single propery
Does anyone have any idea how to realise this using RxJS operators? Any help would be appreciated.
The main problem for what you trying to solve is traverse the object to get all the objects that contains the "name" field, and get their value.
There's no Rx operator to automatically do that, so to achieve this task you can simply use Rx.Observable.create - https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/create.md
const config = {
'mainA': {
sub1: {
name: 'test1'
},
sub2: {
name: 'test2'
}
},
'mainB': {
sub1: {
name: 'test3'
},
sub2: {
name: 'test4'
}
}
};
function traverse(o, func) {
for (var i in o) {
func.apply(this, [i, o[i]]);
if (o[i] !== null && typeof(o[i]) == "object") {
//going on step down in the object tree!!
traverse(o[i], func);
}
}
}
var source = Rx.Observable.create(function(observer) {
traverse(config, function(key, value) {
if (key == "name")
observer.onNext(value);
});
observer.onCompleted();
return function() {
console.log('disposed');
};
});
source.subscribe(function(next) {
console.log(next);
})
Sample: https://jsbin.com/yolufovubi/edit?js,console
we can create a new stream by Observable constructor, You have to manually call next(), error() and complete() functions.
function createCustomObservable(config) {
return new Observable(
observer => {
try {
observer.next(config)
} catch(err) {
observer.error(err);
} finally {
observer.complete();
}
})
}
and use it this way
var observable = createCustomObservable(config);
observable.subscribe((x) => console.log(x));

Categories

Resources