Avoid duplicate code when api might return array or single object - node.js

I'm working with an API to consult car debits. If the car has more than one debit, the API returns an array of debits. If it has only one, it returns a single debit object (not an array with one element).
The problem is that I have to duplicate all the deserialization of this response checking whether the attribute is an array or a single object.
const debits = []
if (car.debits.length > 0) {
car.debits.forEach((debit: any) => {
debits.push({
id: uuidv1(),
description: debit.description,
label: debit.label,
amount: parseInt(debit.amount, 10)
})
})
} else {
debits.push({
id: uuidv1(),
description: debit.description,
label: debit.label,
amount: parseInt(debit.amount, 10)
})
}
Is there any way to simplify this? I showed just a small example but the object is much larger and there are many other attributes that I have to do the same.

If you have control over the API, you should probably have it return an array with a single element. If not, at the start of the function just force it into an array.
car.debits = car.debits.length ? car.debits : [car.debits]
If car.debits.length is undefined, which means it is not an array, you create an array and put the object inside it

Related

Array of Structs returning unusual data in Solidity

I am creating one Voting Smart Contract people can organize one election and voters can vote for their candidate. I have created one function which will return the statistics of ongoing or past elections
///#dev making statistics for all ballot/election
///#return results with all the information of all
function getStatisticsOfAllVote()
public
view
returns (SingleElectionStatistics[] memory )
{
SingleElectionStatistics[] memory results = new SingleElectionStatistics[](BallotArray.length);
for (uint256 i = 0; i < BallotArray.length; i++) {
SingleElectionStatistics memory temp = SingleElectionStatistics(
BallotArray[i]._getName(), //CEO election
BallotArray[i]._getDescription(),//Employees will choose their CEO
BallotArray[i]._getTotalVoteCounted(),//BigNumber { value: "1" }
BallotArray[i]._isVotingEnded(),//true
BallotArray[i]._getWinningProposalName()//John
);
results[i] = temp;
}
return results;
}
Sample returns are added as comments after the function call.
I suppose to get one array of objects. Buts it gave me data unusual format with extra data. Here are the returns data:
[
[
'CEO election',
'Employees will choose their CEO',
BigNumber { value: "1" },
true,
'John',
name: 'CEO election',
description: 'Employees will choose their CEO',
voteCounted: BigNumber { value: "1" },
voteEnded: true,
winningProposalName: 'John'
]
]
Which should return only
[
{
name: 'CEO election',
description: 'Employees will choose their CEO',
voteCounted: BigNumber { value: "1" },
voteEnded: true,
winningProposalName: 'John'
}
]
I may need help from the community. Thanks in advance
I tried with unit tests and also by changing the approaches but it doesn't help at all.
The raw data returned from a node (using eth_call RPC method) is an ABI-encoded byte array, containing each item just once.
The duplication that you see is caused by an offchain framework. Based on other context, I'm assuming that you're using ethers.js.
Their docs page says:
A Result is an array, so each value can be accessed as a positional argument.
Additionally, if values are named, the identical object as its positional value can be accessed by its name.
In your case, the call returns an array of Result types. Since the Solidity return variables are named (your ethers.js instance knows this from the ABI JSON generated during Solidity compilation), each Result type contains both number-indexed and named items.

Most efficient way to compare arrays of objects in typescript?

