How can I only add a key to an object if its corresponding function argument exists without excessive if statements? - javascript

I would like to only add the values to the return object if they hold a value otherwise I don't even want to send them. I know this can be achieved with if statement, but how would I do this if there were even more?
As you can see based on the required key being true, in the code I have a few of these arguments that can but don't actually need to exist. I'd like to avoid adding them if that is true.
I am using MongoDB so if I don't need to add the field I don't want to have to, but the only way I know how to achieve this is by writing a bunch of if statements to check if they exist. Is there a better way to do this?
The reason why MongoDB is relevant is that MongoDB is a no SQL database so just because something is an option doesn't mean it has to exist.
What i tried so far:
I tried returning undefined if the item doesn't exist this does not work however Example Below
resolve: (_, { input: { createdBy, name, date, description, cost, link, weights, eventApplicants } }) => {
return dbMutations.createEvent({
createdBy, name, date, description,
cost: cost ? cost : undefined, link: link ? link : undefined, weights: weights && JSON.parse(weights),
eventApplicants: eventApplicants && JSON.parse(eventApplicants)
})
}
Code:
You really just need to look at the resolve function, but I put the rest of the code there for reference.
createEvent: t.fieldWithInput({
input: {
createdBy: t.input.field({
type: 'mongoId',
required: true
}),
name: t.input.string({
required: true
}),
date: t.input.field({
type: 'Date',
required: true
}),
description: t.input.string({
required: true
}),
cost: t.input.string(),
link: t.input.string(),
weights: t.input.string(),
applicants: t.input.string(),
},
type: 'mongoId',
// #ts-ignore
resolve: (_, {
input: {createdBy, name, date, description, cost, link, weights, applicants}
}) => {
let parsedWeights
let parsedApplicants
if (weights) {
parsedWeights = JSON.parse(weights)
}
if (applicants) {
parsedApplicants = JSON.parse(applicants)
}
return dbMutations.createEvent({
createdBy,
name,
date,
description,
cost,
link,
weights: parsedWeights,
applicants: parsedApplicants
})
}
})
My Idea:
I think what would work is just assigning these variables to an arbitrary object. Once we do that we can do some kind of object map and set the result to the return statement.

Here's a thing that removes null or undefined values. You may want to tweak the condition to take out empty strings or other falsy things.
const clean = obj =>
Object.fromEntries(
Object.entries(obj).filter(([k, v]) => v != null)
);
let o = { a: 0, b: null, c: '' };
console.log( clean(o) )
With it, the resolve function can say...
resolve: (_, {
input: {createdBy, name, date, description, cost, link, weights, applicants}
}) => {
// or promote this in scope to use elsewhere...
const clean = obj =>
Object.fromEntries(
Object.entries(obj).filter(([k, v]) => v != null)
);
const event = clean({
createdBy,
name,
date,
description,
cost,
link,
weights: weights ? JSON.parse(weights) : null,
applicants: applicants ? JSON.parse(applicants) : null
});
console.log(event); // examine this to prove event is the object you want
return dbMutations.createEvent(event)
}

Related

How to search nested object by following JSLint

