I have multiple options and apparently, the third party api doesn't like when I try to pass variables that aren't defined. It'll give me a signature error!
what's required is side, symbol, order_type, qty and time_in_force
and that's what I'm passing in the example below
mutation {
setActiveOrder(
side: "Buy",
symbol: "BTCUSD",
order_type: "Market",
qty: 1,
time_in_force: "GoodTillCancel"
) {
data
}
}
but sometimes (order_type === "Limit") and price is required
sometimes there will be either or both take_profit and stop_loss for either of the two possibilities above.
that being said I'm trying to figure out the best way to optionally pass variables without writing if statements for every possibility the hard way
I'd love another perspective
settActiveOrder: async (
_,
{
side,
symbol,
order_type,
qty,
price,
time_in_force,
take_profit,
stop_loss,
reduce_only,
close_on_trigger,
order_link_id,
},
ctx
) => {
const setActiveOrder = async () => {
try {
return await cclient.placeActiveOrder({
side: side,
symbol: symbol,
order_type: order_type,
qty: qty,
// price: price,
time_in_force: time_in_force,
// take_profit: take_profit,
// stop_loss: stop_loss,
// reduce_only: reduce_only,
// close_on_trigger: close_on_trigger,
// order_link_id: order_link_id,
});
} catch (error) {
console.error(error);
}
};
const data = await setActiveOrder();
return { data: data };
},
If you need to build a new object (as you do in your example), and the api doesn't handle 'undefined' values itself, then you do need a way of checking the input and build the object conditionally, but you can do it in a loop.
To clarify, here your code again, with comments:
settActiveOrder: async (
_,
// here you are using "object destructuring".
// Not existing properties will become a valid,
// existing variable with the value >>undefined<<
{ side, symbol, order_type, qty, /* ... */ },
ctx
) => {
/* ... */
// here your are building a new object,
// possibly having existing properties with the value >>undefined<<,
// but you want these properties to not exist
return await cclient.placeActiveOrder({
side: side,
symbol: symbol,
order_type: order_type,
qty: qty,
/* ... */
});
/* ... */
};
Solution:
you could reference the original object, instead of using object destructuring, and loop over its properties, e.g.:
settActiveOrder: async ( _, originalParams, ctx ) => { // <-- no object destructuring
/* ... */
// build new object in a loop, but skip undefined properties
const passedParams = Object.keys( originalParams ).reduce(( acc, key ) => {
if( typeof originalParams[ key ] === 'undefined' ){
return acc;
} else {
return {
...acc,
[key]: originalParams[ key ]
}
}
}, {});
return await cclient.placeActiveOrder( passedParams );
/* ... */
});
Alternative solution:
In case you just need to pass all defined properties, and do not really have to build the object new, then you could simply pass the whole object as it is, instead of using the object destructuring.
Then the not-existing-properties will stay not-existing-properties, instead of becoming existing-propterties-with-value-undefined.
settActiveOrder: async ( _, originalParams, ctx ) => {
/* ... */
return await cclient.placeActiveOrder( originalParams );
/* ... */
});
I'm afraid you won't avoid if statements,
but better approach than writing all combinations would be to declare dictionary and append prefered variables for request
Something like this:
var dictForRequest = {
side: side,
symbol: symbol,
order_type: order_type,
qty: qty,
time_in_force: time_in_force,
};
if(wantPrice)
dictForRequest["price"] = price;
if(wantTakeProfit)
dictForRequest["take_profit"] = take_profit;
....
Than pass dictForRequest to
...
const setActiveOrder = async () => {
try {
return await cclient.placeActiveOrder(dictForRequest);
} catch (error) {
console.error(error);
}
You can use ternary operators. which are if else statements but shorter and easier to write.
{
price: price ? price : 0,
}
// The above code translates to:
if(price) {
price = price
} else {
price = 0
}
Also, with ES6 you don't necessarily have to write it like this:
{
side: side,
symbol: symbol,
order_type: order_type,
}
// These two are the same. You can write it like below.
{
side,
symbol,
order_type
}
The above code is valid as long as the name of the key and the variable name passed to the key as value are the same. JS can figure out and map the right value to the right key, but they must be named exactly the same. Also, this rule is case sensitive. So, price: Price is not the same. it has to be price: price for this rule to hold true.
If you are looking for another option apart from ternary operators, then you can initialize your variables when you declare them. But, initialize them with the let keyword instead of const. Since, const variables are immutable -> let price = 0;
Related
I need to be able to receive data from an external API and map it dynamically to classes. When the data is plain object, a simple Object.assign do the job, but when there's nested objects you need to call Object.assign to all nested objects.
The approach which I used was to create a recursive function, but I stumble in this case where there's a nested array of objects.
Classes
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
}
Function to initialize a class
function create(instance: object, data: any) {
for (const [key, value] of Object.entries(instance)) {
if (Array.isArray(value)) {
for (const element of data[key]) {
// get the type of the element in array dynamically
const newElement = new User();
create(newElement, element)
value.push(newElement);
}
} else if (typeof value === 'object') {
create(value, data[key]);
}
Object.assign(value, data);
}
}
const orgWithError = Object.assign(new Organization(), { admin: { id: 'admin-external' }});
console.log(orgWithError.admin.getFullName()); // orgWithError.admin.getFullName is not a function
const org = new Organization();
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
create(org, data);
// this case works because I manually initialize the user in the create function
// but I need this function to be generic to any class
console.log(org.users[0].getFullName()); // "name surname"
Initially I was trying to first scan the classes and map it and then do the assign, but the problem with the array of object would happen anyway I think.
As far as I understand from your code, what you basically want to do is, given an object, determine, what class it is supposed to represent: Organization, Account or User.
So you need a way to distinguish between different kinds of objects in some way. One option may be to add a type field to the API response, but this will only work if you have access to the API code, which you apparently don't. Another option would be to check if an object has some fields that are unique to the class it represents, like admin for Organization or account for User. But it seems like your API response doesn't always contain all the fields that the class does, so this might also not work.
So why do you need this distinction in the first place? It seems like the only kind of array that your API may send is array of users, so you could just stick to what you have now, anyway there are no other arrays that may show up.
Also a solution that I find more logical is not to depend on Object.assign to just assign all properties somehow by itself, but to do it manually, maybe create a factory function, like I did in the code below. That approach gives you more control, also you can perform some validation in these factory methods, in case you will need it
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
static fromApiResponse(data: any) {
const org = new Organization()
if(data.id) org.id = data.id
if(data.admin) org.admin = User.fromApiResponse(data.admin)
if(data.users) {
this.users = org.users.map(user => User.fromApiResponse(user))
}
return org
}
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
static fromApiResponse(data: any) {
const user = new User()
if(data.id) user.id = data.id
if(data.name) user.name = data.name
if(data.account)
user.account = Account.fromApiResponse(data.account)
return user
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
static fromApiResponse(data: any) {
const acc = new Account()
if(data.id) acc.id = data.id
if(data.money) acc.money = data.money
return acc
}
}
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const organization = Organization.fromApiResponse(data)
I can't conceive of a way to do this generically without any configuration. But I can come up with a way to do this using a configuration object that looks like this:
{
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
}
and a pointer to the root node, 'org'.
The keys of this object are simple handles for your type/subtypes. Each one is mapped to an object that has a _ctor property pointing to a constructor function, and a collection of other properties that are the names of members of your object and matching properties of your input. Those then are references to other handles. For an array, the handle is [surrounded by square brackets].
Here's an implementation of this idea:
const create = (root, config) => (data, {_ctor, ...keys} = config [root]) =>
Object.assign (new _ctor (), Object .fromEntries (Object .entries (data) .map (
([k, v]) =>
k in keys
? [k, /^\[.*\]$/ .test (keys [k])
? v .map (o => create (keys [k] .slice (1, -1), config) (o))
: create (keys [k], config) (v)
]
: [k, v]
)))
class Organization {
constructor () { this.id = 'org1'; this.admin = new User(); this.users = [] }
}
class User {
constructor () { this.id = 'user1'; this.name = 'name'; this.account = new Account() }
getFullName () { return `${this.name} surname`}
}
class Account {
constructor () { this.id = 'account1'; this.money = 10 }
calculate () { return 10 * 2 }
}
const createOrganization = create ('org', {
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
})
const orgWithoutError = createOrganization ({ admin: { id: 'admin-external' }});
console .log (orgWithoutError .admin .getFullName ()) // has the right properties
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const org = createOrganization (data)
console .log (org .users [0] .getFullName ()) // has the right properties
console .log ([
org .constructor .name,
org .admin .constructor.name, // has the correct hierarchy
org .users [0]. account. constructor .name
] .join (', '))
console .log (org) // entire object is correct
.as-console-wrapper {min-height: 100% !important; top: 0}
The main function, create, receives the name of the root node and such a configuration object. It returns a function which takes a plain JS object and hydrates it into your Object structure. Note that it doesn't require you to pre-construct the objects as does your attempt. All the calling of constructors is done internally to the function.
I'm not much of a Typescript user, and I don't have a clue about how to type such a function, or whether TS is even capable of doing so. (I think there's a reasonable chance that it is not.)
There are many ways that this might be expanded, if needed. We might want to allow for property names that vary between your input structure and the object member name, or we might want to allow other collection types besides arrays. If so, we probably would need a somewhat more sophisticated configuration structure, perhaps something like this:
{
org: { _ctor: Organization, admin: {type: 'usr'}, users: {type: Array, itemType: 'usr'} },
usr: { _ctor: User, account: {type: 'acct', renameTo: 'clientAcct'} },
acct: { _ctor: Account }
}
But that's for another day.
It's not clear whether this approach even comes close to meeting your needs, but it was an interesting problem to consider.
I'm testing a function to see if, when called, it will return the proper created list.
To start, I create the elements, using the createDesign.execute() functions. It's tested on another file and working.
Then, I call the function I want to test: listAllDesigns.execute() and store it's value in a variable.
If I console.log(list), it returns the full list properly.
In pseudocode, what I'd like to do is: Expect list array to have an element with the design object and, within it, a design_id that equals "payload3".
How should I write this test?
Is there a better way to do this? (other than checking if list !== empty, please)
it('should return a list of all designs', async () => {
// Create fake payloads
const payload1 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload1' } },
};
const payload2 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload2' } },
};
const payload3 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload3' } },
};
await createDesign.execute(payload1);
await createDesign.execute(payload2);
await createDesign.execute(payload3);
const list = await listAllDesigns.execute();
// expect(list). ????
});
The easiest method would be a combination of expect.arrayContaining and expect.objectContaining like so:
expect(list).toEqual(
expect.arrayContaining([
expect.objectContaining({
design: expect.objectContaining({
design_id: "payload3"
})
})
])
);
While learning NodeJS, I've been battling to write a more concise logic to this code block (see below) that could either introduce recursion or make use of ES6 methods to provide more elegance and better readability.
I'm bothered by the nesting happening on the for of loops
Thoughts?
export function pleaseRefactorMe(measures, metrics, stats) {
if (!Array.isArray(metrics)) metrics = [metrics] //=> returns array [ 'temperature' ]
if (!Array.isArray(stats)) stats = [stats] //> returns array [ 'min', 'max', 'average' ]
let statistics = []
/**** refactor opportunity for nested for of loops ****/
for (let metric of metrics) {
for (let stat of stats) {
try {
let value = calculateStatsForMetric(stat, metric, measure)
if (value) {
statistics.push({
metric: metric,
stat: stat,
value: value
})
}
} catch (err) {
return err
}
}
}
return statistics
}
First, always pass arrays in, methods usually shouldn't do this sort of input validation in JavaScript. Also don't throw in calculateStatsForMetric, if you have throwing code there wrap it in a try/catch and return a falsey value.
Now, you can use higher order array methods like flatMap and map:
Take each metric
For each metric
Take each stat (this calls for a flatMap on a map)
Calculate a function on it
Keep truthy values (this calls for a filter)
Or in code:
export const refactored = (measure, metrics, stats) =>
metrics.flatMap(metric => stats.map(stat => ({
metric,
stat,
value: calculateStatsForMetric(stat, metric, measure)
}))).filter(o => o.value);
A simple approach would be to use forEach -
let statistics = [];
metrics.forEach(m => {
stats.forEach(s => {
let value = calculateStatsForMetric(s, m, measures);
if (value) {
statistics.push({
metric: m,
stat: s,
value: value
});
}
});
});
I'm using npm module traverse to filter data coming from mongodb / mongoose.
I might get this data:
[ { rating: 5,
title: { da: 'Web udvikling', en: 'Web Development' } },
{ rating: 5, title: { da: 'Node.js', en: 'Node.js' } } ]
'da' and 'en' indicates languages. I use traverse to filter mongoose data after current language like this:
var filtered = filterLanguage(lang, results);
// filter json obj by language
var filterLanguage = function(language, obj) {
return traverse(obj).map(function (item) {
if (this.key === language) {
this.parent.update(item);
}
});
};
I then show this in my template:
res.render('index', {
skills: filtered.skills
});
Finally I display it in the jade view:
ul.list-group
each skill, i in skills
if i < 5
li.list-group-item.sidebar-list-item= skill.title
Unfortunately it's displayed with quotes:
<ul>
<li>'Web Development'</li>
<li>'Node.js'</li>
</ul>
These quotes are not there in the unfiltered data (results.skill.title.da). So traverse is adding them. I used the module with 'plain' json and it's working perfectly.
The mongoose data seems plain and simple but of course there are a lot of properties on the prototype. Also traverse stalls if I don't omit '_id' (type bson/objectid) property from result set.
So traverse seems to have problems with mongoose data... Why is this? And how can I fix it?
-- EDIT --
I found a solution:
Before filtering I do this:
var json = JSON.parse(JSON.stringify(results));
var filtered = filterLanguage(lang, json);
This removes the quotes, but I'm not sure exactly what it does. Somehow converting the mongoose result to JSON? An explanation would be highly appreciated.
Fields in Mongoose documents are getters/setters, which seem to confuse either traverse or Jade/Pug.
The shortest method I found that seems to fix all of your issues is pretty ugly:
var filtered = filterLanguage(lang, results.map(r => JSON.parse(JSON.stringify(r))));
A more elaborate version:
var filtered = filterLanguage(lang, results.map(r => {
let j = r.toJSON()
j._id = j._id.toString()
return j;
}));
It would have been helpful to see what is the body of filterLanguage exactly or understand why it's called twice but as it stands, I don't think you need to use the traverse package at all.
A function such as below should do the trick and I even expanded it to work if the data is more tree-like and not as flat as represented in your example.
const reduceByLang = (data, lang) => {
// Look for a `lang` key in obj or
// if not found but still an object, recurse
const reduceByLangObj = (obj) => {
Object.keys(obj).forEach((key) => {
if (obj[key] === null) {
return;
}
if (obj[key][lang]) {
obj[key] = obj[key][lang]; // replace with desired lang
} else if (typeof obj[key] === 'object') {
reduceByLangObj(obj[key]); // recurse
}
});
return obj;
};
if (Array.isArray(data)) {
return data.map(reduceByLangObj);
} else {
return reduceByLangObj(data);
}
};
See example in JS Bin.
Also, if possible at all and if you do this type of selecting very often, I would look into saving the data in a different structure:
{ ratings: x, locales: { en: { title: 'Y' }, { da: { title: 'Z' } } } }
maybe, so that you can pick the selected language easily either in the query itself and/or in the controller.
EDIT: Checking for null.
ers,
I'm having some trouble with this algorithm.
I'm using Redux, though I don't think that is really relevant for this problem. Basically the console.log statement in this code returns only one object, just as it should, but the function A returns an array of the two objects (even the one that didn't pass the test in function C)
I separated the functions into 3 parts to see if that would help me fix it, but I couldn't figure it out still.
Any advice?
const A = (state) => {
// looks through an array and passes down a resource
return state.resources.locked.filter((resource) => {
return B(state, resource);
})
};
// looks through an array and passes down a building
const B = (state, resource) => {
return state.bonfire.allStructures.filter((building) => {
return C(building, resource);
})
};
// checks if building name and resource requirment are the same, and if building is unlocked
// then returns only that one
const C = (building, resource) => {
if (building.unlocked && building.name == resource.requires.structure) {
console.log(resource);
return resource;
}
}
When using filter, do realise that the callback functions you pass to it are expected to return a boolean value indicating whether a particular element needs to be filtered in or out.
But in your case, B does not return a boolean, but an array. And even when that array is empty (indicating no resource matches), such a value will not be considered false by filter, and so the corresponding resource will still occur in the array returned by A.
A quick fix: get the length of the array that is returned by B, and return that instead. Zero will be considered false:
const A = (state) => {
// looks through an array and passes down a resource
return state.resources.locked.filter((resource) => {
return B(state, resource).length; /// <---- length!
})
};
// looks through an array and passes down a building
const B = (state, resource) => {
return state.bonfire.allStructures.filter((building) => {
return C(building, resource);
})
};
// checks if building name and resource requirement are the same, and if building
// is unlocked and then returns only that one
const C = (building, resource) => {
if (building.unlocked && building.name == resource.requires.structure) {
return resource;
}
}
// Sample data. Only x matches.
var state = {
resources: {
locked: [{ // resource
requires: {
structure: 'x'
}
}, { // resource
requires: {
structure: 'y'
}
}]
},
bonfire: {
allStructures: [{ // building
unlocked: true,
name: 'x'
}, { // building
unlocked: true,
name: 'z'
}]
}
};
console.log(A(state));
But better would be to really return booleans at each place where they are expected. So C should just return the result of the condition, and B could use some instead of filter, which not only returns a boolean, but also stops looking further once a match is found. In A you can have the original code now, as you really want A to return data (not a boolean).
Note also that you can use the short-cut notation for arrow functions that only have an expression that is evaluated:
// looks through an array and passes down a resource
const A = state => state.resources.locked.filter( resource => B(state, resource) );
// looks through an array and passes down a building
// Use .some instead of .filter: it returns a boolean
const B = (state, resource) =>
state.bonfire.allStructures.some( building => C(building, resource) );
// checks if building name and resource requirment are the same, and if building
// is unlocked and then returns only that one
// Return boolean
const C = (building, resource) => building.unlocked
&& building.name == resource.requires.structure;
// Sample data. Only x matches.
var state = {
resources: {
locked: [{ // resource
requires: {
structure: 'x'
}
}, { // resource
requires: {
structure: 'y'
}
}]
},
bonfire: {
allStructures: [{ // building
unlocked: true,
name: 'x'
}, { // building
unlocked: true,
name: 'z'
}]
}
};
console.log(A(state));