There is no single data-storage format in TON. Each smart contract could have it's own way of storing/encoding it's data. Take the NFT item smart contract as an example. It stores it's data in the following format:
;;
;; Storage
;;
;; uint64 index
;; MsgAddressInt collection_address
;; MsgAddressInt owner_address
;; cell content
;;
And the contract has two internal functions to actually load and persist it's data:
(int, int, slice, slice, cell) load_data() {
slice ds = get_data().begin_parse();
var (index, collection_address) = (ds~load_uint(64), ds~load_msg_addr());
if (ds.slice_bits() > 0) {
return (-1, index, collection_address, ds~load_msg_addr(), ds~load_ref());
} else {
return (0, index, collection_address, null(), null()); ;; nft not initialized yet
}
}
() store_data(int index, slice collection_address, slice owner_address, cell content) impure {
set_data(
begin_cell()
.store_uint(index, 64)
.store_slice(collection_address)
.store_slice(owner_address)
.store_ref(content)
.end_cell()
);
}
The data is stored as "cell" that could have an arbitrary format and could reference other nested cells. Look for the Bag of Cells concept in the white paper.
However, you should not try to decode smart contract data directly, even if you know the format of the stored data. The proper way would be to use the "public API" of the contract by means of calling it's get methods. The NFT item contract has a get method for exactly this purpose:
;;
;; GET Methods
;;
(int, int, slice, slice, cell) get_nft_data() method_id {
(int init?, int index, slice collection_address, slice owner_address, cell content) = load_data();
return (init?, index, collection_address, owner_address, content);
}
It will return all the needed data for you in the decoded fashion.
And here is how you would parse the result of this get method:
// Calling the get-method
const result = await this.provider.call2(
myAddress.toString(),
'get_nft_data'
);
// Parsing the data returned by it
const isInitialized = (
(expectBN(result[0]).toNumber() === -1)
);
const index = expectBN(result[1]).toNumber();
const collectionAddress = (
parseAddressFromCell(result[2])
);
const ownerAddress = (isInitialized
? parseAddressFromCell(result[3])
: null
);
const contentCell = result[4];
// Single NFT without a collection
const contentUri = ((isInitialized && !collectionAddress) ?
parseOffchainUriCell(contentCell) :
null
);
Also, some peaces of data is stored as binary strings and require some non-trivial parsing using the TL-B schemas. Each library would give you some utilities to use. One of such tools is usually called a CellSlice
that allows you to read and decode the bits of the bit-string by hand.
Consider this example from the TonWeb:
// Encoding some data as a cell
const cell = new Cell();
cell.bits.writeUint('100500', 32);
cell.bits.writeString('Hello World');
// Reading the data from cell (bit-string)
const slice = new CellSlice(cell);
slice.loadUint(32); // 100500
slice.loadString(); // Hello World
slice.isEmpty(); // true
This should get you started. Just make sure to study all the linked concepts.