Use regex to access object's fields - javascript

I am in a situation in which I may need to process a JSON that could contain varying key names, which I'd need to access via regex.
For simplicity, I could receive a list similar to:
x=[{ bus: "XYZ123", color:"red" },
{ minibus: "ZZZ999", color:"blue" } ]
and I need to have a function that could get XYZ123 and ZZZ999 within a map function.
I've tried x.map( e => e[/.*bus/]) but to no avail, returning [undefined,undefined]
Does anyone have any trick I could follow for this?
Thank you

var x = [{
bus: "XYZ123",
color: "red"
},
{
minibus: "ZZZ999",
color: "blue"
}
];
console.log(
x.map(it => it[Object.keys(it).find(key => key.endsWith('bus'))])
);
This version of the logic gets all the keys, and then finds the key that ends with bus. Assuming there is only one per object, it gets the first one, and then returns that keys value in each object.

You can use a for ... in loop to find a property name on an object by matching a regular expression.
const regex = /.*bus/;
const result = x.map((e) => {
for (var key in e) {
if (key.match(regex)) {
return e[key];
}
}
return undefined;
});
console.log(result);

Map return mapping... one to one. so if some of your data in array don't fulfills your condition then you return an undefined value on this index in array. eg.if you have array that have 100 items, and only 5 fulfills your condition, then a map will return 100 elements in 95 cases you will get undefined. You should use filter, if you need a new copy of your array you can just create it.
const x=[{ bus: "XYZ123", color:"red" },
{ minibus: "ZZZ999", color:"blue" } ,
{ motobicke: "ZZZ999", color:"black" } ]
const filterBy = (data, regex) => {
return Array.from(data).filter(item => Object.keys(item).some(key => regex.test(key)) )
}
console.log(filterBy(x, /bus/));

Related

Alternative to eval for converting a string to an object

I have a function that is using eval to convert a string with an expression to an object based on the parameter.
let indexType = ["Mac", "User", "Line", "Mask", "Ip", "Location"]
const filterIndex = (item) => {
filteredIndexSearch = []
eval(`search${item}`).forEach((e) => filteredIndexSearch.push(searchData[e.key]))
}
filterIndex(indexType[searchTotal.indexOf(Math.max(...searchTotal))])
searchData is an array that returns values based on the user input.
searchTotal is an array with the length of each search{item} array.
The filterIndex function takes the highest value from the searchData array and corresponds it to the indexType array, then use eval to convert the string to an object to pass the value to the filteredIndexSearch array.
What would be a better alternative to eval?
EDIT
To add more information on what this does:
searchData = [
[
{
key: 1,
data: "0123456789101"
},
{
key: 1,
data: "John Smith"
}
],
[
{
key: 2,
data: "0123456789102"
},
{
key: 2,
data: "Jane Smith"
},
]
]
const search = (data, key, container) => {
if (!data) data = "";
if (data.toLowerCase().includes(string)) {
container = container[container.length] = {
key: key,
data: data
}
}
}
const returnSearch = () => {
for (let i = 0; i < searchData.length; i++) {
search(searchData[i][0].data, searchData[i][0].key, searchMac)
search(searchData[i][1].data, searchData[i][1].key, searchUser)
}
}
returnSearch()
The data is incomplete, but hopefully conveys what I'm trying to do.
search will take the user input, and store the information in the corresponding array. If I input "Jo", it will return the searchUser array with only the "John Smith" value and all the other values with the same key. Inputting "102" returns the searchMac with the "0123456789102" value and all other values with the same key.
At the end of the day. I just want to convert search${parameter} to an object without using eval.
Move your global arrays into an object.
Somewhere it appears that you're defining the arrays, something like:
searchMac = [...];
searchUser = [...];
...
Instead of defining them as individual arrays, I'd define them as properties in an object:
searchIndices.Mac = [...];
searchIndices.User = [...];
...
Then, instead of using eval, your can replace your eval().forEach with searchIndices[item].forEach.
If the order of your search isn't important, your can instead loop through the keys of searchIndices:
Object.keys(searchIndices).forEach(item => {
searchIndices[item].forEach(...);
});
This ensures that if you ever add or drop an entry in searchIndices, you won't miss it or accidentally error out on an undefined search index.
Any time you have a situation with variables named x0, x1 etc, that should be a red flag to tell you you should be using an array instead. Variable names should never be semantically meaningful - that is code should never rely on the name of a variable to determine how the code behaves. Convert search0 etc into an array of search terms. Then use:
const filterIndex = (item) => search[item].map(i => searchData[i.key]);
filteredIndexSearch = filterIndex(indexType[searchTotal.indexOf(Math.max(...searchTotal))]);
(simplifying your code). Note that in your code, filteredIndexSearch is modified inside the arrow function. Better to have it return the result as above.

