39

I am storing data in my contract using a mapping of structs.

For examples sake, say I'm storing employee info (name/address/salary), mapped by their employee ID.

Via a web front-end, I'd like to be able to specify an employee ID, and call a function in my contract (using Web3.JS) that returns the employee info.

How can I access the data if a struct is returned. Is that possible?

5 Answers 5

53

Summary

Return the fields of the struct as separate return variables.

Edit: As of 2021, it's possible to return the struct directly. See this answer.


Example

I'm running this code in my local dev blockchain using the following command:

geth --datadir ~/devdata --dev --nodiscover \
  --mine --minerthreads 1 --port 30301      \
  --maxpeers 0 --verbosity 3 --rpc console

Your web frontend should be able to send transactions to insert the users, and call the functions to get the number of users and and the user info.


Sample Contract

contract SalaryInfo {
    struct User {
        uint salaryId;
        string name;
        string userAddress;
        uint salary;
    }
    User[] public users;

    function addUser(uint _salaryId, string _name, string _userAddress, uint _salary) public returns(uint) {
        users.length++;
        users[users.length-1].salaryId = _salaryId;
        users[users.length-1].name = _name;
        users[users.length-1].userAddress = _userAddress;
        users[users.length-1].salary = _salary;
        return users.length;
    }

    function getUsersCount() public constant returns(uint) {
        return users.length;
    }

    function getUser(uint index) public constant returns(uint, string, string, uint) {
        return (users[index].salaryId, users[index].name, users[index].userAddress, users[index].salary);
    }
}

Flatten The Source Code

I use the stripCrLf method (from How to load Solidity source file into geth) to transform the formatted source into a single line that can be inserted within the geth console. Alternatively, search for a web page that will strip your line breaks from your code. Then assign your code to a JavaScript variable:

> var salaryInfoSource='contract SalaryInfo { struct User { uint salaryId; string name; string userAddress; uint salary; } User[] public users; function addUser(uint _salaryId, string _name, string _userAddress, uint _salary) public returns(uint) { users.length++; users[users.length-1].salaryId = _salaryId; users[users.length-1].name = _name; users[users.length-1].userAddress = _userAddress; users[users.length-1].salary = _salary; return users.length; } function getUsersCount() public constant returns(uint) { return users.length; } function getUser(uint index) public constant returns(uint, string, string, uint) { return (users[index].salaryId, users[index].name, users[index].userAddress, users[index].salary); }}'

Insert Contract Into The Blockchain

The compile the code:

> var salaryInfoCompiled = web3.eth.compile.solidity(salaryInfoSource);

Load the code into the blockchain:

> var salaryInfoContract = web3.eth.contract(salaryInfoCompiled.SalaryInfo.info.abiDefinition);
> var salaryInfo = salaryInfoContract.new({from:web3.eth.accounts[0], data: salaryInfoCompiled.SalaryInfo.code, gas: 1000000}, 
  function(e, contract) {
    if (!e) {
      if(!contract.address) {
        console.log("Contract transaction send: TransactionHash: " + 
          contract.transactionHash + " waiting to be mined...");
      } else {
        console.log("Contract mined! Address: " + contract.address);
        console.log(contract);
      }
    }
  }
)

Wait for the following message to indicate that the contract has been mined:

I0505 09:12:15.712867   27030 xeth.go:1026] Tx(0x7747500b881c8da44efbc3b5d1c2c762f1cd52d2dd74050edbfed10e51a29d8a) created: 0x0bb1d7a6b31f7a7e23e6d902bac0eb5f9c721c54
Contract transaction send: TransactionHash: 0x7747500b881c8da44efbc3b5d1c2c762f1cd52d2dd74050edbfed10e51a29d8a waiting to be mined...
...
Contract mined! Address: 0x0bb1d7a6b31f7a7e23e6d902bac0eb5f9c721c54
[object Object]

Insert Users

And here we are adding 2 users to the contract:

> salaryInfo.addUser(123, "User 123", "123 drive way, the uncentralised kingdom", 100, {from:web3.eth.accounts[0], data: salaryInfoCompiled.SalaryInfo.code, gas: 500000});
"0x7c22797d6b7717beb398a65159b1009fba3bbc9e4917ee1584bed60ea74eac11"
> salaryInfo.addUser(234, "User 234", "234 drive way, the uncentralised kingdom", 200, {from:web3.eth.accounts[0], data: salaryInfoCompiled.SalaryInfo.code, gas: 500000});
"0xed197c9a6fbc70c19cc95bcdc6943e38736c080052e9e1a4f7562216d6de4c78"

