In this article, we’ll learn about how systems communicate seamlessly with each other through a process called serialization and deserialization and how we can apply that in TypeScript. The process is seamless because of how the transferable data is handled.
As a user, when you access any application, you do so to retrieve information. This information is made up of data that must be stored somewhere; this is where serialization comes in.
By the end of this article, you should be able to understand how data transfers from one location to another, what format it transfers in, and the possible options for storing data streams. We’ll also cover technical issues that can arise when working with serialization in TypeScript.
So what is serialization and deserialization? It is simply the process that allows a seamless transfer of data.
Caching is a good real-life example of serialization and deserialization where data is stored in memory for faster retrieval.
Cached data is transmitted through a network in a serialized format to be stored in memory, and then, every time the data is requested, it is deserialized back to an object.
In this post, we will also look at different scenarios where serialization and deserialization processes are required. Generally, the serialization and deserialization process happens under the hood without you knowing.
Serialization is mainly used to store an object or when you want to transfer an object from one active script to another. The transfer could be from server to server, server to a client, or client to a server.
Different systems must communicate with each other while accepting specific data formats. In this case, you control serialization and deserialization. Serialization eliminates the need for a gateway to receive these different data formats.
Once serialization finishes, byte streams can be easily shared across multiple platforms. Deserialization is then done to reconstruct the data back to its original form.
The following are a few examples of different use cases of when serialization and deserialization of data are needed.
HTTP uses serialization when transmitting data through a web browser. For example, a RESTFul service has an HTTP request with a message body whose contents are in a serialized form of an object.
Session data is transmitted in a serialized format through a communication protocol across multiple virtual machines.
An example of this is session-based authentication. When saving a user’s information in a serialized format in the server-side, a client can send a request to the server to retrieve the information, and the server processes the request then sends back a deserialized response.
Redis is a type of database that reduces an application’s load time using an in-memory dataset. Using serialization, the code converts to a persistable data structure.
In an application that involves video streaming, the video is broken down into packets, which are a collection of bytes transmitted serially to the client.
Beyond general use cases, serialization provides numerous benefits, including helping with communication issues, deep copy, and caching.
For instance, by allowing different computer designs to share objects simultaneously, we can store the state of any object directly, solving communication issues. An example of this is transferring data through a network such as web and mobile applications.
In the case of deep copy, the cloning process is made simple and accurate by serializing an object into a byte array and deserializing it.
And finally, for caching, the required time to build an object is more than the time taken in deserializing an object, which means serializing saves time by cashing the giant object.
In TypeScript, since this is a simple scenario, you can call the JavaScript function JSON.stringify
to serialize an object to a JSON string and JSON.parse
deserializes the JSON string to an object. Below is an example of a serialized and deserialized Person
object using JSON.stringify
and JSON.parse
respectively.
JSON is a widely used data format, as it represents data as key-value pairs and it does not include class metadata (class definitions of objects like properties, methods, and events) since it’s structured data:
class Person{ name: string; constructor() { this.name = 'Serializer'; } } const human = new Person(); Output1: Person { name: 'Serialer' } const serialized_data = JSON.stringify(human) Output1:// {"name":"Serializer"} const deserialized_data = JSON.parse(serialized_data) Output:// { name: 'Serializer }
Check out the official documentation to learn more about different examples of data types using JSON.stringify
and JSON.parse.
Looking at the Person
class example above, if you want to deserialize a JSON object back to a class instance, using JSON.parse
will not be enough.
The JSON.parse
method returns a plain object and not a class object. Luckily some libraries like Class-transformer
can handle both serialization and deserialization of TypeScript classes without losing the type information.
It allows you to transform plain objects to class objects using decorators. Check out the official documentation to learn more about how you can easily serialize and deserialize a class using the methods it provides.
The Output1
string above can now either be transmitted to other machines or stored. To securely store serialized data, you can encrypt the data. In TypeScript, you can use a library called crypto-js
to encrypt the serialized objects.
The requirement of using this tool is to have Node.js and any node package manager installed. In this example, we will use nmp.
To install crypto-js,
run this command:
>npm install crypto-js Usage: //Import it in your TS file const CryptoJS require("crypto-js"); const AES = require("crypto-js/aes"); const serialized_data_encrytedData = CryptoJS.AES.encrypt( serialized_data, 'my serialized data' ).toString()
Different ways of storing data streams
We have different ways we can serialize objects and store them. The factors to consider when doing this are the need for readability, data complexity, speed, and space constraints.
These data streams can be stored in a database, memory, or file. In our example, we’ll use YAML.
When storing this data, ensure the data is secure to avoid possible tampering. One of the key elements of safe deserialization is to know what the data source is and that the data is safe.
YAML is a data serialization format that stands for “YAML ain’t markup language.”
Using YAML when creating a domain-specific language (DSL) is an added advantage because it’s designed to provide readability. Strings in a YAML file don’t need to be quoted, hence its improved readability.
Due to the nature of YAML’s features that handle complex data types, using YAML is ideal for small datasets and helps reduce performance-related issues.
A simple example of how to store data in a YAML file is using the js-yaml
module. To use this, you must install js-yaml
and initialize it and the file stream:
$ npm install js-yaml const yaml = require('js-yaml'); const fs = require('fs'); const object = { name: "Serialization", category: "db" } const serialized_object = JSON.stringify(object); const yaml_formatted_serialized_object = yaml.dump(serealized_object); console.log(yaml_formatted_serialized_object);
A database is preferred when the following instances apply to the kind of data you’re serializing or deserializing.
First, use a database when your application’s code does not require constant change. For example, a dashboard with population data will rarely need precision insights. The goal here is to report the data.
The data is only read once, hence performance is not prioritized; using a database in this instance is ideal.
Second, if there is no need to select fields in the deserialized data structures, using a database is ideal. However, the downside of this is that it’s almost impossible to know what type of queries you will need in the future.
If you’re using the LocalStorage
datastore as a database, you can implement it with the following:
const object = { name: "Serialization", category: "db" } serialized_object = JSON.stringify(object); // '{"name":"Serialization","category":"db"}' localStorage.setItem("serialized object", serealized_object) localStorage.getItem('serialized object') // '{"name":"Serialization","category":"db"}' localStorage.setItem("serialized object", serealized_object) localStorage.getItem('serialized object')
You can see that deserializing data using JSON.parse()
method returns a plain object and not a class object. Therefore, using JSON.stringify
and JSON.parse
is not enough when serializing class objects because you will lose type information.
Luckily some libraries like Class-transformer
enable you to transform plain objects into class objects.
Storing serialized data in memory is ideal when the following scenarios apply.
The first is when real-time data is required or you need a faster way to access the data. For instance, in the healthcare industry, immediate access to accurate patient information is crucial in potentially saving a life.
When you’re dealing with data that is time-sensitive, storing data streams in memory makes making split-second decisions, like working with stock exchange data, easier. How fast you get the data change is what determines whether you win or lose.
And finally, when reducing an application’s load time, the fastest way for data retrieval is storing it in memory. For example, Redis uses an in-memory dataset that makes retrieving data faster since it doesn’t use the hard disk when storing data.
Below is an example of how to store serialized data in Redis using the SET
and GET
commands, assuming you have Redis installed and running.
To read the data, use the GET
command:
127.0.0.1:6379> SET serializedData '{"name":"Serialization","category":"db"}' output: OK 127.0.0.1:6379> GET serializedData "{\"name\":\"Serialization\",\"category\":\"db\"}"
As we’ve covered in this post, serialization and deserialization are incredibly useful in numerous use cases. But, what are the pros and cons of using serialization and deserialization?
Because simple serialization in JavaScript does not require any third-party libraries, which can often be out-of-date and cause future problems, it avoids the need to mitigate security-related risks that come with using external dependencies.
Serialization and deserialization are also not dependent on any specific platforms. This means that you can serialize data on one platform and deserialize it in another, which is useful when many current technologies rely on serialization and need this flexibility.
Serialized data streams also support encryption, which leads to a smooth, safer process. The ability to encrypt your serialized data ensures that it is secure and more reliable.
Although serialization and deserialization have many benefits, there are disadvantages to consider mitigating when using serialization and deserialization.
While serialization and deserialization are useful mechanisms, the process is fragile, can lead to security problems, and adds complexity that can easily lead to security vulnerability if not used properly.
The rule of thumb is that the more defensive code you write, the more secure your application will be.
Deserialization also holds all variables, properties, and attributes assigned to an object, which makes the process dangerous or a point of vulnerability. It is important to write class-specific serializations methods that will not expose sensitive information about the data stream.
Ensure that the deserialization process does not have any side effects as well.
And, as we saw previously, serialization and deserialization involve data streams that demand an increase in an application’s memory requirements. This is as a result of all the memory allocations of objects and strings of your data streams and can easily lead to memory leaks.
So far, we learned that serialization and deserialization allow the portability of objects and an overview of how you can store serialized streams of data with examples. At the end of the day, the choice of how to serialize and deserialize your data solely depends on your application needs.
Happy learning!
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.