Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the json to solidity engine, best practices for implementation, and data security standards.
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.
// 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;
}
}
// 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);
}
}
// 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;
// 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
}
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.
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.