4

I added comments to detail the question in hand and my attempts to solve it.

pragma solidity ^0.4.19;

contract Users {
    // a user with some metadata
    struct User {
        bytes32 name;
        bytes32 email;
    }

    // users are aggregated (notice the array) by a unique key  
    mapping(uint => User[]) internal users;

    // and of course we keep track of these unique keys
    // because we can't loop through a mapping
    uint[] internal keys;

    function signUp(bytes32 name, bytes32 email) public {
        // our unique key, users are aggregated by day
        uint key = now / 1 days;

        // if this is the first user within a key, we keep track of that key
        if (users[key].length == 0) keys.push(key);

        // we push the new user to the key's array of users
        users[key].push(User(name, email));
    }

    // so far so good, now we move on to retrieving the data

    // this answers the question of: in what keys there are users?
    // one call, we get all the keys, efficient
    function getKeys() public view returns (uint[]) {
        return keys;
    }

    // now we need to answer the question of: who are the users of the key x?

    // throws, Error: Unsupported or invalid type: tuple
    function getUsers(uint key) public view returns(User[]) {
        return users[key];
    }

    // the following approach works
    // but requires users[key].length calls to the network, not efficient

    // first, we need the number of indices within a key
    function getUsersCount(uint key) public view returns (uint) {
        return users[key].length;
    }

    // then, we need to loop getUsersCount(key) times
    // to retrieve the metadata for each user

    // throws, Error: Unsupported or invalid type: tuple
    function getUser(uint key, uint index) public view returns(User) {
        return users[key][index];
    }

    // same error from before, let's try this
    function getUserModified(uint key, uint index) public view returns(bytes32, bytes32) {
        User memory user = users[key][index];
        return (user.name, user.email);
    }

    // works ..
    // aha! so returns(User) is not supported, we have to destruct
    // or is this limitation of Remix only?

    // the question now is ..
    // how can we return an array of (user.name, user.email)?
    // that is, modifying faulty getUsers(), to avoid multiple getUserModified() calls
}
1

Currently, structs cannot be returned in public/external contract methods.. only internally. As you've noted in your example above (getUserModified), you can return multiple variables, but not particularly helpful for handling a list. That leaves you with returning a list of modified user keys, and then iterating through the list and pulling the name/email associated with each key.

Update: See comment below by user5470921 .. it appears that returning an array of structs is possible when using the ethers lib (rather than web3.js) with solidity ^0.4.19. Just be aware that any web clients running web3.js will have issues.

| improve this answer | |
  • How would you recommend architecting the contract to achieve the desired result? I can see how we can do bytes32[] names; bytes32[] emails[]; and not use structs at all, but that doesn't look good to me, is this how all apps do it nowadays? what if we have 10k users, that network call would be very costly with this new design – user5470921 Feb 13 '18 at 20:36
  • I would probably keep the user struct, and add a separate array that holds Users. I would have your key to User[] mapping be a key to uint[] mapping.. ie key to an array of user ids. – Howard Feb 13 '18 at 21:22
  • The getUsers method would return the array of user ids, but yes, getting data for every user would be a lot of network calls. Also.. are you sure that bytes32 will be sufficient to store your users email addresses? A possibility here could be to storing the user data off-chain. Create a hash of the user record and store the bytes32 hash in your contract. Use that to pull full user data from elsewhere. – Howard Feb 13 '18 at 21:28
  • So basically we run into the same issue, too many network calls. Is this a known issue or is the support not there yet and it will be introduced later on (calling nested goodies externally)? And yes (about your email remark) I wrote this example just to explain what I am trying to achieve it's not the actual code. The off chain idea is a good one, but in this case we actually would like the data itself on the blockchain. – user5470921 Feb 13 '18 at 23:40
  • It's strange because the support for returning a single typical array uint[] foo; is already there, but returning an array within a struct or an array within a mapping (I am guessing also a nested array within an array) seems to be cumbersome. – user5470921 Feb 13 '18 at 23:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.