Free & open source — no account required

v1.2.5-PRICING-19
Web & Frontend • Engineering Documentation

Solidity Mastery: Automating Smart Contract Data Models

This technical guide provides an in-depth analysis of the json to solidity engine, best practices for implementation, and data security standards.

JSON to Solidity Struct: Generating Smart Contract Data Structures from JSON

Solidity structs are the primary way to group related data in Ethereum smart contracts. When you design a dApp that stores or processes structured data on-chain, generating the Solidity struct skeleton from a JSON payload saves the type-mapping work and ensures your on-chain data model matches your off-chain API shape. The generator produces a contract with a typed struct and a storage mapping — the starting point for any CRUD contract.

Live Example: NFT Metadata to Solidity

// Input JSON (NFT metadata shape)
{
  "token_id": 1,
  "name": "CryptoPunk #001",
  "description": "A rare pixel art character",
  "owner": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
  "price_wei": 1000000000000000000,
  "is_listed": true,
  "attributes": ["rare", "hat", "glasses"]
}

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

contract RootStore {
    struct Root {
        uint256 id;
        uint256 token_id;
        string name;
        string description;
        string owner;
        uint256 price_wei;
        bool is_listed;
        string[] attributes;
    }
}

Refined Contract: Storage, Mappings, and Events

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

import "@openzeppelin/contracts/access/Ownable.sol";

contract NFTRegistry is Ownable {
    struct NFT {
        uint256 tokenId;
        string name;
        string description;
        address owner;         // address type, not string
        uint256 priceWei;      // uint256 for ETH amounts in wei
        bool isListed;
        string[] attributes;
    }

    // Primary storage: tokenId → NFT
    mapping(uint256 => NFT) public nfts;

    // Track all token IDs
    uint256[] public tokenIds;

    // Events (indexed params are searchable in logs)
    event NFTCreated(uint256 indexed tokenId, address indexed owner);
    event NFTListed(uint256 indexed tokenId, uint256 priceWei);
    event NFTSold(uint256 indexed tokenId, address indexed from, address indexed to, uint256 price);

    constructor() Ownable(msg.sender) {}

    function createNFT(
        uint256 tokenId,
        string calldata name,
        string calldata description,
        string[] calldata attributes
    ) external {
        require(nfts[tokenId].tokenId == 0, "Token already exists");

        nfts[tokenId] = NFT({
            tokenId: tokenId,
            name: name,
            description: description,
            owner: msg.sender,
            priceWei: 0,
            isListed: false,
            attributes: attributes
        });

        tokenIds.push(tokenId);
        emit NFTCreated(tokenId, msg.sender);
    }

    function listForSale(uint256 tokenId, uint256 priceWei) external {
        require(nfts[tokenId].owner == msg.sender, "Not the owner");
        require(priceWei > 0, "Price must be greater than zero");

        nfts[tokenId].priceWei = priceWei;
        nfts[tokenId].isListed = true;
        emit NFTListed(tokenId, priceWei);
    }

    function buy(uint256 tokenId) external payable {
        NFT storage nft = nfts[tokenId];
        require(nft.isListed, "Not for sale");
        require(msg.value >= nft.priceWei, "Insufficient payment");

        address previousOwner = nft.owner;
        nft.owner = msg.sender;
        nft.isListed = false;
        nft.priceWei = 0;

        payable(previousOwner).transfer(msg.value);
        emit NFTSold(tokenId, previousOwner, msg.sender, msg.value);
    }
}

Solidity Type Mapping from JSON

// JSON type → Solidity type decisions

// Numbers: uint256 is the default, but choose by range
uint8   salary_level;    // 0-255
uint16  year;            // 0-65535
uint32  unix_timestamp;  // fits until year 2106
uint256 token_amount;    // ETH amounts, large IDs, unlimited counts
int256  price_delta;     // signed: can be negative

// Strings: string vs bytes32
string  name;            // arbitrary length — stored in dynamic storage (expensive)
bytes32 symbol;          // fixed 32 bytes — cheaper for short known-length values

// Addresses (not strings!)
address owner;           // 20-byte Ethereum address
address payable seller;  // address that can receive ETH

// Arrays
string[]   tags;         // dynamic array
uint256[3] coordinates;  // fixed-size array (cheaper)

// Money: always work in wei, never in ETH floats
// 1 ETH = 1e18 wei — use uint256, never float
uint256 priceWei;

Gas Optimization: Storage Layout Matters

// Bad: each field uses a full 32-byte storage slot = expensive
struct Inefficient {
    uint256 a;  // slot 0
    uint8   b;  // slot 1 (wastes 31 bytes)
    uint256 c;  // slot 2
    uint8   d;  // slot 3 (wastes 31 bytes)
}

// Good: pack small types together into one slot
struct Efficient {
    uint256 a;  // slot 0
    uint256 c;  // slot 1
    uint8   b;  // slot 2 (shares slot with d, e, f...)
    uint8   d;
    bool    e;
    // remaining 29 bytes of slot 2 available for more small types
}

Frequently Asked Questions

Why does the generator use string for addresses instead of address? JSON represents Ethereum addresses as strings (e.g., "0xAb58..."). The generator can't know a string field is an address. Change any string field that holds an Ethereum address to the address type — it's 20 bytes, cheaper to store, and enables payable transfers.

What's the difference between memory, storage, and calldata? storage persists on-chain between transactions (expensive). memory is a temporary copy for computation within a function call (cheap). calldata is read-only input data from the transaction — use it for function parameters you don't modify (cheapest). For string and array parameters: use calldata in external functions, memory in public/internal functions.

Can I store arrays of structs? Yes: NFT[] public allNfts or mapping(address => NFT[]) public nftsByOwner. Arrays of structs in storage are expensive to iterate — design for lookup-by-key patterns (mappings) rather than iteration where possible.

Is my JSON sent to a server? No. TypeMorph runs entirely in your browser — none of your data leaves your machine.

Developer FAQ

Is the processing local-only?

Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.

Can I use this for enterprise projects?

Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.