Array of Structs returning unusual data in Solidity - struct

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.

Related

MongoDB query to find most recently added object in an array within a document then making a further query based on that result

I have two collections in MongoDB; users & challenges.
The structure of the users collection looks like this:
name: "John Doe"
email: "john#doe.com"
progress: [
{
_id : ObjectId("610be25ae20ce4872b814b24")
challenge: ObjectId("60f9629edd16a8943d2cab9b")
completed: true
date_completed: 2021-08-06T12:15:32.129+00:00
}
{
_id : ObjectId("611be24ae32ce4772b814b32")
challenge: ObjectId("60g6723efd44a6941l2cab81")
completed: true
date_completed: 2021-08-07T12:15:32.129+00:00
}
]
date: 2021-08-05T13:06:34.129+00:00
The structure of the challenges collection looks like this:
_id: ObjectId("610be25ae20ce4872b814b24")
section_no: 1
section_name: "Print Statements"
challenge_no: 1
challenge_name: "Hello World!"
default_code: "public class Main {public static void main(String[] args) {}}"
solution: "Hello World!"
What I want to be able to do is find the most recent entry in a particular user's 'progress' array within the users collection and based on that result I want to query the challenges collection to find the next challenge for that user.
So say the most recent challenge entry in that user's 'progress' array is...
{
_id : ObjectId("611be24ae32ce4772b814b32")
challenge: ObjectId("60g6723efd44a6941l2cab81")
completed: true
date_completed: 2021-08-07T12:15:32.129+00:00
}
...which is Section 1 Challenge 2. I want to be able to query the challenges collection to return Section 1 Challenge 3, and if that doesn't exist then return Section 2 Challenge 1.
Apologies if this is worded poorly, I am fairly new to MongoDb and unsure of how to create complex queries in it.
Thanks in advance!
One approach:
[
{ // Unwind all arrays
"$unwind":"$progress"
},
{ // Sort in descending order all documents
"$sort":{
"progress.date_completed":-1
}
},
{ // Group them together again but pick only the most recent array element
"$group":{
"_id":"$_id",
"latestProgress":{
"$first":"$progress"
}
}
},
{ // Join with other collection
"$lookup":{
"from":"challenges",
"localField":"latestProgress.challenge",
"foreignField":"challenge",
"as":"Progress"
}
},
{ // Only pick the first array element (since there will be just one)
"$set":{
"Progress":{
"$first":"$Progress"
}
}
}
]
I have provided a comment for each stage so that it would be easier to understand the idea. I'm not confident it's the best approach but it does work since I have tested.
Just a note that there could be a case where Progress field is missing. In that case there is no such challenge document.

Avoid duplicate code when api might return array or single object

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

How to return both error and data in a graphql resolver?

