I have this response from api
columns: Array(4)
0: "Id"
1: "Title"
2: "Description"
3: "IsActive"
and I need to convert it to this format, so there will "fields" and under the fields it list down the values from the api response and on each value there will be type which I need to determine also if it's Id or IsActive then it will be number. I'm only allow to follow this specific object format and also I need to support IE browser
fields: {
Id: { type: "number" },
Title: { type: "string" },
Description: { type: "string" },
IsActive: { type: "number" }
}
You need to include the additional information about which types are numeric somewhere. This solution stores those in an array, passes this array into a function, and gets back a function which takes an array of columns and returns an object of field definitions.
const makeFields = (numericTypes) => (columns) => columns.reduce(
(a, s) => ({...a, [s]: {type: numericTypes.includes(s) ? 'numeric' : 'string'}}),
{}
)
const numericTypes = ['Id', "IsActive"]
const columns = ["Id", "Title", "Description", "IsActive"]
console.log(makeFields(numericTypes)(columns))
You can save that intermediate function with something like const makeMyFields = makeFields(numericTypes) and then later using it as makeMyFields(columns)
Update
Here is another version that should work in IE (untested):
const makeFields = function(numericTypes) {
return function(columns) {
return columns.reduce(function(a, s) {
a[s] = {type: numericTypes.includes(s) ? 'numeric' : 'string'}
return a
}, {})
}
}
Update 2
You were having problems running this code. I'm guessing that you supplied the parameters incorrectly. Note that this version required you to pass the list of numeric values to get back a function you would then call with the list of columns to get back an object of the types. That is, you had to call it like this:
// makeFields (numericTypes) (columns)
// ^ ^ ^------ call that new function with column names
// | `---- call with list of numeric types, returns a new function
// `-- function name
It's easy enough to change the function so that you can supply all the parameters in one go. But there is an advantage to that formulation. You can call the outer function with the numeric types and get back a reusable function. That inner function can then be applied to any set of columns you choose. It could be passed, say, to map, so that if you had multiple sets of columns, you could simply write multipleColumns.map(makeFields(numericTypes)).
But if you want to change it, the new version might look like this:
const makeFields = function(numericTypes, columns) {
return columns.reduce(function(a, s) {
a[s] = {type: numericTypes.includes(s) ? 'numeric' : 'string'}
return a
}, {})
}
const numericTypes = ['Id', "IsActive"]
const columns = ["Id", "Title", "Description", "IsActive"]
console.log(makeFields(numericTypes, columns))
Related
I have an API that serves JSON data. Currently if you do api/weapons for example it gives you all the weapons available, api/weapons/weaponName gives information about that specific weapon. What I want to do is be able to api/weapons?type=sword&rarity=5 for example. I managed to pull of api/weapons?type=sword and api/weapons?rarity=5 on their own but not together.
Here's what I'm currently doing:
let filtered = [];
if (query.type) {
filtered = filtered.concat((await weapons).filter(w => formatName(w.weaponType) === formatName(query.type)));
}
if (query.rarity) {
filtered = filtered.concat((await weapons).filter(w => w.rarity == query.rarity));
}
if (!filtered.length) filtered = [await weapons]
res.status(HttpStatusCodes.ACCEPTED).send(filtered);
formatName is just a function that makes the string all lowercase and trims it and removes all spaces.
If we take api/weapons?type=sword&rarity=5
I think what's happening right now is:
It is getting all the weapons with the type "sword"
It is getting all the weapons with the rarity "5"
It is joining all the results together, so all the weapons with the type sword (regardless of rarity) and al the weapons with the rarity 5 (regardless of type).
I want it to filter weapons with ONLY that rarity AND ONLY that type. So only 5 rarity swords for example. What is the most beneficial way of handling this
I'd suggest retrieving "weapons" once and then running any filters on them without concatenating the results:
let filtered = [ ...(await weapons) ];
if (query.type) {
filtered = filtered.filter(w => w => formatName(w.weaponType) === formatName(query.type));
}
if (query.rarity) {
filtered = filtered.filter(w => w.rarity == query.rarity);
}
res.status(HttpStatusCodes.ACCEPTED).send(filtered);
Your current logic is testing whether one constraint OR another matches, what you actually need to do is to do an AND, which means you must perform the test in a single pass of filter.
I would slightly modify your code so that you compare all constraints that you're sending...you could further modify the logic below to accept a logical operator to test whether the rarity is >= or <= to a certain number for example.
const weapons = [{
type: 'sword',
name: 'swift blade of zek',
rarity: 5
},
{
type: 'mace',
name: 'hammer of kromzek kings',
rarity: 1
},
{
type: 'sword',
name: 'split blade of thunder',
rarity: 2
},
{
type: 'sword',
name: 'blade of carnage',
rarity: 5
},
]
const getWeapons = (query = {}) => {
let filtered = [];
let constraints = [];
// We could build this object dynamically but I just wanted
// to demonstrate it using your current approach
if (query.hasOwnProperty('type')) {
constraints.push({
name: 'type',
value: query.type
})
}
if (query.hasOwnProperty('rarity')) {
constraints.push({
name: 'rarity',
value: query.rarity
})
}
// Compare all of the conditions and only return weapons
// that match all of the conditions passed.
filtered = weapons.filter(w => {
let matches = 0
constraints.forEach(c => {
if (w[c.name] === c.value) {
matches += 1
}
})
// ensures we only return complete matches
return matches === constraints.length
});
return filtered
}
console.log(getWeapons({
type: 'sword',
rarity: 5
}))
Create an object which has the same property keys as the filters you want to use. Assign a function to each property where the evaluation for that specific filter is specified.
const filters = {
type: (weapon, type) => formatName(weapon.weaponType) === formatName(type),
rarity: (weapon, rarity) => weapon.rarity === rarity,
};
Then loop over the weapons with filter. Inside the filter loop, loop over the keys of the query variable with the every method. This method will return true or false based on if every evaluation is true or not.
In the every loop, use the keys of the query to select the filter from the filters list. Pass the weapon and the values of the query object to these filter functions and return result.
By doing this you can use one, two or no filters at all. And any new filters can be added in the filters object.
const filteredWeapons = weapons.filter((weapon) =>
Object.keys(query).every((filterKey) => {
if (!(filterKey in filters)) {
return false;
}
const filter = filters[filterKey]
const value = query[filterKey]
return filter(weapon, value);
})
);
res.status(HttpStatusCodes.ACCEPTED).send(filteredWeapons);
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.
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' }
Trying to learn a concept.
If I have Object of keyed objects and an array of keys.
const orders = {
"key1" : { id: "key1", number: "ORD001" },
"key3" : { id: "key3", number: "ORD003" },
"key2" : { id: "key2", number: "ORD002" },
};
and an array:
const selectedOrders = ["key1","key2"];
and with the help of Redux Reselect. I want to have a new object like:
const orders = {
"key1" : { id: "key1", number: "ORD001" selected: true},
"key3" : { id: "key3", number: "ORD003" selected: false },
"key2" : { id: "key2", number: "ORD002" selected: true },
};
So later I can iterate over that object via Object.keys(this.orders) and style selected items.
Is this correct to use Reselect for such use-case? If yes, then how should I check-in an efficient and idiomatic way, does an external array contains a given key?
If this idea is totally wrong for such use-case, then how should I do that in the right way?
Addendum: There also could be another array which contains keys in sequence how those orders should be displayed. (User is able to reorder items).
P.S. I don't want to use an array of objects for orders collection.
Yes, you can use reselect to combine two sets of data to produce a third set. Due to reselect's memoization, if the inputs don't change, then the calculation only needs to be performed once.
// You'll need some input selectors to pluck the raw orders from your redux store.
// I'm making these up, since i don't know how your store is arranged.
const getOrders = (state) => state.orders;
const getSelectedOrders = (state) => state.selectedOrders;
const getAugmentedOrders = createSelector(
[getOrders, getSelectedOrders],
(orders, selectedOrders) => {
const augmentedOrders = {};
Object.keys(orders).forEach(key => {
augmentedOrders[key] = {
...orders[key],
selected: selectedOrders.includes(key),
}
});
return augmentedOrders;
}
);
If you have a lot of selected orders, then doing selectedOrders.includes every time through the loop may be a performance problem. In that case i'd create a Set of the selectedOrders, since lookups into the Set will be constant time.
(orders, selectedOrders) => {
const selectedSet = new Set(selectedOrders);
const augmentedOrders = {};
Object.keys(orders).forEach(key => {
augmentedOrders[key] = {
...orders[key],
selected: selectedSet.has(key),
}
});
return augmentedOrders;
}
I'm planning to make a collection to hold different app-wide settings, like, say, amount of logged in users today, Google analytics tracking ID, etc. So I made a schema like this:
options_schema = new SimpleSchema({
key: {
type: String,
unique: true
},
value: {
},
modified: {
type: Date
}
});
Now the main problem is that I want value to be of any type: Number, String, Date, or even custom Objects. Though it has to be present, can't be null.
But of course it gets angry about not specifying the type. Is there a workaround for this?
You can use Match patterns for your fields' type which allow you to do pretty much anything :
const notNullPattern = Match.Where(val => val !== null)
value : {
type : notNullPattern
}
(See Arrow functions)
Note that this will allow everything but null, including undefined.
Defining patterns this way allow you to use them everywhere in your application including in check :
check({
key : 'the key',
modified : Date.now(),
value : {} // or [], 42, false, 'hello ground', ...
}, optionsSchema)
Match.test(undefined, notNullPattern) //true
Match.test({}, notNullPattern) //true
Match.test(null, notNullPattern) //false
A more general solution to exclude one value would simply be:
const notValuePattern =
unwantedValue => Match.Where(val => val !== unwantedValue))
The use of which is similar to the above:
Match.test(42, notValuePattern(null)) // true
Note that due to the use of the identity operator === it will notably fail for NaN:
Match.test(NaN, notValuePattern(NaN)) // true :(
A solution could be:
const notValuePattern =
unwantedValue => Match.Where(val => Number.isNaN(unwantedValue)?
!Number.isNaN(val)
: val !== unwantedValue
)
Should you want a solution to exclude some specific values in a schema (kind of the contrary of Match.OneOf), you could use the following:
const notOneOfPattern = (...unwantedValues) =>
Match.Where(val => !unwantedValues.includes(val)
)
This uses Array.prototype.includes and the ... spread operator. Use as follow:
Match.test(42, notOneOfPattern('self-conscious whale', 43)) // true
Match.test('tuna', notOneOfPattern('tyranny', 'tuna')) // false
Match.test('evil', notOneOfPattern('Plop', 'kittens')) // true
const disallowedValues = ['coffee', 'unicorns', 'bug-free software']
Match.test('bad thing', notOneOfPattern(...disallowedValues)) // true