- JSON is used to serialize the contract’s input/output during a function call
- Borsh is used to serialize the contract’s state.
Overview of Serialization Formats
Let’s give a quick overview of both serialization formats, including their pros and cons, as well as an example of what their serializations look like.JSON: Objects to Strings
Features
- Self-describing format
- Easy interoperability with JavaScript
- Multiple implementations readily available
- But… it is not efficient both in computational times and resulting size
Example
Borsh: Objects to Bytes
Features
- Compact, binary format built to be efficiently (de)serialized
- Strict and canonical binary representation
- Less overhead: it does not need to store attributes names
- But… it is necessary to know the schema to (de)serialize the data
Example
Serializing Input & Output
NEAR contracts can implement methods that both take and return complex objects. In order to handle this data in a simple way, JSON serialization is used. Using JSON makes it easier for everyone to talk with the contracts, since most languages readily implement a JSON (de)serializer.Example
Let’s look at this example, written only for educational purposes:Receiving Data
When a user calls themethod, the contract receives the arguments encoded as a JSON string
(e.g. "{\"a_number\":0, \"b_number\":\"100\"}"), and proceed to (de)serialize them into
the correct object (A{0, 100}) .
Returning Data
When returning the result, the contract will automatically encode the objectB{true, 0}
into its JSON serialized value: "{\"success\":true, \"other_number\":0}" and return this
string.
Borsh: State Serialization
Under the hood smart contracts store data using simple key/value pairs. This means that the contract needs to translate complex states into simple key-value pairs. For this, NEAR contracts use borsh which is optimized for (de)serializing complex objects into smaller streams of bytes.Example
Let’s look at this example, written only for educational purposes:Empty State On Deploy
If we deploy the contract into a new account and immediately ask for the state we will see it is empty:Initializing the State
If we initialize the state we can see how Borsh is used to serialize the stateResult
Result
Contract has a structure string, Vector<u8> the value is interpreted as:
Vector denoted by the "prefix" string:
Modifying the State
If we modify the stored string and add a new number, the state changes accordingly:Result
Result
STATE key changes to reflect the storage of the new string (bye), and that
the vector now has 2 elements.
At the same time, a new key-value was added adding the new vector entry: the 1u8 we just added.
Deserialization Error
When somebody invokes a smart contract method, the first step for the contract is to deserialize its own state. In the example used above, the contract will start by reading theSTATE key and
try to deserialize its value into an object Contract{string: String, vector: Vector<u8>}.
If you deploy a contract into the account with a different Contract structure, then the
contract will fail to deserialize the STATE key and panic Cannot deserialize the contract state.
To solve this, you can either:
- Rollback to the previous contract code
- Implement a method to migrate the contract’s state