From single array convert to an array of object with keys coming from a JSON response -JAVASCRIPT-

I am receiving a json response from an API call. I need to store its keys, and create an array of an object. I am intending to this array of an object is created dynamically no matter the keys of the response.
I've already got the keys like this:
const json_getAllKeys = data => {
const keys = data.reduce((keys, obj) => (
keys.concat(Object.keys(obj).filter(key => (
keys.indexOf(key) === -1))
)
), [])
return keys
}
That returned an array (using a sample json):
['name','username', 'email']
But I am trying to use that array to create an array of object that looks like this one
[
{
name: "name",
username: "username",
email: "Email",
}
];
I've been trying mapping the array, but got multiple objects because of the loop, and I need a single one to make it work.
keys.map(i=>({i:i}))
[
{ i: 'id' },
{ i: 'name' },
{ i: 'username' },
{ i: 'email' }
]
Any hint would be useful!
Thanks in advance :D
What you're looking for is Object.fromEntries, which is ECMA2019, I believe, so available in Node >=14 and will be provided as a polyfill if you employ babel.
I can't quite discern what your reduce should produce, but given the sample input, I would write
const input = ['name','username', 'email'];
const result = Object.fromEntries(input.map(name => ([name, name])));
// result == { name: 'name', username: 'username', email: 'email' }
You're definitely on the right track. One thing to remember is the map function will return the SAME number of output as input. So in your example, an array of 3 returns an array of 3 items.
For this reason, map alone is not going to give you what you want. You may be able to map => reduce it. However, here is a way using forEach instead. This isn't a strictly functional programming style solution, but is pretty straight forward and depending on use case, probably good enough.
let keys = ['name','username', 'email'] //you have this array
const obj = {}; // empty object to hold result
keys.forEach(i => {
obj[i] = i; // set the object as you want
})
console.log(obj); // log out the mutated object
// { name: 'name', username: 'username', email: 'email' }

How to order list item and push something to the beginning of array

I want to order my dynamic data so that if it includes a particular string, push it to the top of the list item. Then push the rest in. I'm confused about how to go about this.
So I have something like
So if it includes overview, push it to the front and rest push after. I'm kind of confused about how to do this.
You can simply use reduce in combination with the unshift and push methods to return a new Array that organizes itself based on whether or not the value of each object includes "overview":
tree.reduce( (acc, cv) => !cv.value.includes( "overview" ) ?
(acc.push(cv), acc) :
(acc.unshift(cv), acc ), []);
//data setup
let tree = ["A","B","overview","C","D","overview","E","overview"].map(_=>Object({value:_}));
//result
let result = tree.reduce((acc, cv) =>
!cv.value.includes( "overview" ) ?
(acc.push(cv), acc) :
(acc.unshift(cv), acc ), []);
console.log( result);
Your code works to push anything with 'overview' to the front with the unshift call. You do not need the last push call as the array is modified in place with unshift.
You can use Array.prototype.sort() method
const values = [
{ value: 'foo' },
{ value: 'bar' },
{ value: 'baz' },
{ value: 'overview' },
]
function toFront(arr, search) {
return arr.sort(a => -a.value.includes(search))
}
console.log(toFront(values, 'overview'))
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
You could use reduce to populate two arrays, one for each category, and join them at the end:
// Input
const tree = [{value: "A"},{value:"B"},{value:"overview"},{value:"C"},{value:"D"},{value:"overview"},{value:"E"},{value:"overview"}];
// Transformation
const result = [].concat(...tree.reduce(
(acc, obj) => (acc[1-obj.value.includes("overview")].push(obj), acc),
[[], []]
));
console.log( result);

Why call Object.create inside an array.push returns a list of empty objects?

