Solidity Mapping – How to Get Values from Mapping in Solidity

contract-designcontract-developmentmappingstorage

Me and my friend are working on a hw assignment. We have a struct:

struct Record {
        string  id;
        uint requestTime;
        uint releaseTime;
        Direction dir;
    }

And we also have a mapping:

mapping(address => Record[]) data;

We will basically have multiple Record instances in the mapping associated with one address. Is there a way to read the Record instances from the mapping? Like a getter function or something that gets the specific address as a param and returns a Record[] array with the Record instances belonging to that address?

function getRecords(address _addr) public view returns(Record[]) {
    ...
    //returns recordsfound, which is a Record[]
    return recordsfound;
}

Or this doesn't work in Solidity and we have to make something else?

Best Answer

First thing. This bad form because there significant differences between 0.5.0x and 0.8.3. You should be giving an indication of the range that actually compiles and works, or even better, just declare the compiler you are using like:

pragma solidity 0.8.3;

Since this is using elements of the formerly "experimental ABI" there is no doubt the code does not compile with 0.7.6 or lower without modification to the code.

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.3;

contract Nested {
    
    struct Record {
        string  id;
        uint requestTime;
        uint releaseTime;
        Direction dir;
    }
    
    struct Direction {
        bool foo;
        uint bar;
    }
    
    mapping(address => Record[]) records;
    
    function insert(address key, string memory id, uint requestTime, uint releaseTime, bool foo, uint bar) public {

        Direction memory d = Direction({
            foo: foo,
            bar: bar
        });
        
        Record memory r = Record({
            id: id,
            requestTime: requestTime,
            releaseTime: releaseTime,
            dir: d
        });
        
        records[key].push(r);
    }
    
    function inspect(address key) public view returns(Record[] memory) {
        return records[key];
    }
    
}

You probably found that you can't just add public to the mapping.

You might get something like:

call to Nested.records errored: Error encoding arguments: Error: invalid BigNumber string (argument="value", value="", code=INVALID_ARGUMENT, version=bignumber/5.0.8)

I found it a little disconcerting that some iterations of my experiment appeared to compile successfully and then generated that error at runtime. IMHO, it would be better if the compiler rejected the source code unless a reliable view function can be constructed - a good reminder to beware of hidden dangers when relying on leading-edge compiler features.

A more traditional and possibly more efficient approach would be to present several fixed-length ABI functions, along these lines:

    function inspectLength(address key) public view returns(uint) {
        return records[key].length;
    }
    
    function inspectRecord(address key, uint record) public view returns(Record memory) {
        return records[key][record];
    }

That pushes the interaction concern to the clients. They can inspect individual records or enumerate them all and there is no need to fetch an entire array when most of its values have been seen before.

Aside from a little extra deployment cost owing to contract size, a case could be made for including both approaches - get all and get one.

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.0;

contract Nested {
    
    struct Record {
        string  id;
        uint requestTime;
        uint releaseTime;
        Direction dir;
    }
    
    struct Direction {
        bool foo;
        uint bar;
    }
    
    mapping(address => Record[]) records;
    
    function insert(address key, string memory id, uint requestTime, uint releaseTime, bool foo, uint bar) public {

        Direction memory d = Direction({
            foo: foo,
            bar: bar
        });
        
        Record memory r = Record({
            id: id,
            requestTime: requestTime,
            releaseTime: releaseTime,
            dir: d
        });
        
        records[key].push(r);
    }
    
    function inspect(address key) public view returns(Record[] memory) {
        return records[key];
    }
    
    function inspectLength(address key) public view returns(uint) {
        return records[key].length;
    }
    
    function inspectRecord(address key, uint record) public view returns(Record memory) {
        return records[key][record];
    }
    
}

Hope it helps.

Related Topic