I'm creating a DAPP and i'm currently drafting the structure of my code. I'm not really experienced with Solidity so i'm trying to have a clear representation of my code before starting to code it. I need to have the following structure :

├── Client 1
│   ├── Year 2018
│   │   ├── Case 1
│   │   └── Case 2
│   └── Year 2017
│       └── Case 1
├── Client 2
│   └── Year 2018
│          ├──Case1
│          └──Case2

So the Client would be a contract, the Year would be simple struct and Cases would be complex structs. So first of all , Is it correct to use this structure ? I saw a post about struct in struct but it seemed to generate error so I want to be sure that it won't cause me problems later before using it.

Thank you and have a good day/night.

  • Would this be strictly append-only or would you need to support a move op where the Case changes Client for some reason? – Rob Hitchens Oct 2 '18 at 23:14

On first look, the separation into years looks a bit more like a way to present it than a way to store it. You might end up with another struct for year.

If you flattened it out a little (with the year stored with the rest of the Case attributes):

├── Client 1
│   │── Case 1
│   │── Case 2
├── Client 2
│   ├── Case 1

It still won't be especially easy to retrieve things by Case number unless you know the Client. I like to cover all bases that might be needed in the future and allow for enumeration at both levels. Instead of structs inside structs (perfectly valid), you might treat them as two related tables.

├── Client 1
│   │── Cases[]
│   │── Cases[]
├── Client 2
│   ├── Cases[]

├── Case 1
├── Case 2

Make Client a property of each Case so it's easy to explore:

struct Client {
  // stuff
  bool isClient;
  bytes32 cases[];

struct Case {
  // stuff
  uint year;
  bool isCase;
  bytes32 client;

Support random access:

mapping(bytes32 => Client) public clients;
mapping(bytes32 => Case) public cases;

function getClient(bytes32 clientId) public view ...
function getCase(bytes32 caseId) public view ... 

Support sequential access:

bytes32[] public clientIdList;
bytes32[] public caseIdList;

function getClientCount() public view returns(uint count) ...
function getCaseCount() public view returns(uint count) ...
function getClientCaseCount(bytes32 clientId) public view returns(uint count) ...
function getClientCaseAtRow(bytes32 clientId, uint row) public view returns(...

There are quite a few things working together there:

function insertClientCase(bytes32 clientId, bytes32 caseId, string attributes ... {
  require(client[clientId].isClient); // client must exist
  require(!cases[caseId].isCase); // no duplicates
  clients[clientId].cases.push(caseId); // append to client's case list
  cases[caseId].isCase = true; // this exists
  cases[caseId].atttributes = attributes; // placeholder for case data
  caseIdList.push(caseId); // index of mapping keys that are populated
  emit LogNewClientCase(msg.sender, clientId, caseId, attributes, ...); // audit
  return true;

You can easily add update methods that just write over the records, and even add a soft delete that just flips isClient and isCase to false while keeping accumulated history, e.g. a deleted client still has related cases.

It's a lot trickier if you need to support a move of a case from one client to another. That would be implemented as a delete from one client's list and an append on the destination list, which involves row pointers and moving the last item on the list into the row to delete.

Hope it helps.


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.