I need to create an array of objects which value of the properties is set dinamically based on an existent array of objects that contains server response.
The flow in this code seems correct to me (also according to some articles on the web) but all I receive as result is an array of n empty objects (neither id: null is displayed).
infoForecasts = [];
buildWidget() {
this.listForecasts.map( (i) => {
const ser = this.utilitiesService;
return this.infoForecasts.push(
Object.create(
{
id: null,
time: ser.getCurrTime(ser.getDateTime(i.dt))
}
)
);
});
}
I also tried to:
...
time: Object.values(ser.getCurrTime(ser.getDateTime(i.dt))
but anything changed.
What am I doing wrong?
The map() method creates a new array with the results of calling a provided function on every element in the calling array.
It should not be use to loop an array, use forEach() instead.
What i suggest you to do is using reduce() like this :
this.listForecasts.reduce(
(acc, cur) => [
...acc,
{
id: null,
time: this.utilitiesService.getCurrTime(
this.utilitiesService.getDateTime(cur.dt)
)
}
],
[]
);
however if you're only trying to push something then Array.push({ id: '', time: '' }) should be perfectly fine !

JavaScript object array filtering with embedded array

I have an object that looks like the following:
let responseData = [
{
"name": "name",
"other": "value",
"anotherField": "blue",
"appRoles": [
{
"code": "roleOne",
"shouldDisplay": true
},
{
"code": "roleTwo",
"shouldDisplay": false
}
]
}
I need to maintain the original structure all while keeping existing properties. I only want to remove/filter out any "appRoles" where "shouldDisplay" is false.
The following works, using a forEach and a filter operation to create a new object array, but is it possible to condense this even more?
let filteredApps;
responseData.forEach((team) => {
let indyTeam = team;
indyTeam.appRoles = team.appRoles.filter((role) => role.shouldDisplay === true);
filteredApps.push(indyTeam);
});
When I use the map operation, I only get an array of the filtered appRoles - missing extra properties on each object such as "name":
let enabledAppRolesOnly =
responseData.map((team) =>
team.appRoles.filter((role) => role.shouldDisplay === true));
array.map function calls a callback for each element of your array, and then push the return value of it to a new array.
from MDN doc:
map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
So in your case, since you return team.appRoles.filter((role) => role.displayByDefault === true) which is your team array, you only get this.
What you could do would be this (in order to fully clone the object):
let responseData = [{
"name": "name",
"appRoles": [
{
"code": "roleOne",
"shouldDisplay": true
},
{
"code": "roleTwo",
"shouldDisplay": false
}
]
}]
let enabledAppRolesOnly = responseData.map(team => {
const appRoles = team.appRoles.filter(role => role.shouldDisplay === true)
return Object.assign({}, team, { appRoles })
});
console.log(enabledAppRolesOnly)
This will achieve your objective non-destructively. It will build a new array for you.
let responseData = [{
name: "name",
appRoles: [{
code: "roleOne",
shouldDisplay: true
}, {
code: "roleTwo",
shouldDisplay: false
}]
}];
let output = responseData.map(o => Object.assign({}, o, {
appRoles: o.appRoles.filter(r => r.shouldDisplay)
}));
console.log(responseData);
console.log(output);
Code explanation -
map
The map function iterates over the whole array and modifying the each item as specified this should be self evident.
Object.assign
This could be the tricky part -
o=>Object.assign({}, o, {appRoles: o.appRoles.filter(r=>r.shouldDisplay)})
From the docs Object.assign is used to copy values from the object.
The first argument {} causes a new object to be created.
The second argument o causes all props from the object o to be copied in the newly created object.
Now, note that we need to modify the appRoles property and keep only those roles which have shouldDisplay as true. That's exactly what the third argument does. It modifies the appRoles property and gives it the new value.
filter
Now the code -
o.appRoles.filter(r=>r.shouldDisplay)
should not be too difficult.
Here we keep only those roles which meet our criterion (namely shouldDisplay should be true)
If you look at the filter function, it expects the callback value to return a boolean value on whose basis it determines whether value has to be kept or not.
So the following code is not even required,
o.appRoles.filter(r=>r.shouldDisplay===true)
This is enough,
o.appRoles.filter(r=>r.shouldDisplay)
There's some missing information in the question. I'll assume the following:
filteredApps should only contain items where there's at least one appRole for display
it is OK if responseData and filteredApps contains references to the same team objects
there are no other references to team objects that need to keep the original data unaffected
As such, you can reduce your code down to this:
let filteredApps = responseData.filter(team =>
(team.appRoles = team.appRoles.filter(role => role.shouldDisplay)).length;
);
The result will be that each team will have only the .shouldDisplay members in its appRoles, and filteredApps will only have teams with at least one appRole with shouldDisplay being true.
You could build a new array with only part who are valid chinldren elements.
let responseData = [{ name: "name", appRoles: [{ code: "roleOne", shouldDisplay: true }, { code: "roleTwo", shouldDisplay: false }] }],
result = responseData.reduce((r, a) => {
var t = a.appRoles.filter(o => o.shouldDisplay);
if (t.length) {
r.push(Object.assign({}, a, { appRoles: t }));
}
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources