Retrieving and Filtering Struct Arrays in Solidity

Solidity manages data locations differently based on whether variables are stored in storage or memory. When working with arrays of structs, attempting to filter data often leads to specific compiler constraints regarding how dynamic arrays behave in memory versus storage.

Consider a contract designed to manage an inventory of items. The struct definition and state variable might look like this:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract InventoryManager {
    struct Item {
        string identifier;
        string category;
        uint256 quantity;
        address owner;
    }

    Item[] public itemRegistry;

Retrieving a Single Record

To find a specific item by its identifier, a simple iteration over the storage array is sufficient. Since the function returns a copy of the struct data, the return variable resides in memory.

function getItemById(string memory _id) public view returns (Item memory) {
    for (uint256 i = 0; i < itemRegistry.length; i++) {
        if (keccak256(abi.encodePacked(itemRegistry[i].identifier)) == keccak256(abi.encodePacked(_id))) {
            return itemRegistry[i];
        }
    }
    revert("Item not found");
}

The Memory Array Push Error

A common error occurs when developers attempt to create a filter function that returns multiple matching records. The instinct is to declare an empty array in memory and dynamically add elements to it.

// This code block will fail to compile
function getItemsByCategory(string memory _category) public view returns (Item[] memory) {
    Item[] memory results = new Item[](0);
    for (uint256 i = 0; i < itemRegistry.length; i++) {
        if (keccak256(abi.encodePacked(itemRegistry[i].category)) == keccak256(abi.encodePacked(_category))) {
            // TypeError: Member "push" is not available in struct InventoryManager.Item[] memory
            results.push(itemRegistry[i]);
        }
    }
    return results;
}

The compiler rejects this because results is a memory array. In Solidity, memory arrays are fixed-size once allocated. The push operation, which dynamically resizes an array, is exclusively available for storage arrays. To return a filtered list from a function, the memory array must be initialized with the exact number of elements it will hold.

Solution: Fixed-Size Memory Allocation

To correctly return a filtered array, the logic requires two passes: first to count the matches and second to populate the fixed-size memory array.

function getItemsByCategory(string memory _category) public view returns (Item[] memory) {
    // Step 1: Count matching elements
    uint256 matchCount = 0;
    for (uint256 i = 0; i < itemRegistry.length; i++) {
        if (keccak256(abi.encodePacked(itemRegistry[i].category)) == keccak256(abi.encodePacked(_category))) {
            matchCount++;
        }
    }

    // Step 2: Initialize memory array with the correct size
    Item[] memory filteredItems = new Item[](matchCount);

    // Step 3: Populate the array
    uint256 currentIndex = 0;
    for (uint256 i = 0; i < itemRegistry.length; i++) {
        if (keccak256(abi.encodePacked(itemRegistry[i].category)) == keccak256(abi.encodePacked(_category))) {
            filteredItems[currentIndex] = itemRegistry[i];
            currentIndex++;
        }
    }

    return filteredItems;
}

Correct Usage of Push for Storage

The push method should be used when modifying the state variable directly. The following function demonstrates the correct way to append a new struct instance to the storage array.

function addItem(string memory _id, string memory _category, uint256 _qty) public {
    // Create the struct in memory
    Item memory newItem = Item({
        identifier: _id,
        category: _category,
        quantity: _qty,
        owner: msg.sender
    });

    // Push the memory struct into the storage array
    itemRegistry.push(newItem);
}

Tags: solidity Ethereum Smart Contracts Data Structures

Posted on Sat, 27 Jun 2026 16:52:36 +0000 by Cannibal_Monkey