Skip to main content
Smart contracts need to be able to communicate complex data in a simple way, while also reading and storing such data into their states efficiently. To achieve such simple communication and efficient storage, smart contracts morph the data from their complex representation into simpler ones. This process of translating complex objects into simpler single-value representations is called serialization. NEAR uses two serialization formats: JSON and Borsh.
  1. JSON is used to serialize the contract’s input/output during a function call
  2. 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 the method, 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 object B{true, 0} into its JSON serialized value: "{\"success\":true, \"other_number\":0}" and return this string.
JSON Limitations Since JSON is limited to 52 bytes numbers, you cannot use u64/u128 as input or output. JSON simply cannot serialize them. Instead, you must use Strings.The NEAR SDK RS currently implements the near_sdk::json_types::{U64, I64, U128, I128} that you can use for input / output of data.

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 state
The first key-value is:
Since the Contract has a structure string, Vector<u8> the value is interpreted as:
Then, the second key-value shows the entries of the Vector denoted by the "prefix" string:

Modifying the State

If we modify the stored string and add a new number, the state changes accordingly:
We can see that the 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 the STATE 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:
  1. Rollback to the previous contract code
  2. Implement a method to migrate the contract’s state

Overriding interface serialization

JSON is the default format for contract parameters and return values. You can opt into Borsh when smaller payloads and lower serialization costs are more important than a human-readable interface. The parameter and result serializers can be selected separately, but all parameters of a method must use the same format:
When calling a Borsh-serialized method through the CLI, pass its serialized bytes as Base64 arguments:
Use the same serialization format on both sides of a cross-contract call. JSON remains the better default when the interface is also used by frontends, scripts, or other languages.

JSON wrapper types

The SDK provides wrapper types in near_sdk::json_types for Rust values whose default JSON representation is inconvenient.

Large integers

JSON consumers cannot always represent u64 and u128 values without losing precision. U64 and U128 serialize those values as strings while retaining their native integer representation inside the contract.

Byte arrays

A Vec<u8> normally serializes to an array of JSON integers. Use Base64VecU8 to expose those bytes as a compact Base64 string: