Serialization formats within the SDK define how data structures are translated into bytes which are needed for passing data into methods of the smart contract or storing data in state. For the case of method parameters, JSON (default) and Borsh are supported with the SDK and for storing data on-chain Borsh is used.
The qualities of JSON and Borsh are as follows:
- Self-describing format (don't need to know the underlying type)
- Less efficient size and (de)serialization
- Compact, binary format that's efficient for serialized data size
- Need to know data format or have a schema to deserialize data
- Strict and canonical binary representation
- Fast and less overhead in most cases
In general, JSON will be used for contract calls and cross-contract calls for a better DevX, where Borsh can be used to optimize using less gas by having smaller parameter serialization and less deserialization computation within the contract.
The result and parameter serialization can be opted into separately, but all parameters must be of the same format (can't serialize some parameters as borsh and others as JSON). An example of switching both the result and parameters to borsh is as follows:
result_serializer(borsh) annotation will override the default result serialization protocol from JSON to borsh and the
serializer(borsh) annotations will override the parameter serialization.
A simple demonstration of getting a Borsh-serialized, base64-encoded value from a unit test:
The following snippet shows a simple function that takes this value from a frontend or CLI. Note: this method doesn't have a return value, so the
#[result_serializer(borsh)] isn't needed.
Note that this is using this simple struct:
To call this with NEAR CLI, use a command similar to this:
To help with serializing certain types to JSON which have unexpected or inefficient default formats, there are some wrapper types in
near_sdk::json_types that can be used.
2^53 - 1, you will lose precision if deserializing the JSON integer is above this range. To counteract this, you can use the
U128 in place of the native types for these parameters or result to serialize the value as a string. By default, all integer types will serialize as an integer in JSON.
You can convert from
u64 and back using
You can also access inner values and using
And you can cast the lower-case
u variants to upper-case
U variants using
Combining it all:
Although there are these JSON wrapper types included with the SDK, any custom type can be used, as long as it implements
serde serialize and deserialize respectively. All of these types just override the JSON format and will have a consistent
borsh serialization and deserialization as the inner types.
Another example of a type you may want to override the default serialization of is
Vec<u8> which represents bytes in Rust. By default, this will serialize as an array of integers, which is not compact and very hard to use. There is a wrapper type
Base64VecU8 which serializes and deserializes to a Base-64 string for more compact JSON serialization.