Skip to main content

Command Palette

Search for a command to run...

Serialization and Deserialization in Node.js

Updated
β€’13 min read
Serialization and Deserialization in Node.js
S
Full Stack Developer | AI Enthusiast | Builder πŸ› οΈ Currently focused on building high-performance products like ShortIQ (AI content engine) and Spectr (real-time analytics). I write about Next.js, backend architecture, and the journey of scaling modern web apps. Tech Stack: JavaScript/TypeScript, Next.js, Node.js, PostgreSQL, and AI integration (Gemini/Sarvam). Check out my work: subhashjha.me

Serialization and deserialization are two fundamental processes that play a pivotal role in modern software development, especially within the context of Node.js.

They are the unsung heroes behind the scenes β€” responsible for converting complex data structures into formats that can be easily stored, transmitted, and reconstructed when needed. In Node.js applications where data is at the core of functionality, a solid understanding of these processes is crucial.

This article is your complete guide to serialization and deserialization in Node.js. We will explore various serialization formats, implement practical examples, uncover best practices, and examine real-world use cases. By the end, you will have the knowledge and skills necessary to effectively manage data in Node.js.


What Is Serialization?

Serialization is the process of converting a live, in-memory data structure or object into a format that can be stored (on disk, in a database, or a cache) or transmitted (over a network or between processes).

Inside a running Node.js process, your objects live in the V8 heap β€” a region of RAM where they are stored as a web of pointers referencing each other. These pointers are memory-address-specific, meaning they are only meaningful within your current running process. You cannot write raw heap pointers to a file or send them over HTTP β€” the receiving machine has different memory addresses entirely.

Serialization solves this by flattening the in-memory object graph into a linear, self-contained sequence of bytes or characters that any system can read.

[In-Memory Object (V8 Heap)]
User Object ──> Address Object ──> "21 Some Street"
    β”‚
    └──> Hobbies Array ──> ["eating", "sleeping", "coding"]
    β”‚
    Serialization (JSON.stringify)
    β–Ό
[Serialized Output β€” Flat, Transferable String]
'{"name":"Subhash Jha","address":"21 Some Street","hobbies":["eating","sleeping","coding"]}'

When Do You Need Serialization?

  • Sending data from a Node.js API to a frontend browser

  • Persisting JavaScript objects to a database (e.g., MongoDB, Redis)

  • Writing application state to a file on disk

  • Passing messages between microservices or worker threads


What Is Deserialization?

Deserialization is the exact reverse of serialization. It takes the flat sequence of bytes or characters β€” from a file, a database, or a network response β€” and reconstructs a live, fully usable in-memory object.

Without deserialization, all the data you receive from external sources is just raw text or binary that your application cannot interact with. Deserialization gives it structure and meaning.

[Serialized Input β€” Raw String from HTTP Request or File]
'{"name":"Subhash Jha","city":"Delhi","hobbies":["eating","sleeping","coding"]}'
    β”‚
    Deserialization (JSON.parse)
    β–Ό
[Reconstructed In-Memory Object]
parsedData.name    β†’ "Subhash Jha"
parsedData.city    β†’ "Delhi"
parsedData.hobbies β†’ ["eating", "sleeping", "coding"]

When Do You Need Deserialization?

  • Reading a JSON body from an incoming HTTP POST request

  • Loading a configuration file from disk at startup

  • Receiving a message from a message queue (e.g., RabbitMQ, Kafka)

  • Reading database records back into your application


Serialization Formats

Node.js supports several serialization formats, each suited to specific use cases. Here is a quick overview before we dive into implementation:

Format Type Human-Readable Performance Best Used For
JSON Text βœ… Yes Medium Web APIs, configuration, NoSQL databases
XML Text βœ… Yes Slow Enterprise systems, SOAP APIs
YAML Text βœ… Yes Medium Config files (Docker, CI/CD pipelines)
Binary (Buffer) Binary ❌ No Very Fast Images, audio, video, file I/O
Protobuf Binary ❌ No Extremely Fast Microservices, gRPC, high-performance systems

Implementing Serialization in Node.js

Implementing serialization means converting your native JavaScript objects or data structures into a format suitable for storage or transmission.

1. JSON Serialization

JavaScript Object Notation (JSON) is the most widely used serialization format in Node.js. It is lightweight, human-readable, and natively supported in every modern JavaScript runtime.

JSON represents data as key-value pairs. Objects are wrapped in curly braces {} and arrays in square brackets [].

// JavaScript Object in memory
const data = {
  name: "Subhash Jha",
  occupation: "Software Engineer",
  city: "Delhi",
  address: "21 Some Address",
  hobbies: ["eating", "sleeping", "coding"]
};

// Serialize: Convert the JavaScript object into a JSON string
const jsonString = JSON.stringify(data);
console.log(jsonString);
// Output:
// '{"name":"Subhash Jha","occupation":"Software Engineer","city":"Delhi","address":"21 Some Address","hobbies":["eating","sleeping","coding"]}'

console.log(typeof jsonString); // "string"

The JSON.stringify() method traverses the object's properties and encodes each key-value pair into a flat JSON string. This string is now safe to write to a file, store in Redis, or send in an HTTP response body.

JSON Serialization with Formatting

You can pass a third argument to JSON.stringify() to produce a human-readable, indented output:

const prettyJson = JSON.stringify(data, null, 2);
console.log(prettyJson);
/* Output:
{
  "name": "Subhash Jha",
  "occupation": "Software Engineer",
  "city": "Delhi",
  "address": "21 Some Address",
  "hobbies": [
    "eating",
    "sleeping",
    "coding"
  ]
}
*/

JSON Serialization Limitations

JSON does not support every JavaScript data type. Be aware of these edge cases:

const edgeCases = {
  date:  new Date(),       // Date β†’ becomes an ISO string "2023-12-04T..."
  map:   new Map([["a", 1]]), // Map β†’ becomes {} (empty object, data is LOST)
  set:   new Set([1, 2, 3]),  // Set β†’ becomes {} (empty object, data is LOST)
  fn:    () => "hello",    // Functions β†’ omitted entirely from output
  undef: undefined,        // undefined values β†’ omitted entirely from output
};

console.log(JSON.stringify(edgeCases));
// Output: '{"date":"2023-12-04T00:00:00.000Z"}'
// Map, Set, function, and undefined are all silently dropped!

Important: Always be aware of what data you are serializing. Silent data loss from Map, Set, and undefined is a common source of bugs.


2. XML Serialization

Extensible Markup Language (XML) is a text-based format that structures data using hierarchical tags. While more verbose than JSON, it remains a standard in enterprise environments and legacy SOAP-based APIs.

To work with XML in Node.js, install the xml2js library:

npm install xml2js

You can build an XML string manually or use a builder utility. Here is how to serialize a JavaScript object into XML using xml2js:

const xml2js = require('xml2js');

const data = {
  person: {
    name: "Subhash Jha",
    location: "PO Box 34635",
    lightbill: 200
  }
};

// Serialize: Convert JavaScript object to XML string
const builder = new xml2js.Builder();
const xmlString = builder.buildObject(data);
console.log(xmlString);
/* Output:
<person>
  <name>Subhash Jha</name>
  <location>PO Box 34635</location>
  <lightbill>200</lightbill>
</person>
*/

3. Binary Serialization

Binary serialization converts data into raw bytes rather than human-readable text. This results in significantly smaller payload sizes and faster encoding/decoding β€” making it ideal for performance-critical applications and handling multimedia files.

In Node.js, the native Buffer class represents raw binary data. The built-in fs module provides methods to read files as binary buffers:

const fs = require("fs");

// Serialize: Read the image file into a raw binary Buffer
const binaryData = fs.readFileSync("downloaded.jpg");

console.log(binaryData);         // Raw binary content of the image file
console.log(typeof binaryData);  // "object" (a Buffer instance)
console.log(binaryData.length);  // Size of the file in bytes

The fs.readFileSync() method reads the file from disk and returns a Buffer β€” a fixed-length, raw binary data store. This buffer can now be stored in a database blob column, uploaded to a cloud storage bucket, or sent over the network.


