diff --git a/components/seo/UnserializerSEO.tsx b/components/seo/UnserializerSEO.tsx
new file mode 100644
index 0000000..0b7d9df
--- /dev/null
+++ b/components/seo/UnserializerSEO.tsx
@@ -0,0 +1,68 @@
+export default function UnserializerSEO() {
+ return (
+
+
+ Free Online Unserializer - Parse Serialized Data Instantly
+
+ Paste a serialized string and instantly convert it into a
+ human-readable format. Choose between print_r() for a quick overview
+ or var_dump() for type-annotated detail. Ideal for inspecting
+ WordPress options, transients, and metadata stored in the database.
+
+
+
+
+ How to Use the Unserializer
+
+
+ Step 1:
+ Paste a serialized string into the input box. You can grab these
+ from database fields, API responses, or debug logs.
+
+
+ Step 2:
+ Select an output format — print_r() for a compact view or{" "}
+ var_dump() for detailed type and length information.
+
+
+ Step 3:
+ Copy the formatted output and use it in your debugging workflow.
+
+
+
+
+
+ FAQs
+
+
+ What is a serialized string?
+
+ Serialization converts a data structure into a compact string
+ representation for storage or transfer. This tool reverses that
+ process so you can read the original data.
+
+
+ Where do I find serialized data?
+
+ Common sources include the WordPress wp_options table, user meta
+ fields, WooCommerce order meta, transient caches, and plugin
+ settings stored in the database.
+
+
+ What is the difference between print_r() and var_dump()?
+
+ print_r() shows values and structure in a concise format. var_dump()
+ adds explicit type and length annotations for every value, which is
+ more useful for type-sensitive debugging.
+
+
+ Does this tool modify the original data?
+
+ No. The tool only reads and formats the input — nothing is stored,
+ sent, or altered.
+
+
+
+
+ );
+}
diff --git a/components/utils/tools-list.ts b/components/utils/tools-list.ts
index 36657e1..d2e8027 100644
--- a/components/utils/tools-list.ts
+++ b/components/utils/tools-list.ts
@@ -23,6 +23,12 @@ export const tools = [
"Format and beautify your JSON data for better readability and debugging. Quickly visualize and organize your JSON data with ease.",
link: "/utilities/json-formatter",
},
+ {
+ title: "Unserializer",
+ description:
+ "Parse serialized strings and render readable output in print_r() or var_dump() format. Useful for debugging WordPress database values.",
+ link: "/utilities/unserializer",
+ },
{
title: "JSONL Validator",
description:
diff --git a/components/utils/unserializer.utils.test.ts b/components/utils/unserializer.utils.test.ts
new file mode 100644
index 0000000..d7ac0c1
--- /dev/null
+++ b/components/utils/unserializer.utils.test.ts
@@ -0,0 +1,148 @@
+import {
+ formatPrintR,
+ formatVarDump,
+ isValidSerialized,
+ unserialize,
+} from "./unserializer.utils";
+
+describe("unserializer.utils", () => {
+ describe("unserialize", () => {
+ test("parses primitive values", () => {
+ expect(unserialize('s:5:"hello";')).toEqual({
+ type: "string",
+ value: "hello",
+ });
+ expect(unserialize("i:42;")).toEqual({ type: "int", value: 42 });
+ expect(unserialize("d:3.14;")).toEqual({ type: "float", value: 3.14 });
+ expect(unserialize("b:1;")).toEqual({ type: "bool", value: true });
+ expect(unserialize("b:0;")).toEqual({ type: "bool", value: false });
+ expect(unserialize("N;")).toEqual({ type: "null" });
+ });
+
+ test("parses multi-byte UTF-8 strings", () => {
+ expect(unserialize('s:4:"😊";')).toEqual({
+ type: "string",
+ value: "😊",
+ });
+ });
+
+ test("parses nested arrays from sample payload", () => {
+ const input =
+ 'a:2:{i:0;s:12:"Sample array";i:1;a:2:{i:0;s:5:"Apple";i:1;s:6:"Orange";}}';
+
+ expect(unserialize(input)).toEqual({
+ type: "array",
+ entries: [
+ {
+ key: { type: "int", value: 0 },
+ value: { type: "string", value: "Sample array" },
+ },
+ {
+ key: { type: "int", value: 1 },
+ value: {
+ type: "array",
+ entries: [
+ {
+ key: { type: "int", value: 0 },
+ value: { type: "string", value: "Apple" },
+ },
+ {
+ key: { type: "int", value: 1 },
+ value: { type: "string", value: "Orange" },
+ },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ test("resolves references", () => {
+ const result = unserialize('a:2:{s:1:"x";s:3:"foo";s:1:"y";R:2;}');
+ expect(result).toEqual({
+ type: "array",
+ entries: [
+ {
+ key: { type: "string", value: "x" },
+ value: { type: "string", value: "foo" },
+ },
+ {
+ key: { type: "string", value: "y" },
+ value: { type: "string", value: "foo" },
+ },
+ ],
+ });
+ });
+
+ test("throws clear errors for invalid input", () => {
+ expect(() => unserialize("")).toThrow(
+ "Please enter a serialized string."
+ );
+ expect(() => unserialize("xyz")).toThrow();
+ expect(() => unserialize('a:1:{s:1:"x";R:99;}')).toThrow(
+ "Reference index 99 does not exist"
+ );
+ });
+ });
+
+ describe("isValidSerialized", () => {
+ test("returns true for valid payloads and false for invalid payloads", () => {
+ expect(isValidSerialized('s:5:"hello";')).toBe(true);
+ expect(isValidSerialized("i:42;")).toBe(true);
+ expect(isValidSerialized("")).toBe(false);
+ expect(isValidSerialized("not serialized")).toBe(false);
+ });
+ });
+
+ describe("formatPrintR", () => {
+ test("matches expected output for sample payload", () => {
+ const input =
+ 'a:2:{i:0;s:12:"Sample array";i:1;a:2:{i:0;s:5:"Apple";i:1;s:6:"Orange";}}';
+ const parsed = unserialize(input);
+
+ const expected = [
+ "Array",
+ "(",
+ " [0] => Sample array",
+ " [1] => Array",
+ " (",
+ " [0] => Apple",
+ " [1] => Orange",
+ " )",
+ "",
+ ")",
+ ].join("\n");
+
+ expect(formatPrintR(parsed)).toBe(expected);
+ });
+ });
+
+ describe("formatVarDump", () => {
+ test("matches expected output for sample payload", () => {
+ const input =
+ 'a:2:{i:0;s:12:"Sample array";i:1;a:2:{i:0;s:5:"Apple";i:1;s:6:"Orange";}}';
+ const parsed = unserialize(input);
+
+ const expected = [
+ "array(2) {",
+ " [0]=>",
+ ' string(12) "Sample array"',
+ " [1]=>",
+ " array(2) {",
+ " [0]=>",
+ ' string(5) "Apple"',
+ " [1]=>",
+ ' string(6) "Orange"',
+ " }",
+ "}",
+ ].join("\n");
+
+ expect(formatVarDump(parsed)).toBe(expected);
+ });
+
+ test("uses UTF-8 byte length for strings", () => {
+ const parsed = unserialize('s:4:"😊";');
+ expect(formatVarDump(parsed)).toBe('string(4) "😊"');
+ });
+ });
+});
diff --git a/components/utils/unserializer.utils.ts b/components/utils/unserializer.utils.ts
new file mode 100644
index 0000000..2c597ba
--- /dev/null
+++ b/components/utils/unserializer.utils.ts
@@ -0,0 +1,450 @@
+export type SerializedValue =
+ | { type: "string"; value: string }
+ | { type: "int"; value: number }
+ | { type: "float"; value: number }
+ | { type: "bool"; value: boolean }
+ | { type: "null" }
+ | {
+ type: "array";
+ entries: Array<{ key: SerializedValue; value: SerializedValue }>;
+ }
+ | {
+ type: "object";
+ className: string;
+ properties: Array<{ key: SerializedValue; value: SerializedValue }>;
+ };
+
+class UnserializeError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = "UnserializeError";
+ }
+}
+
+class Parser {
+ private pos = 0;
+ private readonly parsedValues: SerializedValue[] = [];
+
+ constructor(private input: string) {}
+
+ parse(): SerializedValue {
+ const value = this.readValue();
+ if (this.pos !== this.input.length) {
+ throw new UnserializeError(
+ `Unexpected trailing characters at position ${this.pos}`
+ );
+ }
+ return value;
+ }
+
+ private readValue(): SerializedValue {
+ if (this.pos >= this.input.length) {
+ throw new UnserializeError("Unexpected end of input");
+ }
+
+ const type = this.input[this.pos];
+
+ switch (type) {
+ case "s":
+ return this.readString();
+ case "i":
+ return this.readInt();
+ case "d":
+ return this.readFloat();
+ case "b":
+ return this.readBool();
+ case "N":
+ return this.readNull();
+ case "a":
+ return this.readArray();
+ case "O":
+ return this.readObject();
+ case "r":
+ case "R":
+ return this.readReference();
+ default:
+ throw new UnserializeError(
+ `Unknown type "${type}" at position ${this.pos}`
+ );
+ }
+ }
+
+ private expect(char: string) {
+ if (this.input[this.pos] !== char) {
+ throw new UnserializeError(
+ `Expected "${char}" at position ${this.pos}, got "${this.input[this.pos]}"`
+ );
+ }
+ this.pos++;
+ }
+
+ private readUntil(char: string): string {
+ const start = this.pos;
+ while (this.pos < this.input.length && this.input[this.pos] !== char) {
+ this.pos++;
+ }
+
+ if (this.pos >= this.input.length) {
+ throw new UnserializeError(`Expected "${char}" but reached end of input`);
+ }
+
+ return this.input.slice(start, this.pos);
+ }
+
+ private readString(registerValue = true): SerializedValue {
+ this.expect("s");
+ this.expect(":");
+ const lenStr = this.readUntil(":");
+ const len = parseInt(lenStr, 10);
+ if (Number.isNaN(len) || len < 0) {
+ throw new UnserializeError(`Invalid string length: "${lenStr}"`);
+ }
+
+ this.expect(":");
+ this.expect('"');
+ const value = this.readUtf8ByteLengthString(len);
+ this.expect('"');
+ this.expect(";");
+
+ const parsed: SerializedValue = { type: "string", value };
+ if (registerValue) {
+ this.parsedValues.push(parsed);
+ }
+ return parsed;
+ }
+
+ private readInt(registerValue = true): SerializedValue {
+ this.expect("i");
+ this.expect(":");
+ const numStr = this.readUntil(";");
+ this.expect(";");
+ const value = parseInt(numStr, 10);
+ if (Number.isNaN(value)) {
+ throw new UnserializeError(`Invalid integer value: "${numStr}"`);
+ }
+
+ const parsed: SerializedValue = { type: "int", value };
+ if (registerValue) {
+ this.parsedValues.push(parsed);
+ }
+ return parsed;
+ }
+
+ private readFloat(): SerializedValue {
+ this.expect("d");
+ this.expect(":");
+ const numStr = this.readUntil(";");
+ this.expect(";");
+
+ let value: number;
+ if (numStr === "INF") value = Infinity;
+ else if (numStr === "-INF") value = -Infinity;
+ else if (numStr === "NAN") value = NaN;
+ else {
+ value = parseFloat(numStr);
+ if (Number.isNaN(value)) {
+ throw new UnserializeError(`Invalid float value: "${numStr}"`);
+ }
+ }
+
+ const parsed: SerializedValue = { type: "float", value };
+ this.parsedValues.push(parsed);
+ return parsed;
+ }
+
+ private readBool(): SerializedValue {
+ this.expect("b");
+ this.expect(":");
+ const val = this.readUntil(";");
+ this.expect(";");
+ if (val !== "0" && val !== "1") {
+ throw new UnserializeError(`Invalid boolean value: "${val}"`);
+ }
+
+ const parsed: SerializedValue = { type: "bool", value: val === "1" };
+ this.parsedValues.push(parsed);
+ return parsed;
+ }
+
+ private readNull(): SerializedValue {
+ this.expect("N");
+ this.expect(";");
+ const parsed: SerializedValue = { type: "null" };
+ this.parsedValues.push(parsed);
+ return parsed;
+ }
+
+ private readArray(): SerializedValue {
+ this.expect("a");
+ this.expect(":");
+ const countStr = this.readUntil(":");
+ const count = parseInt(countStr, 10);
+ if (Number.isNaN(count) || count < 0) {
+ throw new UnserializeError(`Invalid array count: "${countStr}"`);
+ }
+ this.expect(":");
+ this.expect("{");
+
+ const parsed: {
+ type: "array";
+ entries: Array<{ key: SerializedValue; value: SerializedValue }>;
+ } = {
+ type: "array",
+ entries: [],
+ };
+ this.parsedValues.push(parsed);
+
+ const entries: Array<{ key: SerializedValue; value: SerializedValue }> = [];
+ for (let i = 0; i < count; i++) {
+ const key = this.readArrayKey();
+ const value = this.readValue();
+ entries.push({ key, value });
+ }
+
+ this.expect("}");
+ parsed.entries = entries;
+ return parsed;
+ }
+
+ private readObject(): SerializedValue {
+ this.expect("O");
+ this.expect(":");
+ const classNameLenStr = this.readUntil(":");
+ const classNameLen = parseInt(classNameLenStr, 10);
+ if (Number.isNaN(classNameLen) || classNameLen < 0) {
+ throw new UnserializeError(
+ `Invalid class name length: "${classNameLenStr}"`
+ );
+ }
+ this.expect(":");
+ this.expect('"');
+ const className = this.readUtf8ByteLengthString(classNameLen);
+ this.expect('"');
+ this.expect(":");
+ const countStr = this.readUntil(":");
+ const count = parseInt(countStr, 10);
+ if (Number.isNaN(count) || count < 0) {
+ throw new UnserializeError(
+ `Invalid object property count: "${countStr}"`
+ );
+ }
+ this.expect(":");
+ this.expect("{");
+
+ const parsed: {
+ type: "object";
+ className: string;
+ properties: Array<{ key: SerializedValue; value: SerializedValue }>;
+ } = {
+ type: "object",
+ className,
+ properties: [],
+ };
+ this.parsedValues.push(parsed);
+
+ const properties: Array<{ key: SerializedValue; value: SerializedValue }> =
+ [];
+ for (let i = 0; i < count; i++) {
+ const key = this.readObjectKey();
+ const value = this.readValue();
+ properties.push({ key, value });
+ }
+
+ this.expect("}");
+ parsed.properties = properties;
+ return parsed;
+ }
+
+ private readReference(): SerializedValue {
+ this.pos++; // skip "r" or "R"
+ this.expect(":");
+ const refStr = this.readUntil(";");
+ this.expect(";");
+
+ const refIndex = parseInt(refStr, 10);
+ if (Number.isNaN(refIndex) || refIndex < 1) {
+ throw new UnserializeError(`Invalid reference index: "${refStr}"`);
+ }
+
+ const referencedValue = this.parsedValues[refIndex - 1];
+ if (!referencedValue) {
+ throw new UnserializeError(`Reference index ${refIndex} does not exist`);
+ }
+
+ return referencedValue;
+ }
+
+ private readUtf8ByteLengthString(byteLength: number): string {
+ const start = this.pos;
+ let consumedBytes = 0;
+
+ while (consumedBytes < byteLength) {
+ if (this.pos >= this.input.length) {
+ throw new UnserializeError(
+ "Unexpected end of input while reading string bytes"
+ );
+ }
+
+ const codePoint = this.input.codePointAt(this.pos);
+ if (codePoint === undefined) {
+ throw new UnserializeError("Invalid string code point");
+ }
+
+ const char = String.fromCodePoint(codePoint);
+ const charByteLength = utf8ByteLength(char);
+ consumedBytes += charByteLength;
+ this.pos += char.length;
+ }
+
+ if (consumedBytes !== byteLength) {
+ throw new UnserializeError(
+ `String byte length mismatch. Expected ${byteLength}, got ${consumedBytes}`
+ );
+ }
+
+ return this.input.slice(start, this.pos);
+ }
+
+ private readArrayKey(): SerializedValue {
+ const keyType = this.input[this.pos];
+ switch (keyType) {
+ case "i":
+ return this.readInt(false);
+ case "s":
+ return this.readString(false);
+ default:
+ throw new UnserializeError(
+ `Invalid array key type "${keyType}" at position ${this.pos}`
+ );
+ }
+ }
+
+ private readObjectKey(): SerializedValue {
+ const keyType = this.input[this.pos];
+ if (keyType !== "s") {
+ throw new UnserializeError(
+ `Invalid object property key type "${keyType}" at position ${this.pos}`
+ );
+ }
+ return this.readString(false);
+ }
+}
+
+export function unserialize(input: string): SerializedValue {
+ if (!input || input.trim() === "") {
+ throw new UnserializeError("Please enter a serialized string.");
+ }
+
+ const parser = new Parser(input.trim());
+ return parser.parse();
+}
+
+export function isValidSerialized(input: string): boolean {
+ try {
+ unserialize(input);
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+function keyToString(key: SerializedValue): string {
+ switch (key.type) {
+ case "int":
+ return String(key.value);
+ case "string":
+ return key.value;
+ case "bool":
+ return key.value ? "1" : "0";
+ case "null":
+ return "";
+ case "float":
+ return String(Math.trunc(key.value));
+ default:
+ return "";
+ }
+}
+
+function utf8ByteLength(value: string): number {
+ return encodeURIComponent(value).replace(/%[A-F\d]{2}/gi, "x").length;
+}
+
+export function formatPrintR(value: SerializedValue, indent = 0): string {
+ const pad = " ".repeat(indent * 4);
+ const innerPad = " ".repeat((indent + 1) * 4);
+
+ switch (value.type) {
+ case "string":
+ return value.value;
+ case "int":
+ case "float":
+ return String(value.value);
+ case "bool":
+ return value.value ? "1" : "";
+ case "null":
+ return "";
+ case "array": {
+ if (value.entries.length === 0) {
+ return `Array\n${pad}(\n${pad})`;
+ }
+
+ let result = `Array\n${pad}(\n`;
+ for (const entry of value.entries) {
+ const k = keyToString(entry.key);
+ const v = formatPrintR(entry.value, indent + 2);
+ result += `${innerPad}[${k}] => ${v}\n`;
+ }
+ result += `${pad})`;
+ return indent > 0 ? `${result}\n` : result;
+ }
+ case "object": {
+ let result = `${value.className} Object\n${pad}(\n`;
+ for (const property of value.properties) {
+ const k = keyToString(property.key);
+ const v = formatPrintR(property.value, indent + 2);
+ result += `${innerPad}[${k}] => ${v}\n`;
+ }
+ result += `${pad})`;
+ return indent > 0 ? `${result}\n` : result;
+ }
+ }
+}
+
+export function formatVarDump(value: SerializedValue, indent = 0): string {
+ const pad = " ".repeat(indent * 2);
+ const innerPad = " ".repeat((indent + 1) * 2);
+
+ switch (value.type) {
+ case "string":
+ return `${pad}string(${utf8ByteLength(value.value)}) "${value.value}"`;
+ case "int":
+ return `${pad}int(${value.value})`;
+ case "float":
+ return `${pad}float(${value.value})`;
+ case "bool":
+ return `${pad}bool(${value.value ? "true" : "false"})`;
+ case "null":
+ return `${pad}NULL`;
+ case "array": {
+ let result = `${pad}array(${value.entries.length}) {\n`;
+ for (const entry of value.entries) {
+ const k = keyToString(entry.key);
+ const keyBracket = entry.key.type === "int" ? `[${k}]` : `["${k}"]`;
+ result += `${innerPad}${keyBracket}=>\n`;
+ result += `${formatVarDump(entry.value, indent + 1)}\n`;
+ }
+ result += `${pad}}`;
+ return result;
+ }
+ case "object": {
+ let result = `${pad}object(${value.className})#1 (${value.properties.length}) {\n`;
+ for (const property of value.properties) {
+ const k = keyToString(property.key);
+ result += `${innerPad}["${k}"]=>\n`;
+ result += `${formatVarDump(property.value, indent + 1)}\n`;
+ }
+ result += `${pad}}`;
+ return result;
+ }
+ }
+}
diff --git a/pages/utilities/unserializer.tsx b/pages/utilities/unserializer.tsx
new file mode 100644
index 0000000..5558269
--- /dev/null
+++ b/pages/utilities/unserializer.tsx
@@ -0,0 +1,164 @@
+import { useCallback, useState } from "react";
+import { Textarea } from "@/components/ds/TextareaComponent";
+import PageHeader from "@/components/PageHeader";
+import { Card } from "@/components/ds/CardComponent";
+import { Button } from "@/components/ds/ButtonComponent";
+import { Label } from "@/components/ds/LabelComponent";
+import Header from "@/components/Header";
+import { useCopyToClipboard } from "@/components/hooks/useCopyToClipboard";
+import { CMDK } from "@/components/CMDK";
+import CallToActionGrid from "../../components/CallToActionGrid";
+import GitHubContribution from "@/components/GitHubContribution";
+import UnserializerSEO from "@/components/seo/UnserializerSEO";
+import Meta from "@/components/Meta";
+import {
+ formatPrintR,
+ formatVarDump,
+ unserialize,
+} from "@/components/utils/unserializer.utils";
+
+type OutputMode = "print_r" | "var_dump";
+
+const SAMPLE_INPUT =
+ 'a:2:{i:0;s:12:"Sample array";i:1;a:2:{i:0;s:5:"Apple";i:1;s:6:"Orange";}}';
+
+export default function Unserializer() {
+ const [input, setInput] = useState("");
+ const [output, setOutput] = useState("");
+ const [error, setError] = useState("");
+ const [mode, setMode] = useState("print_r");
+ const { buttonText, handleCopy } = useCopyToClipboard();
+
+ const runUnserialize = useCallback(
+ (nextInput: string, nextMode: OutputMode) => {
+ if (nextInput.trim() === "") {
+ setOutput("");
+ setError("");
+ return;
+ }
+
+ try {
+ const parsed = unserialize(nextInput);
+ const formatted =
+ nextMode === "print_r" ? formatPrintR(parsed) : formatVarDump(parsed);
+ setOutput(formatted);
+ setError("");
+ } catch (err) {
+ const message = err instanceof Error ? err.message : "Invalid input";
+ setOutput("");
+ setError(message);
+ }
+ },
+ []
+ );
+
+ const handleInputChange = useCallback(
+ (event: React.ChangeEvent) => {
+ const { value } = event.currentTarget;
+ setInput(value);
+ runUnserialize(value, mode);
+ },
+ [mode, runUnserialize]
+ );
+
+ const handleModeChange = useCallback(
+ (nextMode: OutputMode) => {
+ setMode(nextMode);
+ runUnserialize(input, nextMode);
+ },
+ [input, runUnserialize]
+ );
+
+ const loadExample = useCallback(() => {
+ setInput(SAMPLE_INPUT);
+ runUnserialize(SAMPLE_INPUT, mode);
+ }, [mode, runUnserialize]);
+
+ const clearAll = useCallback(() => {
+ setInput("");
+ setOutput("");
+ setError("");
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Serialized Input
+
+ Load Example
+
+
+
+
+
+
+ handleModeChange("print_r")}
+ >
+ print_r()
+
+ handleModeChange("var_dump")}
+ >
+ var_dump()
+
+
+ Clear
+
+
+
+
+ Output ({mode === "print_r" ? "print_r()" : "var_dump()"})
+
+
+
+
handleCopy(output)}
+ disabled={!output || !!error}
+ >
+ {buttonText}
+
+
+
+
+
+
+
+
+
+
+ );
+}