I was thinking about ways of implementing graphql response that would contain both an error and data.
Is it possible to do so without creating a type that would contain error?
e.g.
Mutation addMembersToTeam(membersIds: [ID!]! teamId: ID!): [Member] adds members to some team. Suppose this mutation is called with the following membersIds: [1, 2, 3].
Members with ids 1 and 2 are already in the team, so an error must be thrown that these members cannot be added, but member with an id 3 should be added as he is not in the team.
I was thinking about using formatResponse but seems that I can't get an error there.
Is it possible to solve this problem without adding error field to the return type?
Is it possible to solve this problem without adding error field to the return type?
Unfortunately, no.
A resolver can either return data, or return null and throw an error. It cannot do both. To clarify, it is possible to get a partial response and some errors. A simple example:
const typeDefs = `
type Query {
foo: Foo
}
type Foo {
a: String
b: String
}
`
const resolvers = {
Query: {
foo: () => {},
}
Foo: {
a: () => 'A',
b: () => new Error('Oops!'),
}
}
In this example, querying both fields on foo will result in the following response:
{
"data": {
"foo": {
"a": "A",
"b": null
}
},
"errors": [
{
"message": "Oops",
"locations": [
{
"line": 6,
"column": 5
}
],
"path": [
"foo",
"b"
]
}
]
}
In this way, it's possible to send back both data and errors. But you cannot do so for the same field, like in your question. There's a couple of ways around this. As you point out, you could return the errors as part of the response, which is usually how this is done. You could then use formatResponse, walk the resulting data, extract any errors and combine them with them with any other GraphQL errors. Not optimal, but it may get you the behavior you're looking for.
Another alternative is to modify the mutation so it takes a single memberId. You can then request a separate mutation for each id you're adding:
add1: addMemberToTeam(memberId: $memberId1 teamId: $teamId): {
id
}
add2: addMemberToTeam(memberId: $memberId2 teamId: $teamId): {
id
}
add3: addMemberToTeam(memberId: $memberId3 teamId: $teamId): {
id
}
This can be trickier to handle client-side, and is of course less efficient, but again might get you the expected behavior.
If you think about combining the GraphQL error - there is a way to do it in Apollo.
You need to set errorPolicy to all. That will help you notify users about the error and at the same time have as much data as possible.
none: This is the default policy to match how Apollo Client 1.0
worked. Any GraphQL Errors are treated the same as network errors and
any data is ignored from the response.
ignore: Ignore allows you to
read any data that is returned alongside GraphQL Errors, but doesn’t
save the errors or report them to your UI.
all: Using the all policy
is the best way to notify your users of potential issues while still
showing as much data as possible from your server. It saves both data
and errors into the Apollo Cache so your UI can use them.
But according to best practices, you shouldn't manipulate it in this way.
This is a great article about handling errors in GraphQL.
So, preferable way is to add "errors" field as part of your response and handle it in JS code.
We can achieve this by using a union. I would recommend visiting the great article Handling GraphQL errors like a champ
Example:
Mutation part: We can return the union type for the response & capture the result according to types.
type MemberType {
id: ID!
name: String!
}
enum ErrorType {
BAD_REQUEST_ERROR
FORBIDDEN_ERROR
INTERNAL_SERVER_ERROR
NOT_FOUND_ERROR
UNAUTHORIZED_ERROR
}
type GraphqlError {
type: ErrorType!
code: String!
message: String!
helpLink: URL
}
union UserRegisterResult = MemberType | GraphqlError;
addMembersToTeam(membersIds: [ID!]! teamId: ID!): UserRegisterResult!
Response:
addMembersToTeam(membersIds: [ID!]! teamId: ID!): {
...on MemberType{
id,
name,
}
...on GraphqlError{
id,
message,
statusCode,
}
}

How do I get json data with retrhinkdb

(I'm not good eng.)
I use a group to include dates. I want to get the information out in a row. What do i need to do
.group(r.row('time_create').dayOfWeek())
json export
[
{
group: 1,
reduction: [
{
detail: "no",
id: "37751c10-97ea-4a3a-b2c9-3e8b39383b79",
order_id: "15",
status: "Not_Delivery",
time_create: "2018-09-23T15:25:13.141Z"
}
]
}
]
i want change data json to
{
"date":
{
"Sun": [
{
detail: "no",
order_id: "15",
status: "Not_Delivery",
time_create: "2018-09-28 15:25:13"
}
]
}
}
Do i have to give the information out as i want.
Looks like you tried but didn't manage to transform the data from your previous question. ;)
Here is a proposition, this is not the only way of doing it.
First, it seems you want to remove the id field. You may do that in your ReQL using without:
.group(r.row('time_create').dayOfWeek()).without('id')
(You may apply without('id') before group, it should work the same, see this for more details.)
Then, to transform the result array (let's call it queryResult) into an object (let's call it output):
// prepare the skeleton of the output
let output = {
date: {}
};
// walk the result, filling the output in the process
queryResult.forEach((groupData) => {
let key = groupData.group;
if (!output[key]) {
output[key] = [];
}
output.date[key].push(...groupData.reduction);
})
Now you almost have your desired structure in output, the only thing is that day keys are still numbers and not a short day name. In my opinion, this should be handled by the front-end, since you may want to have different languages implemented for your front-end. But anyway, the idea is always the same: having a translation table that maps Rethink's day numbers with human-readable day names:
const translationTable = {
1: 'Mon',
2: 'Tue',
// ...
7: 'Sun'
};
Now if you do that in your front-end, you just replace the data's keys on the fly, when displaying is needed (or retrieve the key from the day name, depending on how you display stuff). Otherwise, if you go for a back-end implementation (which, again, is clearly not the best solution), you can change one line in the code above (assuming you declared translationTable already):
let key = groupData.group;
// becomes
let key = translationTable[groupData.group];
Feel free to ask in comments if there's something you don't understand!

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