Retrieve Data

Let's get the number of users:

> var numberOfUsers = salaryInfo.getUsersCount();
undefined
> numberOfUsers
2

Let's get the information for the first user:

> salaryInfo.getUser(0)
[123, "User 123", "123 drive way, the uncentralised kingdom", 100]

And the second user:

> var user2 = salaryInfo.getUser(1);
undefined

> user2
[234, "User 234", "234 drive way, the uncentralised kingdom", 200]
3
  • Wow! This is incredibly helpful. Thank you so much for your in-depth answer. I really appreciate it!
    – Amer Ameen
    May 5, 2016 at 20:57
  • 2
    I get a BigNumber not a Base 16 number... how can I return string attributes with your kind of solution?? (I'm using web3 0.16) Jul 5, 2016 at 13:35
  • 1
    @BokkyPooBah same issue. Getting same big nunber issue. Any help on this? Jul 29, 2017 at 13:55
11

Starting from solidity 0.8.0 you can return a struct directly. Here is a simple example contract:

pragma solidity ^0.8.0;
    
contract Example {
    struct Store {
        string id;         
        uint time;         
    }
 
    mapping (address => Store) public purchases;

    function set(string memory _id, uint _time) public returns(bool) {
        purchases[msg.sender].id = _id;
        purchases[msg.sender].time = _time;
        return true;
    }

    function get() public view returns(Store memory) {
        return purchases[msg.sender];
    }
}

If you must use any solidity version <0.8.0, then you need to add this for the code above to work:

pragma experimental ABIEncoderV2;      
6

Here's an example from my work:

function getChannel(bytes32 channelId) returns(
    address addr0,
    address addr1,
    uint8 phase,
    uint challengePeriod,
    uint closingBlock,
    bytes state,
    uint sequenceNumber,
    bytes evidence0,
    bytes evidence1
) {
    addr0 = channels[channelId].addr0;
    addr1 = channels[channelId].addr1;
    phase = channels[channelId].phase;
    challengePeriod = channels[channelId].challengePeriod;
    closingBlock = channels[channelId].closingBlock;
    state = channels[channelId].state;
    sequenceNumber = channels[channelId].sequenceNumber;
    evidence0 = channels[channelId].evidence0;
    evidence1 = channels[channelId].evidence1;
}

This returns

[ 
    '0xe7d3b0123a4f0294e06d212876ade6277b47f473',
    '0x3e4280efa3dd3014ca26022ac7dfe8e6c3070c67',
    { [String: '0'] s: 1, e: 0, c: [ 0 ] },
    { [String: '1'] s: 1, e: 0, c: [ 1 ] },
    { [String: '0'] s: 1, e: 0, c: [ 0 ] },
    '0x2222',
    { [String: '1'] s: 1, e: 0, c: [ 1 ] },
    '0x',
    '0x' 
]

Not the most readable, but that's life!

1
  • 4
    You never wrote a return statement? Does it return automatically since its the same variable names?
    – vonGohren
    May 16, 2017 at 20:53
5

You can only return a struct from a function for internal calls in a contract.

One possible approach would be to return the content of the struct in an array.

1
  • 1
    But that might work only if all the members of the struct are of the same type, no ? From the salary example above, one can not return "string" username and "uint" salary in the same array. Sep 3, 2018 at 13:33
1

Using pragma experimental ABIEncoderV2 you can return a struct and read it via web3js.

Here is a simple example contract:

pragma solidity ^0.5.12;
pragma experimental ABIEncoderV2;
    
contract Example {

    struct Store {
        string id;         
        uint time;         
    }
 
    mapping (address => Store) public purchases;

    function set(string memory _id, uint _time) public returns(bool) {
        purchases[msg.sender].id = _id;
        purchases[msg.sender].time = _time;
        return true;
    }

    function get() public view returns(Store memory) {
        return purchases[msg.sender];
    }
}

JavaScript code to read the struct:

Example.methods.get().call({from: userAddress})
.then(function(result){
  console.log(result[0]);
  console.log(result[1]);
});
2
  • This is a good example that I am following. Wondering if the mapping here should be mapping (address => Store) public purchases; rather than string=> Store given later purchases[msg.sender] is being used?
    – Zakoff
    Aug 16, 2020 at 12:08
  • 1
    Yes it should be address => store you are right Aug 18, 2020 at 13:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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