Deserialization in Node.js

Deserialization is the process of converting serialized data β€” whether a JSON string, XML markup, or a binary buffer β€” back into its original, usable form inside your Node.js application.

1. JSON Deserialization

To deserialize a JSON string back into a JavaScript object, use the JSON.parse() method. This is the most common deserialization operation in Node.js.

// A JSON string received from an HTTP request body, file, or database
const jsonString = '{"name":"Subhash Jha","occupation":"Software Engineer","city":"Delhi","address":"21 Some Address","hobbies":["eating","sleeping","coding"]}';

// Deserialize: Convert the JSON string back into a JavaScript object
const parsedData = JSON.parse(jsonString);

// The object is now fully usable
console.log(parsedData.name);       // Outputs: Subhash Jha
console.log(parsedData.city);       // Outputs: Delhi
console.log(parsedData.hobbies[0]); // Outputs: eating
console.log(typeof parsedData);     // "object"

Always Handle Errors During Deserialization

Deserialization is a boundary operation β€” the incoming data may be malformed, truncated, or intentionally crafted by an attacker. Always wrap JSON.parse() in a try/catch block:

function safeDeserialize(rawString) {
  try {
    const parsed = JSON.parse(rawString);
    return { success: true, data: parsed };
  } catch (error) {
    console.error("Deserialization failed:", error.message);
    return { success: false, data: null };
  }
}

// Malformed JSON string
const result = safeDeserialize('{\"name\": \"Ali\", broken json}');
console.log(result);
// Output: { success: false, data: null }

The Date Deserialization Gotcha

JSON.parse() does not automatically restore Date objects. They remain plain strings after deserialization, which is one of the most common bugs in Node.js applications:

const event = { scheduledAt: new Date("2023-12-04") };
const jsonString = JSON.stringify(event);
const restored = JSON.parse(jsonString);

console.log(restored.scheduledAt);         // "2023-12-04T00:00:00.000Z" (a string!)
console.log(typeof restored.scheduledAt);  // "string" β€” NOT a Date object

// Fix: manually convert back to a Date
const fixedDate = new Date(restored.scheduledAt);
console.log(fixedDate instanceof Date); // true

2. XML Deserialization

To deserialize an XML string back into a JavaScript object, use xml2js.parseString(). This is an asynchronous operation that accepts a callback:

const xml2js = require('xml2js');

// XML string received from an external API or file
const xmlData = `
<person>
  <name>Subhash Jha</name>
  <location>PO Box 34635</location>
  <lightbill>200</lightbill>
</person>`;

// Deserialize: Parse the XML string into a JavaScript object
xml2js.parseString(xmlData, (err, result) => {
  if (err) {
    console.error("XML deserialization failed:", err.message);
    return;
  }
  // xml2js wraps every value in an array by default
  console.log(result.person.name[0]);      // Outputs: Subhash Jha
  console.log(result.person.location[0]);  // Outputs: PO Box 34635
  console.log(result.person.lightbill[0]); // Outputs: 200
});

Note: By default, xml2js wraps all values inside arrays (e.g., result.person.name[0] instead of result.person.name). You can disable this with the explicitArray: false option:

xml2js.parseString(xmlData, { explicitArray: false }, (err, result) => {
  console.log(result.person.name); // Outputs: Subhash Jha (no array!)
});

3. Binary Data Deserialization

When dealing with binary data, you use Node.js Buffer objects to reconstruct the original data from its binary representation. In the case of image files, deserialization simply means writing the binary buffer back to a file:

const fs = require("fs");

// Read the binary data from disk (this is the "serialized" form)
const binaryData = fs.readFileSync("downloaded.jpg");

// Deserialize: Write the binary buffer back to a new image file
fs.writeFileSync("output.jpg", binaryData);
console.log("Binary data deserialized and saved as output.jpg");

For more complex scenarios β€” such as reading custom binary protocols β€” you access specific byte offsets in the buffer to extract individual fields:

// Suppose a binary packet has this layout:
// Bytes 0-3: User ID (32-bit unsigned integer)
// Bytes 4-7: Score  (32-bit unsigned integer)
const packet = Buffer.from([0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xE8]);