I have my object structured as below and I want to find the product with provided ID.
0 :{
id: 0,
title: 'xxxx',
url: "www.test.com"
quantity: 100
},
1 :{
id: 10,
title: 'xxxx',
url: "www.test.com"
quantity: 100
},
// and so on...
In order to search nested attribute within the object, I have written the below function:
export const selectProductById = (state, productId) => {
const obj_index = Object.keys(state.products).find(function(idx) {
if (state.products[idx].id == productId) {
return idx;
}
}
return state.products[obj_index]
}
This works but I will always get a warning during compilation of my react app.
Expected '===' and instead saw '=='
But if I change this into === the code will not work anymore, does anyone knows how to change this so that it follows JSLint rules ?
It sounds like the productId is not a number. Cast it to a number first:
if (state.products[idx].id === Number(productId)) {
But you should return a truthy or falsey value from the .find callback, not something that you're iterating over (since you may not be sure whether it's truthy or falsey, and it's potentially confusing). Return the result of the === comparison instead:
const { products } = state;
const obj_index = Object.keys(products).find(
key => products[key].id === Number(productId)
);

Destructing and/or mass assigning in ES6

I have 2 sources of data. One of the sources is the "template" to what is acceptable for the data. However, the second source may have a large amount of data that I don't care about (100+ properties in the JSON). Here are the schemas:
// Only store the data we care about. Only a small subset of
// data that I need for this particular dataset.
state = {
isDirty: false,
data: {
name: '',
address: '',
city: '',
state: ''
}
}
The second source will have the 4 attributes in the data schema above (plus many many more I don't care about). Currently, I am assigning them like this:
let updatedData = {};
for(const key in this.state.data) {
updatedData[key] = someDataSource[key];
}
this.state.data = updatedData;
Using ES6, and perhaps destructing, is there a better way to mass assign variables like this?
Thanks again!
EDIT
Added for clarification the assignment after the loop.
Lodash pick can be used to pick specific keys, or helper function can be used for same purpose:
const pick = (obj, keys) => Object.keys(obj)
.filter((key) => keys.indexOf(key) >= 0)
.reduce(
(newObj, key) => Object.assign(newObj, { [key]: obj[key] }),
{}
);
This is already suggested in many related questions. The thing that is specific to this question is:
this.state.data = pick(someDataSource, Object.keys(this.state.data));
Properties can be excluded and modified in the JSON.parse reviver :
var o = JSON.parse('{"a":1, "b":2}', (k, v) => k === 'a' ? void 0 : k === 'b' ? 3 : v)
console.log( o )
A trick you can do (trick because it requires to swallow an error) is to use an non extensible object, using the Object.preventExtensions and then use Object.assign to fill it with data (in a try/catch block).
// Only store the data we care about. Only a small subset of
// data that I need for this particular dataset.
state = {
isDirty: false,
data: {
name: '',
address: '',
city: '',
state: ''
}
}
const newData = {
name:'name',
address:'address',
city:'city',
state:'state',
phone:'phone',
zip:'zip'
}
const updatedData = Object.preventExtensions({...state.data});
try{
Object.assign(updatedData, newData);
} catch(throwaway){};
console.log(updatedData);
And as a function for reuse
function schemaMerge(schema, data) {
const mergedData = Object.preventExtensions({...schema});
try {
Object.assign(mergedData, data);
} catch (throwaway) {};
return ({...mergedData}); // create a new object from the merged one so that it no longer is extensionless
}
// Only store the data we care about. Only a small subset of
// data that I need for this particular dataset.
state = {
isDirty: false,
data: {
name: '',
address: '',
city: '',
state: ''
}
}
const newData = {
name: 'name',
address: 'address',
city: 'city',
state: 'state',
phone: 'phone',
zip: 'zip'
}
const updatedData = schemaMerge(state.data, newData);
state.data = updatedData;
console.log(state.data);

How can i use ramda.js with this code?

I am beginning to use ramda, but have doubts about how to implement functions ramda.
This code is to mount a select object to do queries for sequelize.
See code:
const stringRequired = string => !!string.length
const numberRequired = number => Number.isInteger(number)
const validadeInput = (value, initial, validade) => (
validade(value) ? value : initial
)
const input = (obj, key, initial, validade) => (
typeof obj[key] !== 'undefined' ?
validadeInput(obj[key], initial, validade) :
initial
);
const addValue = (obj, key, value) => {
const prop = {}
prop[key] = value
return Object.assign(obj, prop)
}
const addFilter = (obj, key, value, validate) => (
validate(value) ? addValue(obj, key, value)
: obj
)
const selector = (query = {}) => {
const limit = input(query, 'limit', 10, numberRequired);
const name = input(query, 'name', '', stringRequired);
let select = {}
select = addFilter(select, 'name', name, stringRequired);
select = addFilter(select, 'limit', limit, numberRequired);
return select
}
console.log(selector());
// { limit: 10 }
console.log(selector({ name: 'David Costa' }));
// { limit: 10, name: 'David Costa' }
console.log(selector({ limit: 50 }));
// { limit: 50 }
Or see demo on link
http://jsbin.com/zerahay/edit?js,console,output
Why?
I think you need to consider why you want to convert this to Ramda
(Disclaimer: I'm a Ramda author) Ramda is a library, a toolkit. Use it when it helps clean up your code or when it makes it easier to understand the problem and its solution. Don't use it when it doesn't.
That said, I did refactor it, using Ramda:
A Refactoring
I simply tried to refactor it a bit. In the end, I replaced all your helper functions with a single one that takes a list of conditions such as ['limit', 10, numberRequired] to create a function equivalent to your selector.
I did use a few Ramda functions along the way, but the only one that offers substantive help is assoc, which creates a new object from an old one and a key and value. Using, for instance, compose(Boolean, length) is cleaner than const stringRequired = string => !!string.length, but it's not a large difference.
The important change, to my mind, is the makeSelector function, which makes creating your selector function much more declarative. It's a bit ugly, and I probably would write it differently if I were starting from scratch, but I did this in a series of steps, inlining your helper functions until I had a reasonably short function that had the same behavior.
// rules
const stringRequired = R.compose(Boolean, R.length)
const numberRequired = number => Number.isInteger(number)
// utils
const makeSelector = (conditions) => (query = {}) => R.reduce(
(select, [key, initial, validate]) => {
const value = key in select && validate(select[key]) ? select[key] : initial;
return validate(value) ? R.assoc(key, value, select) : select
},
query,
conditions
)
// main
const selector = makeSelector([
['limit', 10, numberRequired],
['name', '', stringRequired]
])
console.log(selector());
console.log(selector({ name: 'David Costa' }));
console.log(selector({ limit: 50 }));
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
I'm a little hesitant to simply respond with a translated block of code and my recommendation for learning going forward would be to try replace the functions you already have one by one with those from Ramda.
Hesitation aside, the following offers one example of what your code might look like when using various functions from Ramda.
// The existing check for `!!string.length` allows for potential issues with
// arrays being passed in and incorrectly validating, so this will ensure the
// value is indeed a string before checking its length
const stringRequired = R.both(R.is(String), R.complement(R.isEmpty))
// `addFilter` produces a function that takes an object and uses the provided
// function to validate the value associated with the provided key if it exists
// otherwise using the provided `initial` value. If valid, an object containing
// the key and value will be returned, otherwise an empty object is returned.
const addFilter = (validate, initial, key) =>
R.pipe(
R.propOr(initial, key),
R.ifElse(validate, R.objOf(key), R.always({}))
)
// `selector` takes an object and passes it to each function generated by
// calling `addFilter`, merging the resulting objects together.
const selector = (q = {}) =>
R.converge(R.merge, [
addFilter(stringRequired, '', 'name'),
addFilter(Number.isInteger, 10, 'limit')
])(q)
console.log(selector())
console.log(selector({ name: 'David Costa' }))
console.log(selector({ limit: 50 }))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

Typescript - if conditrional inside a map

I am mapping a subset of user data to an object of a refined data set. Inside the map i want to check if a variable is null or undefined, and if yes, then to set this variable to a placeholder value.
The issue I am facing is that declaring an if statement inside the map is causing an error, but even though a map can have an index as a parameter, how can we use it functionally with a conditional Statement? Any insight most appreciated.
return this.UserService.search(url)
.map((data) => {
console.log(data);
data.result = <any> data.result.map((user, index) => ({
// if statement causing error here
if(user.name === null || user.name === undefined){
// error with this if condition
},
id: user.id,
name: user.name,
type: user.type,
password: user.password,
}));
return data;
}).map(data => ({
meta: { totalItems: data.size },
data: data.result,
}));
You're attempting to use an object literal as the return type, but naturally, an if statement (or any statement) can't be inside object literal syntax.
So instead, define a function body, which also uses curly braces, and put your code inside with an explicit return statement.
// Starts function body instead of single expression-v
data.result = <any> data.result.map((user, index) => {
if (some_condition) {
return "some value"; // The object?
} else {
return "other value"; // A different object?
}
/*
// I assume these are to be used in the object you return
id: user.id,
name: user.name,
type: user.type,
password: user.password,
*/
});
You can express conditions in literal maps, but it is somewhat ugly.
return {
a: 1,
...(some_condition && {
b: 1,
})
};
As far as i know you can't do that with JUST a map.
however you could follow it up with a filter() function:
const newArray = oldArray.map((value, index) => condition ? value : null).filter(v => v);
you basicaly iterate over each item and then return the value or null depending on your condition.
Now once you have the map you just filter it by removing the null values from the array.
Notice that the original array is not altered and a new one is returned.
thanks for the idea #user8897421 for the idea. i just wanted to turn it into a one liner.

SimpleSchema match any type but null

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

Categories

Resources