I am receiving an array of order objects from 2 different APIs and I want to compare if both arrays contain the same number of orders and values of these orders are also same. Here is how an order looks like
class Order {
id: number;
coupon: Coupon;
customerId: number;
delivered: boolean;
orderDate: string;
}
output1: Order[];
output2: Order[];
How can I compare output1 and output without using nested loops and comparing all fiields one by one.
Note:
I don't want to use lodash.
JSON.stringfy() could be an option but I am afraid it can have sorting problem.
Make a function that serializes an order as a string (maybe use JSON.stringify or something like https://www.npmjs.com/package/json-stable-stringify), then compares sets of those.
function key(o: Order) {
// Or maybe return JSON.stringify(o);
return `${o.id}-${o.customerId}-${o.orderDate}`;
}
function sameOrders(a: Order[], b: Order[]) {
const keys = new Set(a.map(key);
const aHasB = b.every(order => keys.delete(key(order)));
return aHasB && keys.size === 0;
}

Vuex State of an Array of Array is undefined

I have a store on Vuex with a socket listener.
This listener add to the state messages an array of array.
export const store = new Vuex.Store({
state: {
messages: []
},
mutations: {
SOCKET_GET_MESSAGES: (state, data) => {
state.messages[data[0].recipient] = data[0].res
// Data[0].recipient = the id of the recipient
// Data[0].res is an object with a login and a message.
},
}
In my console I can see the structure is correct if I do:
console.log(this.$store.state.messages)
with this output:
[__ob__: Observer]
2: Array(5)
> 0: {login: "w", message: "ABCD", id: 65}
> 1: {login: "w", message: "Deux", id: 66}
> 2: {login: "w", message: "Quatre", id: 67}
> 3: {login: "w", message: "J'envoie au deux", id: 69}
> 4: {login: "w", message: "Test", id: 70}
length: 5
__proto__: Array(0)
length: 3
__ob__: Observer {value: Array(3), dep: Dep, vmCount: 0}
__proto__: Array
But if I ask a specific ID I get undefined in my console log.
For example I ask for my first user with a message :
console.log(this.$store.state.messages[2])
Do you know how to solve this issue ?
I read lot of stuff on stackoverflow and on vuex documentation but I don't find an answer.
Thank you in advance for your help.
You're modifying the array directly, without using any actual method to do so. Therefore Vue cannot pick up the change you've done. You need to either use push or some other Vue helpers like $set. Quoting some helper docs:
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes. Always modify arrays by using an Array instance method, or replacing it entirely. Vue provides a convenience method arr.$set(index, value) which is syntax sugar for arr.splice(index, 1, value).
Also, here's a list of all supported mutation methods (wrapped by Vue):
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Thank you everyone,
After some research I want complete the answer of Andrey Popov.
Indeed if we use Vuex we need to use directly:
Vue.set(state.object, key, data)
The this.$set method is not available in Vuex.
It's now working with this method.

Cheerio Map Strange Behaviour

I'm using map with a list of Cheerio results to return an attribute value. What I want is a variable that contains a list of attribute values (in this case ID's), but instead I'm getting the ID's and extra data.
The following code prints a list of ID's:
let ids = $('[data-profileid]').map(function() {
console.log($(this).attr('data-profileid'))
})
Result:
1012938412
493240324
123948532
423948234
...
But, the following code returns the IDs but in a different format:
let ids = $('[data-profileid]').map(function() {
return $(this).attr('data-profileid')
})
console.log(ids)
Results:
...
'69': '234234234,
'70': '9328402397432',
'71': '1324235234',
options:
{ withDomLvl1: true,
normalizeWhitespace: false,
xmlMode: false,
decodeEntities: true },
_root:
{ '0':
{ type: 'root',
name: 'root',
attribs: {},
...
What is all this extra data? It certainly isn't required. I'd rather just have an ordinary array.
According to http://api.jquery.com/map/:
As the return value is a jQuery object, which contains an array, it's
very common to call .get() on the result to work with a basic array.
So it looks like this should work:
let ids = $('[data-profileid]').map(function() {
return $(this).attr('data-profileid')
}).get()
What is all this extra data? It certainly isn't required. I'd rather just have an ordinary array.
Cheerio has a fluent API, meaning most of its functions return an object on which additional functions can be chained. If map just returned an "ordinary array" then you wouldn't be able to call additional Cheerio functions on the result. There aren't a lot of ways you can chain additional function calls onto the result of your map call, which returns an array of strings, but Cheerio's developers (taking a cue from jQuery's developers) chose to keep a consistent API rather than pepper it with special cases.
If you want an ordinary array, though, Cheerio gives you a handy toArray function:
let ids = $('[data-profileid]').map(function() {
return $(this).attr('data-profileid')
});
console.log(ids.toArray());
// => [ '1012938412', '493240324', '123948532', '423948234' ]

Replacing an object in an object array in Redux Store using Javascript/Lodash

I have an object array in a reducer that looks like this:
[
{id:1, name:Mark, email:mark#email.com},
{id:2, name:Paul, email:paul#gmail.com},
{id:3,name:sally, email:sally#email.com}
]
Below is my reducer. So far, I can add a new object to the currentPeople reducer via the following:
const INITIAL_STATE = { currentPeople:[]};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_PERSON:
return {...state, currentPeople: [ ...state.currentPeople, action.payload]};
}
return state;
}
But here is where I'm stuck. Can I UPDATE a person via the reducer using lodash?
If I sent an action payload that looked like this:
{id:1, name:Eric, email:Eric#email.com}
Would I be able to replace the object with the id of 1 with the new fields?
Yes you can absolutely update an object in an array like you want to. And you don't need to change your data structure if you don't want to. You could add a case like this to your reducer:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => {
if (person.id === action.payload.id) {
return action.payload;
}
return person;
}),
};
This can be be shortened as well, using implicit returns and a ternary:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => (person.id === action.payload.id) ? action.payload : person),
};
Mihir's idea about mapping your data to an object with normalizr is certainly a possibility and technically it'd be faster to update the user with the reference instead of doing the loop (after initial mapping was done). But if you want to keep your data structure, this approach will work.
Also, mapping like this is just one of many ways to update the object, and requires browser support for Array.prototype.map(). You could use lodash indexOf() to find the index of the user you want (this is nice because it breaks the loop when it succeeds instead of just continuing as the .map would do), once you have the index you could overwrite the object directly using it's index. Make sure you don't mutate the redux state though, you'll need to be working on a clone if you want to assign like this: clonedArray[foundIndex] = action.payload;.
This is a good candidate for data normalization. You can effectively replace your data with the new one, if you normalize the data before storing it in your state tree.
This example is straight from Normalizr.
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
Can be normalized this way-
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
What's the advantage of normalization?
You get to extract the exact part of your state tree that you want.
For instance- You have an array of objects containing information about the articles. If you want to select a particular object from that array, you'll have to iterate through entire array. Worst case is that the desired object is not present in the array. To overcome this, we normalize the data.
To normalize the data, store the unique identifiers of each object in a separate array. Let's call that array as results.
result: [1, 2, 3 ..]
And transform the array of objects into an object with keys as the id(See the second snippet). Call that object as entities.
Ultimately, to access the object with id 1, simply do this- entities.articles["1"].
If you want to replace the old data with new data, you can do this-
entities.articles["1"] = newObj;
Use native splice method of array:
/*Find item index using lodash*/
var index = _.indexOf(currentPeople, _.find(currentPeople, {id: 1}));
/*Replace item at index using splice*/
arr.splice(index, 1, {id:1, name:'Mark', email:'mark#email.com'});

Resources