// Deserialize: Read fields from specific byte offsets
const userId = packet.readUInt32BE(0); // Read 4 bytes at offset 0
const score  = packet.readUInt32BE(4); // Read 4 bytes at offset 4

console.log(`User ID: ${userId}`); // Outputs: User ID: 101
console.log(`Score: ${score}`);    // Outputs: Score: 1000

Real-World Use Cases

Serialization and deserialization are fundamental to virtually every area of a Node.js application.

Web APIs and microservices: Serialization enables sending structured data between clients and servers. JSON is the standard format for REST APIs. When a Node.js Express server receives a POST request, the body parser deserializes the raw JSON string from the request body into a usable JavaScript object. When sending a response, the server serializes its output back to JSON.

Database interactions: When persisting data to NoSQL databases like MongoDB, your JavaScript objects are serialized to BSON (Binary JSON) before storage. When you query records back, the MongoDB driver deserializes the BSON response back into JavaScript objects. Be vigilant about data types such as Date, ObjectId, and Decimal128 β€” these require careful handling during deserialization.

File uploads and downloads: Binary serialization is essential for handling multimedia. When a user uploads an image, the server reads it as a Buffer, optionally processes it, and stores the binary data. When serving the file, the binary buffer is streamed back to the client. For large files, always use streams (fs.createReadStream) to avoid loading the entire file into heap memory at once.


Best Practices

Serialization

  • Choose the right format. Use JSON for web APIs. Use binary protocols (Protobuf, MessagePack) for high-throughput microservice communication where bandwidth and speed matter.

  • Minimize payload size. Only serialize the fields your receiver actually needs. Avoid sending the entire database row when only two fields are needed.

  • Handle unsupported types explicitly. Use a custom replacer function with JSON.stringify() to convert Date, Map, and Set types into serializable representations before serializing.

Deserialization

  • Always wrap deserialization in try/catch. Any incoming data β€” from APIs, files, or databases β€” can be malformed. Unhandled parse errors crash your Node.js server.

  • Validate the shape of deserialized data. Use schema validation libraries like Zod or Ajv to confirm the deserialized object has the exact structure your code expects before using it:

const { z } = require('zod');

const UserSchema = z.object({
  name:       z.string(),
  occupation: z.string(),
  city:       z.string(),
});

const rawInput = JSON.parse(incomingJsonString);
// Throws a detailed error if shape doesn't match
const validUser = UserSchema.parse(rawInput);
console.log(validUser.name); // Safe to use
  • Never deserialize data from untrusted sources using executable formats. Formats like Python's pickle or PHP's unserialize can execute code during deserialization, leading to Remote Code Execution (RCE). In Node.js, avoid libraries that evaluate JavaScript from serialized strings.

  • Stream large payloads. For large JSON files or binary files, use streams to process the data incrementally instead of loading it all into memory at once:

const fs = require("fs");
const { Transform } = require("stream");

// Process a large file chunk-by-chunk β€” zero memory overload
fs.createReadStream("large_data.json")
  .pipe(new Transform({
    transform(chunk, encoding, callback) {
      // Process each chunk here
      this.push(chunk);
      callback();
    }
  }))
  .pipe(fs.createWriteStream("output.json"));

Conclusion

Serialization and deserialization are the invisible bridges that make modern Node.js applications work. Every HTTP request body you parse, every database record you read, and every file you process depends on these two operations.

Concept Direction Key Method
Serialization Object β†’ String/Bytes JSON.stringify(), builder.buildObject(), fs.readFileSync()
Deserialization String/Bytes β†’ Object JSON.parse(), xml2js.parseString(), fs.writeFileSync()

To go deeper, explore advanced topics like handling circular references with identity tracking, building custom replacer/reviver functions for JSON.stringify/JSON.parse, and adopting binary protocols like Protocol Buffers for high-performance microservice architectures.

More from this blog

B

Blogs

2 posts

A developer blog by Subhash Jha β€” full-stack developer passionate about building open-source tools, SaaS products, and modern web experiences. Writing about Next.js, Node.js, system design, and the journey of growing as a developer.